浮点数字的零比较是否有效?

时间:2015-04-27 19:52:29

标签: c++ c++11 floating-point

来自N3337(C ++ 11草案)第3.9.1.8节:

  

浮点类型的值表示是实现定义的。

这是否适用于float类型的任何和所有用法,无论它是否是文字?以下是引起我一些担忧的例子:

float foo{0.0f};
if (foo == 0.0f)
{
    // Am I always guaranteed to get here?
}

如果我们假设0.0f在实现方面并不是真的0,而是一些未定义的数字,那么这种比较在技术上仍然有效,因为两个操作数都是通过常量获得的,即使我可能没有知道它的真正价值,它们两者仍然是一样的吗?

与像这样的浮点文字的平等比较总是有代码味道,我只是想确保某些用例没有意义或有效。

4 个答案:

答案 0 :(得分:1)

是的,你保证能够到达那里。在对有关数字进行处理操作后发生浮动不精确。常数对你来说是安全的。

但是,如果通过提供过多的小数来超过浮点数精度,或者使用其他数据类型初始化浮点数,则可能会有不同的解释。

例如,这可能不会成功:

float foo{2.1234321f};
if (foo * 6.1234321f / 0.1234321f == 105.3428750f)
{
    // Am I always guaranteed to get here? Not at all.
}

如果你想比较浮点数时要安全,你应该"近似"结果。请参阅下面的代码。

#include <limits>
#include <type_traits>

using namespace std;

class exact{};
class approx{};

template<class> struct tolerance;

template<>
struct tolerance<float>
{
    static constexpr float value() { return 0.00001; }
}

template<class T>
bool close_enough(T a, T b, exact)
{
    return a == b;
}

template<class T>
bool close_enough(T a, T b, approx)
{
    return abs(a - b) <= tolerance<T>::value();
}

template<class T>
bool close_enough(T a, T b)
{
    return close_enough(a, b,
        conditional<numeric_limits<T>::is_exact, exact, approx>::type{});
}

int main()
{
    float a = 2.1234321f, b = 105.3428750f;

    if (close_enough(a * 6.1234321f / 0.1234321f, b))
    {
        // Am I always guaranteed to get here? Yes!
    }
    else
    {
        // ...
    }
}

答案 1 :(得分:0)

据我所知,肯定会去那个街区。因为 0.0f 是浮点常量。

float f = 0.0; /* OK, throw away bits to convert 0.0 from double to float */
assert ( f == 0.0 ); /* not OK, f is converted from float to double
   and the value of 0.0 depends on how many bits you use to represent it. */
assert ( f == 0.0f ); /* OK, comparing two floats, although == is finicky. */

另请注意,在float x = 0中,存在从int到float的隐式类型转换。 在float x = 0.0f我们没有这样的类型。在float x = 0.0中,我们有一个从double到float的隐式类型转换。

好读:what every computer scientist should know about floating-point arithmetic

答案 2 :(得分:0)

虽然标准中关于浮点数的技术上几乎没有保证,但CPU只对它们使用两种不同的表示:二进制编码的十进制和IEEE 754。现在第一个是相当深奥的,并且从未默认使用,因为它提供的精度低于IEEE浮点数,因此可以安全地假设只要有浮点就有IEEE数。

为什么这很重要?仅仅因为IEEE格式保证了很多值的精度。具体来说,它定义了+0.0-0.0是什么,并且从格式规范开始,float可以表示-1677721616777216范围内的所有整数究竟。 (同样地,double格式允许范围-2^532^53,这非常舒适。)从这里,您可以扣除计算保持精确的时间,从而允许平等比较。

但是,每当您使用此信息时,您还应该在扣除时写下评论,您不能认为其他人会发现它们显而易见......

答案 3 :(得分:-1)

实践中

float foo = 0.0 ;
if (foo == 0.0)
{
}

倾向于工作。但是,做

float foot = SOMEARBITRARYCONSTANT ;
if (foo == SOMEARBITRARYCONSTANT)
{
}

可能无效。

当SOMEARBITRARYCONSTANT是一个可以准确表示的值时,它可能会起作用,否则可能会失败。

我见过相当于:

float foo = 0.1 ;
if (foo == 0.1)
{
}

不起作用,因为编译器以不同方式舍入0.1。

一般来说,浮点==是一个坏主意。