通过C / C ++中的双精度传输时,是否可以保留浮点数?

时间:2013-02-08 13:00:04

标签: c++ c floating-point double ieee-754

假设IEEE-754符合性,是否保证在通过双重传输时保留浮点数?

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

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

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

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

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

3 个答案:

答案 0 :(得分:30)

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

  

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

其他所有C和C ++标准都说明了相同的东西。据我所知,NaNs和无穷大是“float类型的值”,虽然在用作操作数时具有一些特殊情况规则的值。

浮动的事实 - >双 - >浮点转换会恢复float的原始值(通常),因为如果数值转换在目标类型中可以表示,则数值转换都会保留该值。

比特级表示略有不同。想象一下,float的值有两个不同的按位表示。那么C标准中的任何内容都不会阻止浮动 - >双 - >从一个切换到另一个的浮动转换。在IEEE中,除非存在填充位,否则“实际值”不会发生,但我不知道IEEE是否排除了具有不同按位表示的单个NaN。 NaNs无论如何都不等于它们,所以也没有标准方法来判断两个NaN是否是“相同的NaN”或“不同的NaN”,而不是将它们转换为字符串。这个问题可能没什么问题。

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

答案 1 :(得分: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转换将保留原始的float值。

该标准还定义了INFINITY中的宏NAN7.12 Mathematics <math.h>

  

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

所以,有这样的特殊值的规定,转换也可能对它们起作用(包括负无穷大和负零)。

答案 2 :(得分:2)

断言将在flush-to-zero和/或denormalized-is-zero模式下失败(例如,使用-mfpmath = sse,-fast-math等编译的代码,但默认情况下编译器和体系结构也是如此,如果f被非规范化,那么比如英特尔的C ++编译器。

虽然您无法在该模式下生成非规范化浮点数,但仍然可以使用该场景:

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);
}