这种与浮点文字零的比较有效吗?

Is this comparison to floating point literal zero valid?

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

The value representation of floating-point types is implementation-defined.

这是否适用于浮点类型的所有用法,无论它是否为文字?这是让我有些担心的例子:

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

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

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

是的,您一定会到达那里。对相关数字进行处理操作后出现浮点数不精确。常量在您的情况下是安全的。

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

例如,这可能不会成功:

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

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

#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
    {
        // ...
    }
}

据我所知是的,可以保证去那个街区。因为 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

在实践中

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。

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

虽然标准中关于浮点数的技术保证很少,但 CPU 仅使用两种不同的表示形式:二进制编码的十进制数和 IEEE 754。现在第一个相当深奥,默认情况下从未使用过,因为它提供的精度低于 IEEE 浮点数,因此可以安全地假设只要你有浮点数,你就有 IEEE 数字。

为什么这很重要?仅仅是因为 IEEE 格式保证了很多值的精度。具体来说,它定义了 +0.0-0.0 是什么,并且从格式规范可以看出 float 可以表示 -1677721616777216 范围内的所有整数确切地。 (同样,double 格式允许 -2^532^53 的范围,这很舒服。平等比较。

但是,无论何时您使用这些信息,您还应该对您的推论进行评论,您不能假设其他人会发现它们是显而易见的...