考虑以下代码:
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
为什么会出现这些错误?
当前回答
简而言之,这是因为:
浮点数不能以二进制精确表示所有小数
因此,就像10/3不精确地存在于基数10中(它将是3.33……重复出现)一样,1/10也不存在于二进制中。
那又怎么样?如何处理?有什么解决办法吗?
为了提供最佳解决方案,我可以说我发现了以下方法:
parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3
让我解释一下为什么这是最好的解决方案。正如上面提到的其他答案一样,使用现成的Javascript toFixed()函数来解决问题是一个好主意。但很可能你会遇到一些问题。
假设你将两个浮点数相加,如0.2和0.7,这里是:0.2+0.7=0.8999999999999999。
您的预期结果是0.9,这意味着您需要一个精度为1位数的结果。因此,您应该使用(0.2+0.7).tfixed(1)但是不能只给toFixed()一个特定的参数,因为它取决于给定的数字,例如
0.22 + 0.7 = 0.9199999999999999
在本例中,您需要2位精度,因此它应该为Fixed(2),那么,适合每个给定浮点数的参数应该是什么?
你可以说在每种情况下都是10:
(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"
该死你打算怎么处理那些9后不需要的零?现在是将其转换为浮动的时候了,以实现您的愿望:
parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9
既然找到了解决方案,那么最好将其作为如下函数提供:
function floatify(number){
return parseFloat((number).toFixed(10));
}
让我们自己试试吧:函数floatify(数字){return parseFloat((number).toFixed(10));}函数addUp(){var number1=+$(“#number1”).val();var number2=+$(“#number2”).val();var expectedResult=number1+number2;var expectedResult=浮动(number1+number2);$(“#意外结果”).text(意外结果);$(“#expectedResult”).text(expectedResult);}addUp();输入{宽度:50px;}#预期结果{颜色:绿色;}#未预期结果{颜色:红色;}<script src=“https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js“></script><input id=“number1”value=“0.2”onclick=“addUp()”onkeyup=“addUp()”/>+<input id=“number2”value=“0.7”onclick=“addUp()”onkeyup=“addUp()”/>=<p>预期结果:<span id=“expectedResult”></span></p><p>意外结果:<span id=“expectedResult”></span></p>
您可以这样使用:
var x = 0.2 + 0.7;
floatify(x); => Result: 0.9
正如W3SCHOOLS所建议的,还有另一种解决方案,您可以通过乘法和除法来解决上述问题:
var x = (0.2 * 10 + 0.1 * 10) / 10; // x will be 0.3
请记住,(0.2+0.1)*10/10根本不起作用,尽管看起来是一样的!我更喜欢第一种解决方案,因为我可以将其作为一个函数应用,将输入浮点转换为精确的输出浮点。
仅供参考,乘法也存在同样的问题,例如0.09*10返回0.8999999999999999。应用flotify函数作为解决方法:flotify(0.09*10)返回0.9
其他回答
它被打破的方式与你在小学学习并每天使用的十进制(以10为基础)表示法完全相同,只是以2为基础。
要理解,请考虑将1/3表示为十进制值。这是不可能做到的!世界将在你写完小数点后的3之前结束,所以我们写了一些地方,认为它足够准确。
以同样的方式,1/10(十进制0.1)不能以2为基数(二进制)精确地表示为“十进制”值;小数点后的重复模式将永远持续下去。该值不精确,因此无法使用常规浮点方法对其进行精确计算。与基数10一样,还有其他值也显示了这个问题。
这个问题的许多重复问题都是关于浮点舍入对特定数字的影响。在实践中,通过查看感兴趣的计算的确切结果而不是仅仅阅读它,更容易了解它的工作原理。一些语言提供了实现这一点的方法,例如在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,以偶数结尾,因此是正确的结果。
正常的算术是以10为基数的,所以小数表示十分、百分等。当你试图用二进制2为基数的算术表示浮点数时,你要处理的是半、四、八等。
在硬件中,浮点存储为整数尾数和指数。尾数表示有效数字。指数类似于科学记数法,但它使用的基数是2而不是10。例如,64.0将用尾数1和指数6表示。0.125将用尾数1和指数-3表示。
浮点小数必须加上2的负幂
0.1b = 0.5d
0.01b = 0.25d
0.001b = 0.125d
0.0001b = 0.0625d
0.00001b = 0.03125d
等等
在处理浮点运算时,通常使用误差增量而不是相等运算符。而不是
if(a==b) ...
你会使用
delta = 0.0001; // or some arbitrarily small amount
if(a - b > -delta && a - b < delta) ...
硬件设计师的视角
我认为,既然我设计并构建了浮点硬件,我就应该添加一个硬件设计师的视角。了解错误的来源可能有助于了解软件中发生的情况,最终,我希望这有助于解释为什么浮点错误会发生并似乎会随着时间累积的原因。
1.概述
从工程角度来看,大多数浮点运算都会有一些误差,因为进行浮点运算的硬件只需要在最后一个位置的误差小于一个单位的一半。因此,许多硬件将停止在一个精度上,该精度只需要在单个操作的最后位置产生小于一个单位的一半的误差,这在浮点除法中尤其有问题。什么构成一个操作取决于该单元需要多少个操作数。大多数情况下,它是两个,但有些单位需要3个或更多操作数。因此,不能保证重复操作会导致期望的错误,因为错误会随着时间的推移而增加。
2.标准
大多数处理器遵循IEEE-754标准,但有些处理器使用非规范化或不同的标准例如,IEEE-754中存在一种非规范化模式,该模式允许以精度为代价表示非常小的浮点数。然而,下面将介绍IEEE-754的标准化模式,这是典型的操作模式。
在IEEE-754标准中,硬件设计者可以使用误差/ε的任何值,只要它在最后一个位置小于一个单位的一半,并且一次操作的结果只需要在最后一位小于一个单元的一半。这解释了为什么当重复操作时,错误会增加。对于IEEE-754双精度,这是第54位,因为53位用于表示浮点数的数字部分(标准化),也称为尾数(例如5.3e5中的5.3)。下一节将更详细地介绍各种浮点操作的硬件错误原因。
3.除法舍入误差的原因
浮点除法误差的主要原因是用于计算商的除法算法。大多数计算机系统使用逆函数的乘法来计算除法,主要是Z=X/Y,Z=X*(1/Y)。迭代地计算除法,即每个周期计算商的一些比特,直到达到所需的精度,对于IEEE-754来说,这是最后一位误差小于一个单位的任何值。Y(1/Y)的倒数表在慢除法中被称为商选择表(QST),商选择表的位大小通常是基数的宽度,或每次迭代中计算的商的位数,加上几个保护位。对于IEEE-754标准,双精度(64位),它将是除法器基数的大小,加上几个保护位k,其中k>=2。因此,例如,一次计算2位商(基数4)的除法器的典型商选择表将是2+2=4位(加上几个可选位)。
3.1除法舍入误差:倒数近似
商选择表中的倒数取决于除法:慢除法如SRT除法,或快除法如Goldschmidt除法;根据除法算法修改每个条目,以尝试产生最小的可能误差。然而,在任何情况下,所有的倒数都是实际倒数的近似值,并引入了一些误差因素。慢除法和快除法都迭代地计算商,即每一步计算商的一些位数,然后从被除数中减去结果,除法器重复这些步骤,直到误差小于最后一个单位的一半。慢除法计算每一步的商的固定位数,通常构建成本较低,而快除法计算每步的位数可变,构建成本通常较高。除法中最重要的部分是,它们大多依赖于通过倒数的近似值进行重复乘法,因此容易出错。
4.其他操作中的舍入错误:截断
所有操作中舍入误差的另一个原因是IEEE-754允许的最终答案的不同截断模式。有截断、向零舍入、向最接近(默认)舍入、向下舍入和向上舍入。所有方法都会在单个操作的最后位置引入小于一个单位的误差元素。随着时间的推移和重复操作,截断也会累积地增加结果误差。这种截断误差在涉及某种形式的重复乘法的求幂运算中尤其有问题。
5.重复操作
由于执行浮点计算的硬件只需要在单个操作的最后一个位置产生误差小于一个单位的一半的结果,因此如果不注意,误差将随着重复操作而增加。这就是为什么在需要有界误差的计算中,数学家使用诸如在IEEE-754的最后一位使用舍入到最接近的偶数位的方法,因为随着时间的推移,误差更可能相互抵消,而区间算术结合了IEEE754舍入模式的变化来预测舍入误差,并对其进行校正。由于与其他舍入模式相比,其相对误差较低,因此舍入到最近的偶数位(最后一位)是IEEE-754的默认舍入模式。
请注意,默认舍入模式(舍入到最后一位最接近的偶数位)保证一次操作的误差小于最后一位单位的一半。仅使用截断、向上舍入和向下舍入可能会导致误差大于最后一位一个单位的一半,但小于最后一位的一个单位,因此不建议使用这些模式,除非它们用于区间算术。
6.总结
简而言之,浮点运算中出现错误的根本原因是硬件中的截断和除法中倒数的截断。由于IEEE-754标准只要求单个操作的误差小于最后一位一个单位的一半,因此重复操作中的浮点误差将相加,除非得到纠正。
浮点舍入错误。从每个计算机科学家应该知道的浮点运算:
将无限多的实数压缩成有限位数需要近似表示。虽然有无限多的整数,但在大多数程序中,整数计算的结果可以存储在32位中。相反,给定任何固定位数,大多数使用实数的计算将产生无法使用那么多位数精确表示的量。因此,浮点计算的结果必须经常舍入,以适应其有限表示。这种舍入误差是浮点计算的特征。