36

假设符合IEEE-754 标准,浮点数在通过双精度传输时是否可以保证保留?

换句话说,以下断言是否总是被满足?

int main()
{
    float f = some_random_float();
    assert(f == (float)(double)f);
}

假设f可以获取 IEEE 定义的任何特殊值,例如 NaN 和 Infinity。

根据 IEEE 的说法,是否存在满足断言的情况,但在通过 double 传输后不保留确切的位级表示?

代码片段在 C 和 C++ 中均有效。

4

3 回答 3

31

你甚至不需要假设 IEEE。C89 在 3.1.2.5 中说:

类型的值集是类型float值集的子集double

并且所有其他 C 和 C++ 标准都表示相同的内容。据我所知,NaN 和无穷大是“类型的值float”,尽管在用作操作数时具有一些特殊情况规则的值。

float -> double -> float 转换恢复了以下的原始值float(通常),这是因为数字转换都保留了在目标类型中可表示的值。

位级表示是一个稍微不同的问题。想象一下,有一个值,float它有两种不同的按位表示。那么 C 标准中的任何内容都不会阻止 float -> double -> float 转换从一个转换到另一个。在 IEEE 中,除非有填充位,否则“实际值”不会发生这种情况,但我不知道 IEEE 是否排除了具有不同按位表示的单个 NaN。无论如何,NaN 不会与自己比较,因此除了可能将它们转换为字符串之外,也没有标准的方法来判断两个 NaN 是“相同的 NaN”还是“不同的 NaN”。这个问题可能没有实际意义。

需要注意的一件事是编译器的不一致模式,其中它们将超精确值“隐藏”在其中,例如留在浮点寄存器中的中间结果并在不进行舍入的情况下重复使用。我认为这不会导致您的示例代码失败,但是一旦您进行浮点运算==,您就会开始担心这种事情。

于 2013-02-08T13:20:12.447 回答
16

从 C99 开始:

6.3.1.5 实浮点类型
1 当float 提升为double 或long double,或double 提升为long double 时,其值不变。
2 当 double 降级为 float 时,long double 降级为 double 或 float,或者以比其语义类型(见 6.3.1.8)要求的更高的精度和范围表示的值显式转换为其语义类型,如果被转换的值可以在新类型中精确表示,它是不变的......

我认为,这可以保证 float->double->float 转换将保留原始浮点值。

该标准还定义了宏INFINITYNANin 7.12 Mathematics <math.h>

4 宏 INFINITY 扩展为浮点类型的常量表达式,表示正或无符号无穷大(如果可用);否则为在翻译时溢出的浮点类型的正常量。
5 当且仅当实现支持浮点类型的静默 NaN 时,才定义宏 NAN。它扩展为一个浮点类型的常量表达式,表示一个安静的 NaN。

因此,对此类特殊值进行了规定,并且转换也可能适用于它们(包括负无穷大和负零)。

于 2013-02-08T13:08:30.860 回答
2

断言将在刷新为零和/或非规范化为零模式下失败(例如,使用 -mfpmath=sse、-fast-math 等编译的代码,但也会在默认的编译器和架构堆上,例如 Intel 的C++ 编译器)如果 f 是非规范化的。

但是,您不能在该模式下生成非规范化浮点数,但这种情况仍然是可能的:

a) 非规范化浮点数来自外部源。

b) 一些库篡改了 FPU 模式,但在每次调用它后忘记(或故意避免)将它们设置回来,从而使调用者可能不匹配规范化。

打印以下的实际示例:

f = 5.87747e-39
f2 = 5.87747e-39

f = 5.87747e-39
f2 = 0
error, f != f2!

该示例适用于 VC2010 和 GCC 4.3,但假设 VC 默认使用 SSE 进行数学运算,而 GCC 使用 FPU 作为默认数学运算。否则,该示例可能无法说明问题。

#include <limits>
#include <iostream>
#include <cmath>

#ifdef _MSC_VER
#include <xmmintrin.h>
#endif

template <class T>bool normal(T t)
{
    return (t != 0 || fabsf( t ) >= std::numeric_limits<T>::min());
}

void csr_flush_to_zero()
{
#ifdef _MSC_VER
    _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
#else
    unsigned csr = __builtin_ia32_stmxcsr();
    csr |= (1 << 15);
    __builtin_ia32_ldmxcsr(csr);
#endif
}

void test_cast(float f) 
{
    std::cout << "f = " << f << "\n";
    double d = double(f);
    float f2 = float(d);
    std::cout << "f2 = " << f2 << "\n";

    if(f != f2)
        std::cout << "error, f != f2!\n";

    std::cout << "\n";
}

int main()
{
    float f = std::numeric_limits<float>::min() / 2.0;

    test_cast(f);
    csr_flush_to_zero();
    test_cast(f);
}
于 2013-02-11T23:12:28.310 回答