避免 JavaScript 奇怪的十进制计算问题
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5037839/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Avoiding problems with JavaScript's weird decimal calculations
提问by Lèse majesté
I just read on MDNthat one of the quirks of JS's handling of numbers due to everything being "double-precision 64-bit format IEEE 754 values"is that when you do something like .2 + .1
you get 0.30000000000000004
(that's what the article reads, but I get 0.29999999999999993
in Firefox). Therefore:
我刚刚在 MDN上读到,由于一切都是“双精度 64 位格式 IEEE 754 值”,JS 处理数字的怪癖之一是当你做类似.2 + .1
你得到的事情时0.30000000000000004
(这就是文章读的内容,但我明白了0.29999999999999993
在 Firefox 中)。所以:
(.2 + .1) * 10 == 3
evaluates to false
.
评估为false
.
This seems like it would be very problematic. So what can be done to avoid bugs due to the imprecise decimal calculations in JS?
这看起来会很成问题。那么如何避免由于 JS 中的十进制计算不精确而导致的错误呢?
I've noticed that if you do 1.2 + 1.1
you get the right answer. So should you just avoid any kind of math that involves values less than 1? Because that seems very impractical. Are there any other dangers to doing math in JS?
我注意到,如果你这样做,1.2 + 1.1
你会得到正确的答案。那么您是否应该避免任何涉及小于 1 的值的数学?因为这看起来很不切实际。在 JS 中做数学还有其他危险吗?
Edit:
I understand that many decimal fractions can't be stored as binary, but the way most other languages I've encountered appear to deal with the error (like JS handles numbers greater than 1) seems more intuitive, so I'm not used to this, which is why I want to see how other programmers deal with these calculations.
编辑:
我知道许多小数不能存储为二进制,但我遇到的大多数其他语言似乎处理错误的方式(如 JS 处理大于 1 的数字)似乎更直观,所以我不是习惯了这个,这就是为什么我想看看其他程序员如何处理这些计算。
采纳答案by Adriaan Stander
In situations like these you would tipically rather make use of an epsilon estimation.
在这种情况下,您通常宁愿使用 epsilon 估计。
Something like (pseudo code)
类似(伪代码)
if (abs(((.2 + .1) * 10) - 3) > epsilon)
where epsilon is something like 0.00000001, or whatever precision you require.
其中 epsilon 类似于 0.00000001,或者您需要的任何精度。
Have a quick read at Comparing floating point numbers
快速阅读比较浮点数
回答by rsp
1.2 + 1.1 may be ok but 0.2 + 0.1 may not be ok.
1.2 + 1.1 可能没问题,但 0.2 + 0.1 可能不行。
This is a problem in virtually every language that is in use today. The problem is that 1/10 cannot be accurately represented as a binary fraction just like 1/3 cannot be represented as a decimal fraction.
这在当今使用的几乎所有语言中都是一个问题。问题是 1/10 不能准确表示为二进制小数,就像 1/3 不能表示为十进制小数一样。
The workarounds include rounding to only the number of decimal places that you need and either work with strings, which are accurate:
解决方法包括四舍五入到您需要的小数位数,或者使用字符串,这是准确的:
(0.2 + 0.1).toFixed(4) === 0.3.toFixed(4) // true
or you can convert it to numbers after that:
或者您可以在此之后将其转换为数字:
+(0.2 + 0.1).toFixed(4) === 0.3 // true
or using Math.round:
或使用 Math.round:
Math.round(0.2 * X + 0.1 * X) / X === 0.3 // true
where X
is some power of 10 e.g. 100 or 10000 - depending on what precision you need.
哪里X
是 10 的某个幂,例如 100 或 10000 - 取决于您需要的精度。
Or you can use cents instead of dollars when counting money:
或者你可以在数钱时使用美分而不是美元:
cents = 1499; // .99
That way you only work with integers and you don't have to worry about decimal and binary fractions at all.
这样你就只能处理整数,而根本不必担心十进制和二进制分数。
2017 Update
2017年更新
The situation of representing numbers in JavaScript may be a little bit more complicated than it used to. It used to be the casethat we had only one numeric type in JavaScript:
在 JavaScript 中表示数字的情况可能比以前复杂一些。它曾经是的情况下,我们在JavaScript中只有一个数值类型:
- 64-bit floating point (the IEEE 754 double precision floating-point number- see: ECMA-262 Edition 5.1, Section 8.5and ECMA-262 Edition 6.0, Section 6.1.6)
- 64 位浮点(IEEE 754 双精度浮点数- 请参阅:ECMA-262 5.1 版,8.5 节和ECMA-262 6.0 版,6.1.6 节)
This is no longer the case- not only there are currently more numerical types in JavaScript today, more are on the way, including a proposal to add arbitrary-precision integers to ECMAScript, and hopefully, arbitrary-precision decimals will follow - see this answer for details:
情况不再如此- 不仅目前 JavaScript 中目前有更多的数字类型,而且还有更多的数字类型,包括向 ECMAScript 添加任意精度整数的提议,并且希望任意精度小数会随之而来 - 请参阅此答案详情:
See also
也可以看看
Another relevant answer with some examples of how to handle the calculations:
另一个相关答案,其中包含一些有关如何处理计算的示例:
回答by Bernardo Martinez
(Math.floor(( 0.1+0.2 )*1000))/1000
This will reduce the precision of float numbers but solves the problem if you are not working with very small values. For example:
这将降低浮点数的精度,但如果您不使用非常小的值,则可以解决问题。例如:
.1+.2 =
0.30000000000000004
after the proposed operation you will get 0.3 But any value between:
在建议的操作之后,您将获得 0.3 但介于以下之间的任何值:
0.30000000000000000
0.30000000000000999
will be also considered 0.3
也将被视为 0.3
回答by Chris Williams
Understanding rounding errors in floating point arithmetic is not for the faint-hearted! Basically, calculations are done as though there were infinity bits of precision available. The result is then rounded according to rules laid down in the relevant IEEE specifications.
了解浮点运算中的舍入错误不适合胆小的人!基本上,计算就像有无限位可用的精度一样。然后根据相关 IEEE 规范中规定的规则对结果进行四舍五入。
This rounding can throw up some funky answers:
这种四舍五入可能会抛出一些时髦的答案:
Math.floor(Math.log(1000000000) / Math.LN10) == 8 // true
This an an entire order of magnitudeout. That's some rounding error!
这是一个完整的数量级。这是一些舍入错误!
For any floating point architecture, there is a number that represents the smallest interval between distinguishable numbers. It is called EPSILON.
对于任何浮点架构,都有一个数字表示可区分数字之间的最小间隔。它被称为EPSILON。
It will be a part of the EcmaScript standard in the near future. In the meantime, you can calculate it as follows:
在不久的将来,它将成为 EcmaScript 标准的一部分。同时,您可以按如下方式计算:
function epsilon() {
if ("EPSILON" in Number) {
return Number.EPSILON;
}
var eps = 1.0;
// Halve epsilon until we can no longer distinguish
// 1 + (eps / 2) from 1
do {
eps /= 2.0;
}
while (1.0 + (eps / 2.0) != 1.0);
return eps;
}
You can then use it, something like this:
然后你可以使用它,就像这样:
function numericallyEquivalent(n, m) {
var delta = Math.abs(n - m);
return (delta < epsilon());
}
Or, since rounding errors can accumulate alarmingly, you may want to use delta / 2
or delta * delta
rather than delta
.
或者,由于舍入误差可能会惊人地累积,您可能希望使用delta / 2
或delta * delta
而不是delta
。
回答by Eric Seastrand
There are libraries that seek to solve this problembut if you don't want to include one of those (or can't for some reason, like working inside a GTM variable) then you can use this little function I wrote:
有一些库试图解决这个问题,但如果你不想包含其中一个(或者由于某种原因不能包含,比如在GTM 变量中工作),那么你可以使用我写的这个小函数:
Usage:
用法:
var a = 194.1193;
var b = 159;
a - b; // returns 35.11930000000001
doDecimalSafeMath(a, '-', b); // returns 35.1193
Here's the function:
这是函数:
function doDecimalSafeMath(a, operation, b, precision) {
function decimalLength(numStr) {
var pieces = numStr.toString().split(".");
if(!pieces[1]) return 0;
return pieces[1].length;
}
// Figure out what we need to multiply by to make everything a whole number
precision = precision || Math.pow(10, Math.max(decimalLength(a), decimalLength(b)));
a = a*precision;
b = b*precision;
// Figure out which operation to perform.
var operator;
switch(operation.toLowerCase()) {
case '-':
operator = function(a,b) { return a - b; }
break;
case '+':
operator = function(a,b) { return a + b; }
break;
case '*':
case 'x':
precision = precision*precision;
operator = function(a,b) { return a * b; }
break;
case '÷':
case '/':
precision = 1;
operator = function(a,b) { return a / b; }
break;
// Let us pass in a function to perform other operations.
default:
operator = operation;
}
var result = operator(a,b);
// Remove our multiplier to put the decimal back.
return result/precision;
}
回答by Yochai Timmer
You need a bit of error control.
你需要一点错误控制。
Make a little double comparing method:
做一个小小的双重比较方法:
int CompareDouble(Double a,Double b) {
Double eplsilon = 0.00000001; //maximum error allowed
if ((a < b + epsilon) && (a > b - epsilon)) {
return 0;
}
else if (a < b + epsilon)
return -1;
}
else return 1;
}