我有以下虚拟测试脚本:
函数测试(){变量x=0.1*0.2;document.write(x);}测试();
这将打印结果0.020000000000000004,而它应该只打印0.02(如果您使用计算器)。据我所知,这是由于浮点乘法精度的错误。
有没有人有一个好的解决方案,在这种情况下,我得到了正确的结果0.02?我知道还有一些函数,比如toFixed或舍入,这是另一种可能,但我真的希望在不进行任何切割和舍入的情况下打印整个数字。我只是想知道你们中的一个人是否有一些好的、优雅的解决方案。
当然,否则我会舍入到10位数左右。
浮点值编程缺乏精度
在不同的编程语言中,浮点值编程缺乏精度是一个已知的问题。Javascript是使用浮点值进行数学运算时遇到问题的一种。这种问题主要是由于这些编程语言实现的浮点数的内存二进制表示。
让我们希望编程语言、编译器、硬件开发人员和工程师能够一次性解决这些问题,至少解决1024个浮点数字。。。也许这将足以让99.999%的其他开发人员在下一个世纪的编程中感到安心。。。
一种可能的解决方案:转换为整数,计算,然后返回
一种可能的解决方案是将浮点数转换为整数,执行计算,然后将结果转换回浮点数。这似乎在大多数时候都有效。原则上:
函数测试(){变量x=0.1*0.2;//原始方法变量y=((0.1*10)*(0.2*10))/100;//可行的解决方案document.write(x+'<br>'+y);}测试();
当然,在需要的情况下,可以相应地调整功能,例如:
函数测试(){变量x=0.12*0.23;//原始方法变量y=((0.12*100)*(0.23*100))/10000;//可行的解决方案document.write(x+'<br>'+y);}测试();
它也可以是自动化的:
函数myToInt(v){设i=1;设r;而(r%1!=0){i=i*10;r=v*i;}返回[r,i];}函数测试(){设x=0.11111111;设y=0.22222222;设a=x*y;//原始方法[x,i]=myToInt(x);[y,j]=myToInt(y);设d=i*j;设b=x*y/d;//另一种可行的解决方案console.log(a+'\n'+b)document.write(a+'<br>'+b);}测试();
考虑到这一点,我们可以这样做:
函数myToInt(v){设i=1;设r;而(r%1!=0){i=i*10;r=v*i;}返回[r,i];}函数测试(){设x=14898设y=10.8设z=100设a=x*y/z;//原始方法[y,i]=myToInt(y);设b=(x*y)/(z*i);//另一种可行的解决方案console.log(a+'\n'+b)document.write(a+'<br>'+b);}测试();
从我的角度来看,这里的想法是将fp数舍入,以便获得一个漂亮/简短的默认字符串表示。
53位有效位精度提供15到17位有效小数位数精度(2−53≈1.11×10−16)。如果具有最多15个有效数字的十进制字符串被转换为IEEE 754双精度表示,然后转换回具有相同位数的十进制字符串,最终结果应与原始字符串匹配。如果IEEE 754双精度数字被转换为具有至少17个有效数字的十进制字符串,然后转换回双精度表示,最终结果必须与原始数字匹配。...由于分数(F)有效位的52位出现在内存格式中,因此总精度为53位(约16位小数,53 log10(2)≈15.955)。。。维基百科
(0.1).toPrecision(100) ->
0.1000000000000000055511151231257827021181583404541015625000000000000000000000000000000000000000000000
(0.1+0.2).toPrecision(100) ->
0.3000000000000000444089209850062616169452667236328125000000000000000000000000000000000000000000000000
然后,据我所知,我们可以将值四舍五入到15位,以保持良好的字符串表示。
10**Math.floor(53 * Math.log10(2)) // 1e15
eg.
Math.round((0.2+0.1) * 1e15 ) / 1e15
0.3
(Math.round((0.2+0.1) * 1e15 ) / 1e15).toPrecision(100)
0.2999999999999999888977697537484345957636833190917968750000000000000000000000000000000000000000000000
功能如下:
function roundNumberToHaveANiceDefaultStringRepresentation(num) {
const integerDigits = Math.floor(Math.log10(Math.abs(num))+1);
const mult = 10**(15-integerDigits); // also consider integer digits
return Math.round(num * mult) / mult;
}
问题
浮点不能精确存储所有十进制值。因此,当使用浮点格式时,输入值将始终存在舍入错误。输入端的错误当然会导致输出端的错误。在离散函数或运算符的情况下,函数或运算符离散的点附近的输出可能存在很大差异。
浮点值的输入和输出
因此,在使用浮点变量时,您应该始终注意这一点。并且,在显示之前,无论您希望从带有浮点的计算中获得什么输出,都应该在显示之前进行格式化/调整。当只使用连续函数和运算符时,舍入到所需的精度通常是可行的(不要截断)。用于将浮点数转换为字符串的标准格式功能通常会为您提供这一功能。由于舍入增加了一个误差,该误差可能导致总误差超过所需精度的一半,因此应根据输入的预期精度和输出的预期精度对输出进行校正。你应该
将输入舍入到预期精度,或确保不能输入精度更高的值。在舍入/格式化输出之前,为输出添加一个小值,该值小于或等于所需精度的1/4,并且大于输入和计算期间舍入误差导致的最大预期误差。如果不可能,则所用数据类型的精度组合不足以为计算提供所需的输出精度。
这两件事通常都没有做,在大多数情况下,不做它们所造成的差异太小,对大多数用户来说都不重要,但我已经有了一个项目,如果没有这些更正,输出不会被用户接受。
离散函数或运算符(如模)
当涉及离散运算符或函数时,可能需要额外的校正以确保输出符合预期。舍入和在舍入之前添加小的修正并不能解决这个问题。应用离散函数或运算符后,可能需要立即对中间计算结果进行特殊检查/校正。对于特定的情况(模运算符),请参阅我对问题的回答:为什么模运算符在javascript中返回小数?
最好避免出现问题
通过使用数据类型(整数或定点格式)进行这样的计算,通常可以更有效地避免这些问题,这样可以存储预期的输入,而不会出现舍入误差。这方面的一个例子是,您永远不应该在财务计算中使用浮点值。