请注意,我的问题不是“为什么浮点数会失去精度”我知道并非数字的所有小数部分都可以采用二进制形式。真正存储的实际上是“最接近的数字” 占用了所有尾数位空间。
鉴于:
//This is just an example.
float price1 = 17.96f, price3 = 17.98f;
double price2 = 17.96, price4 = 17.98;
printf("1. 17.96 = %f (as cents:%d)\n", price1, (int)(price1 * 100));
printf("2. 17.96 = %lf (as cents:%d)\n", price2, (int)(price2 * 100));
printf("3. 17.98 = %f (as cents:%d)\n", price3, (int)(price3 * 100));
printf("4. 17.98 = %lf (as cents:%d)\n", price4, (int)(price4 * 100));
输出是
1. 17.96 = 17.959999 (as cents:1795)
2. 17.96 = 17.960000 (as cents:1796)
3. 17.98 = 17.980000 (as cents:1798)
4. 17.98 = 17.980000 (as cents:1798)
我将打印格式改为小数点后25位,看看内存中存储的“实数”是多少。 输出结果
1. 17.96 = 17.9599990844726562500000000 (as cents:1795)
2. 17.96 = 17.9600000000000008526512829 (as cents:1796)
3. 17.98 = 17.9799995422363281250000000 (as cents:1798)
4. 17.98 = 17.9800000000000004263256415 (as cents:1798)
问题是,根据“实际存储的数字”是 17.9599990844726562500000000,为什么 (int)(price1 * 100) 结果是 1795 而不是 1796。
但是 (int)(price3 * 100) 结果是 1798 而不是 1797。而“实际存储的数字”是 17.9799995422363281250000000。 “the”实数 * 100 等于 1797.99995422363281250000000。所以整数截断后,应该是 1797,但我得到了 1798。
常见的
float
和 double
是一些整数乘以 2 的幂。参见 二元有理数。
17.96
和 17.98
不能 完全这样表示。
而是使用附近的可表示数字,该数字位于所需代码浮点常量的上方或下方。
printf("%.52e %.52e\n", 17.96f, 17.98f);
printf("%.52e %.52e\n", 17.96, 17.98);
1.7959999084472656250000000000000000000000000000000000e+01 1.7979999542236328125000000000000000000000000000000000e+01
1.7960000000000000852651282912120223045349121093750000e+01 1.7980000000000000426325641456060111522674560546875000e+01
按 100 缩放会产生 额外舍入效应 - 可能会向上一点或向下一点。产品可能是 1796、1798 或略高于或略低于。
回想一下,
17.96f * 100
与数学17.96 * 100不同,但17.95999908447265625f * 100.0f
(即1795.999908447265625),然后该乘积四舍五入到最接近的float
:1795.9998779296875。printf("%.52e %.52e\n", 17.96f * 100, 17.98f * 100);
printf("%.52e %.52e\n", 17.96 * 100, 17.98 * 100);
1.7959998779296875000000000000000000000000000000000000e+03 1.7980000000000000000000000000000000000000000000000000e+03
1.7960000000000000000000000000000000000000000000000000e+03 1.7980000000000000000000000000000000000000000000000000e+03
C23 可选择提供十进制浮点,最适合解决类似的十进制问题。在没有清楚了解边缘效应的情况下,不要对浮点计算结果使用像
(int)
这样的粗略转换。
应用int
转换是有问题的编码,因为乘积计算可能会导致 1796 或 1798,因为之前的乘积是 1795.999...1797.999...使用
lround()
会更有意义。
要使用 float
(一个糟糕的主意)扩大货币规模,请乘以 100.0(
double
)并使用 llround()
或 lround()
。要使用 double
将货币缩放为整数,请乘以 100.0L(a
long double
)并使用 llroundl()
或 lroundl()
。额外的精度将有助于解决边缘情况。
printf("%ld %ld\n", lround(17.96f * 100.0), lround(17.98f * 100.0));
printf("%lld %lld\n", llroundl(17.96 * 100.0L), llroundl(17.98 * 100.0L));
1796 1798
1796 1798
17.96f * 100
就像
17.96f * 100.0f
- float
乘法。这与 17.96f * 100.0
- double
乘法不同。