考虑以下代码:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
为什么会出现这些错误?
考虑以下代码:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
为什么会出现这些错误?
当前回答
由于这篇文章对当前的浮点实现进行了一般性的讨论,我想补充一下,有一些项目正在解决它们的问题。
看看https://posithub.org/例如,它展示了一种称为posit(及其前身unum)的数字类型,它承诺以更少的比特提供更好的精度。如果我的理解是正确的,它也解决了问题中的问题。非常有趣的项目,背后的人是数学家约翰·古斯塔夫森博士。整个过程都是开源的,用C/C++、Python、Julia和C#实现了许多实际的实现(https://hastlayer.com/arithmetics).
其他回答
浮点舍入错误。从每个计算机科学家应该知道的浮点运算:
将无限多的实数压缩成有限位数需要近似表示。虽然有无限多的整数,但在大多数程序中,整数计算的结果可以存储在32位中。相反,给定任何固定位数,大多数使用实数的计算将产生无法使用那么多位数精确表示的量。因此,浮点计算的结果必须经常舍入,以适应其有限表示。这种舍入误差是浮点计算的特征。
这个问题的许多重复问题都是关于浮点舍入对特定数字的影响。在实践中,通过查看感兴趣的计算的确切结果而不是仅仅阅读它,更容易了解它的工作原理。一些语言提供了实现这一点的方法,例如在Java中将浮点或双精度转换为BigDecimal。
由于这是一个语言不可知的问题,因此需要语言不可知工具,例如十进制到浮点转换器。
将其应用于问题中的数字,视为双精度:
0.1转换为0.1000000000000000055511151231257827021181583404541015625,
0.2转换为0.200000000000000011102230246251565404236316680908203125,
0.3转换为0.299999999999999988897769753748434595763683319091796875,以及
0.300000000000000004转换为0.30000000000000000444089209850062616169452667236328125。
手动或在十进制计算器(如Full Precision calculator)中添加前两个数字,显示实际输入的精确和为0.30000000000000000166533453693773481063544750213623046875。
如果四舍五入到等于0.3,则舍入误差将为0.000000000000000027755575615628913510591702705078125。四舍五入等于0.300000000000000004也会产生舍入误差0.000000000000000027755575615628913510591702705078125。打成平手的规则适用。
返回浮点转换器,0.300000000000000004的原始十六进制是3fd333333333334,以偶数结尾,因此是正确的结果。
浮点舍入错误。由于缺少5的素因子,0.1在基-2中不能像在基-10中那样精确地表示。正如1/3以十进制表示需要无限位数,但以3为基数表示为“0.1”,0.1以2为基数表示,而以10为基数不表示。计算机没有无限的内存。
为了好玩,我按照标准C99的定义玩了浮点数的表示,并编写了下面的代码。
代码以3个独立的组打印浮点的二进制表示
SIGN EXPONENT FRACTION
然后,它打印一个和,当以足够的精度求和时,它将显示硬件中真正存在的值。
因此,当你写float x=999…时,编译器会将该数字转换为函数xx打印的位表示,这样函数yy打印的和就等于给定的数字。
事实上,这个总数只是一个近似值。对于数字999999999,编译器将在浮点的位表示中插入数字1000000000
代码之后,我附加了一个控制台会话,在该会话中,我计算硬件中真正存在的两个常量(减去PI和999999999)的项和,并由编译器插入其中。
#include <stdio.h>
#include <limits.h>
void
xx(float *x)
{
unsigned char i = sizeof(*x)*CHAR_BIT-1;
do {
switch (i) {
case 31:
printf("sign:");
break;
case 30:
printf("exponent:");
break;
case 23:
printf("fraction:");
break;
}
char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
printf("%d ", b);
} while (i--);
printf("\n");
}
void
yy(float a)
{
int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
int fraction = ((1<<23)-1)&(*(int*)&a);
int exponent = (255&((*(int*)&a)>>23))-127;
printf(sign?"positive" " ( 1+":"negative" " ( 1+");
unsigned int i = 1<<22;
unsigned int j = 1;
do {
char b=(fraction&i)!=0;
b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
} while (j++, i>>=1);
printf("*2^%d", exponent);
printf("\n");
}
void
main()
{
float x=-3.14;
float y=999999999;
printf("%lu\n", sizeof(x));
xx(&x);
xx(&y);
yy(x);
yy(y);
}
这里是一个控制台会话,我在其中计算硬件中存在的浮点值的实际值。我使用bc打印主程序输出的项的总和。可以将该和插入python-repl或类似的内容中。
-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872
就是这样。999999999的值实际上是
999999999.999999446351872
您也可以通过bc检查-3.14也受到干扰。不要忘记在bc中设置比例因子。
显示的金额是硬件内部的金额。通过计算它获得的值取决于设置的比例。我确实将比例因子设置为15。数学上,以无限的精度,它似乎是1000000000。
我的解决方法:
function add(a, b, precision) {
var x = Math.pow(10, precision || 2);
return (Math.round(a * x) + Math.round(b * x)) / x;
}
精度是指在加法过程中要保留小数点后的位数。