浮动浮动

问题描述

float a = 1.0 + ((float) (1 << 25))
float b = 1.0 + ((float) (1 << 26))
float c = 1.0 + ((float) (1 << 27))

运行这段代码后,a、b、c 的浮点值是多少?解释为什么 a、b 和 c 的位布局会导致每个值都是原来的样子。

解决方法

运行这段代码后,a、b 和 c 的浮点值是多少?

int 为 32 位时,以下整数移位定义明确且准确。代码不会移动 float @EOF

// OK with 32-bit int
1 << 25
1 << 26
1 << 27

转换为 float,上述 2 的幂值也定义明确,没有精度损失。

// OK and exact
(float) (1 << 25)
(float) (1 << 26)
(float) (1 << 27)

将这些添加到 double 1.0 是定义明确的精确和。典型的 double 有 53 位有效数,可以准确地表示 0x8000001.0p0。例如:DBL_MANT_DIG == 53

// Let us use hexadecimal FP notation
1.0 + ((float) (1 << 25))  // 0x2000001.0p0 or 0x1.0000008p+25
1.0 + ((float) (1 << 26))  // 0x4000001.0p0 or 0x1.0000004p+26
1.0 + ((float) (1 << 27))  // 0x8000001.0p0 or 0x1.0000002p+27

最终代码尝试将 double 值分配给 float,而在典型的 float 编码范围内,无法准确表示这些值。

typical float 有一个 24 位有效数。例如:FLT_MANT_DIG == 24

如果要转换的值在可以表示但不能准确表示的值范围内,则结果是最接近的较高或最接近的较低可表示值,以实现定义的方式选择。 C17dr § 6.3.1.4 2.

典型的实现定义方式四舍五入到最近,与偶数相关。

  float s = 0x0800001.0p0; printf("%a\n",s);
  float t = 0x1000001.0p0; printf("%a\n",t);// 0x1000001.0p0 1/2 way between two floats 
  float a = 0x2000001.0p0; printf("%a\n",a);
  float b = 0x4000001.0p0; printf("%a\n",b);
  float c = 0x8000001.0p0; printf("%a\n",c);

输出

0x1.000002p+23   // exact conversion double to float
0x1p+24          
0x1p+25
0x1p+26
0x1p+27

解释为什么 a、b 和 c 的位布局会导致每个值都是原来的样子。

位布局不是问题。它是带有 floatFLT_MANT_DIG == 24 的属性,它是一个 24 位有效数和实现定义的行为,导致 double 值四舍五入为附近的 float 一个。任何带有 floatFLT_MANT_DIG == 24 布局都会有类似的结果。