为什么整数截断在这种情况下不适用?

问题描述 投票:0回答:1

请注意,我的问题不是“为什么浮点数会失去精度”我知道并非数字的所有小数部分都可以采用二进制形式。

鉴于:

//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。为什么在进行显式转换时它不适用于整数截断?

c floating-point double precision floating-accuracy
1个回答
3
投票

常见的

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不同,但1.795999908447265625f * 100.0f(即179.5999908447265625),然后该乘积四舍五入到最接近的
float
:179.59998779296875。
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
乘法不同。
    

© www.soinside.com 2019 - 2024. All rights reserved.