【问题标题】:Floating-point-to-integer conversion rounding up instead of truncating浮点到整数的转换舍入而不是截断
【发布时间】:2021-03-05 22:00:31
【问题描述】:

我惊讶地发现浮点到整数的转换是向上舍入而不是截断小数部分。以下是一些使用 Clang 编译的示例代码,可重现该行为:

double a = 1.12;  // 1.1200000000000001 * 2^0
double b = 1024LL * 1024 * 1024 * 1024 * 1024;  // 1 * 2^50
double c = a * b;  // 1.1200000000000001 * 2^50
long long d = c;  // 1261007895663739

使用精确数学,浮点值表示

1.1200000000000001 * 2^50 = 1261007895663738.9925899906842624

由于截断,我期望得到的整数是1261007895663738,但实际上它是1261007895663739。为什么?

【问题讨论】:

  • 使用精确数学 - 但 C 不使用精确数学。 ideone.com/kG3SD4
  • 1.121.1200000000000001 都不是精确的 double 值。
  • 假设 double 映射到 IEEE-754 binary64: a=1.1200000000000001e+00 b=1.1258999068426240e+15 c=1.2610078956637390e+15 trunc(c)=1.2610078956637390e+15 floor(c)=1.2610078956637390e+15 ceil(c)=1.2610078956637390e+15 rint(c)=1.2610078956637390e+15 round(c)=1.2610078956637390e+15 d=1261007895663739。换句话说,c 是一个整数值,在转换为d 的过程中不会丢失任何信息。
  • double a = 1.12; // 1.1200000000000001 * 2^0 这是错误的。基数是 10,而不是 2:1.1200000000000001 * 10^0

标签: c floating-point clang ieee-754 floating-point-conversion


【解决方案1】:

假设 IEEE 754 双精度,1.12 正好

1.12000000000000010658141036401502788066864013671875

用二进制写,它的有效数字是:

1.0001111010111000010100011110101110000101000111101100

注意最后两个零是有意的,因为它是双精度(小数分隔符前 1 位,加上 52 个小数位)。

所以,如果你移动 50 个位置,你会得到一个整数值

100011110101110000101000111101011100001010001111011.00

或十进制

1261007895663739

当转换为long long时,不会发生截断/舍入,转换是精确的。

【讨论】:

  • 有趣。我还注意到,如果我将double 更改为long double,浮点数是不同的(更精确)。您可以在答案中对此进行扩展吗?谢谢!
  • 假定80位的x86的推广的精度,也就是64位有效数字,1.12确切值是1.120000000000000000004336808689942017736029811203479766845703125,从而比双精度略小...除以2 ^ 50移,则得到完全相同“1261007895663738.8800048828125 '。转换为 long long 时,应将其截断为 1261007895663738...
  • 请注意,我正在使用 Squeak Smalltalk 轻松获取这些值。我没有在答案中提到它,因为与C标签没有直接关系。但由于它是相同的底层浮点(就 C 遵守 IEEE 754 而言,它并不总是如此),任何具有方便实用程序的语言都可以完成这项工作。 Smalltalk 有 LargeInteger (bigNum)、精确分数,我用 ArbitraryPrecisionFloat 对其进行了扩展,这是一个很好的候选对象。
  • 这是我使用((ArbitraryPrecisionFloat readFrom: '1.12' readStream numBits: 64) timesTwoPower: 50) asFraction printShowingMaxDecimalPlaces: 1001.12 significandAsInteger printStringBase: 2等的sn-p示例...
  • 但它们几乎相同,除了最后几位。当一个值不能以基数 2 表示时(如 4/3 由无限系列的位 1+1/2^2+1/2^4+1/2^6+1/2^8+... .. 即 1.01010101010101010101010...),无限系列的位必须四舍五入到可用的精度,即双精度有效位的前导 1 之后的 52 位和长双精度的 63 位(至少在 x86 上)。 1.12 是 112/100 = 28/25。 1/25 在基数 2 中不可表示,1/5 是无限位系列 0.00110011001100110011 .....
【解决方案2】:

使用精确数学,浮点值表示 ...

a 不完全是 1.12,因为 0.12 不是 dyadic

// `a` not exactly 1.12 
double a = 1.12;  // 1.1200000000000001 * 2^0

double 附近的值:

1.11999999999999988...  Next closest double
1.12                    Code
1.12000000000000011...  Closest double
1.12000000000000033...

相反,让我们更接近更真实的价值观。

#include <stdio.h>
#include <float.h>

int main() {
  double a = 1.12;  // 1.1200000000000001 * 2^0
  double b = 1024LL * 1024 * 1024 * 1024 * 1024;  // 1 * 2^50
  int prec = DBL_DECIMAL_DIG;
  printf("a %.*e\n", prec, a);
  printf("b %.*e\n", prec, b);

  double c = a * b;
  double whole;
  printf("c %.*e (r:%g)\n", prec, c, modf(c, &whole));
  long long d = (long long) c;
  printf("d %lld\n", d);
}

输出

a 1.12000000000000011e+00
b 1.12589990684262400e+15
c 1.26100789566373900e+15 (r:0)
d 1261007895663739

【讨论】:

    猜你喜欢
    • 2017-04-27
    • 2011-12-27
    • 1970-01-01
    • 2011-01-26
    • 2021-10-09
    • 1970-01-01
    • 2010-10-13
    相关资源
    最近更新 更多