如何处理JavaScript中的浮点数精度?[复制]

How to deal with floating point number precision in JavaScript? [duplicate]

提问人:Juri 提问时间:9/22/2009 最后编辑:Derek PollardJuri 更新时间:11/23/2023 访问量:693059

问:

这个问题在这里已经有答案了:
浮点数学坏了吗? (34 个答案)
9个月前关闭。

社群在 25 天前审查了是否重新打开这个问题,并关闭了这个问题:

原始关闭原因未解决

我有以下虚拟测试脚本:

function test() {
  var x = 0.1 * 0.2;
  document.write(x);
}
test();

这将打印结果,而它应该只是打印(如果您使用计算器)。据我了解,这是由于浮点乘法精度的误差造成的。0.0200000000000000040.02

有没有人有一个好的解决方案,以便在这种情况下我得到正确的结果?我知道有像或四舍五入这样的功能是另一种可能性,但我想真正打印整数而不进行任何切割和四舍五入。只是想知道你们中是否有人有一些漂亮、优雅的解决方案。0.02toFixed

当然,否则我会四舍五入到 10 位数左右。

JavaScript 浮点

评论

155赞 Aaron Digulla 9/22/2009
实际上,错误是因为没有办法映射到有限的二进制浮点数。0.1
18赞 Nate Zaugg 1/11/2011
大多数分数无法精确转换为小数点。一个很好的解释是:docs.python.org/release/2.5.1/tut/node16.html
4赞 Salman A 11/18/2012
@AaronDigulla:是。(new Number(0.1)).valueOf()0.1
63赞 Aaron Digulla 11/19/2012
@SalmanA:你的 JavaScript 运行时向你隐瞒了这个问题并不意味着我错了。
12赞 Fabien Haddadi 11/14/2016
不同意 Aaron 的观点,有一些方法可以完美地、完全地以二进制形式编写 0.1。但 IEEE 754 并不一定对此进行定义。想象一下,您将一方面用二进制编码整数部分,另一方面将小数部分编码为二进制,最多 n 个小数部分,就像普通整数> 0 一样,最后是小数点的位置。好吧,您将完美地表示 0.1,没有错误。顺便说一句,由于 JS 在内部使用有限数量的小数,因此他们开发人员不妨对胆量进行编码,以免在最后的小数点上犯错误。

答:

4赞 Marius 9/22/2009 #1

看看定点算术。如果您要操作的数字范围很小(例如,货币),它可能会解决您的问题。我会将其四舍五入到几个十进制值,这是最简单的解决方案。

评论

7赞 Michael Borgwardt 8/9/2010
问题不在于浮点与定点,而在于二进制与十进制。
1赞 Himadri 9/22/2009 #2

var x = 0.1*0.2;
 x =Math.round(x*Math.pow(10,2))/Math.pow(10,2);

评论

4赞 Juri 9/22/2009
嗯。。。但请注意,这始终四舍五入到小数点后 2 位。这当然是一种选择,但是计算 0.55*0.55 呢(因为我事先不知道确切的数字。这将给出 0.3 而不是 0.3025。当然,我可以使用.四舍五入始终是一种选择,但我只是想知道是否有更好的解决方案Math.round(x*Math.pow(10,4))/Math.pow(10,4);
0赞 Žilvinas 3/15/2019
10.2 始终返回 10.19 jsbin.com/tozogiwide/edit?html,js,console,output
8赞 Peter 9/22/2009 #3

不优雅,但可以完成工作(删除尾随零)

var num = 0.1*0.2;
alert(parseFloat(num.toFixed(10))); // shows 0.02

评论

8赞 Sam Hasler 10/25/2010
toFixed 并不总是有效:stackoverflow.com/questions/661562/...
4赞 MSN 8/6/2010 #4

不能使用二进制浮点类型(这是 ECMAScript 用来表示浮点值的类型)精确表示大多数十进制分数。因此,除非您使用任意精度算术类型或基于十进制的浮点类型,否则没有优雅的解决方案。例如,Windows 附带的计算器应用现在使用任意精度算术来解决此问题

15赞 ZXX 8/6/2010 #5

你只需要决定你真正想要多少个十进制数字 - 不能吃蛋糕,也吃它:-)

每次进一步的操作都会累积数值误差,如果你不及早切断它,它只会增长。呈现结果看起来干净的数值库只是在每一步中切断最后 2 位数字,出于同样的原因,数值协处理器也具有“正常”和“完整”长度。Cuf-offs 对于处理器来说很便宜,但在脚本中对您来说非常昂贵(乘法和除法以及使用 pov(...))。好的数学库会提供 floor(x,n) 来为你做截止。

所以至少你应该用 pov(10,n) 创建全局变量/常量 - 这意味着你决定了你需要的精度 :-)然后做:

Math.floor(x*PREC_LIM)/PREC_LIM  // floor - you are cutting off, not rounding

你也可以继续做数学运算,只在最后截断 - 假设你只显示而不是对结果做 if-s。如果可以这样做,那么 .toFixed(...) 可能会更有效。

如果你正在做if-s/comparisons,并且不想削减,那么你还需要一个小常数,通常称为eps,它比最大预期误差高一个小数位。假设您的截止值是小数点后两位 - 那么您的 eps 在倒数第三位(第 3 位最低有效性)的第 3 位有 1,您可以使用它来比较结果是否在预期的 eps 范围内(0.02 -eps < 0.1*0.2 < 0.02 +eps)。

评论

0赞 cmroanirgo 12/4/2012
你也可以加 0.5 来做一个穷人的四舍五入:Math.floor(x*PREC_LIM + 0.5)/PREC_LIM
0赞 MikeM 1/21/2013
但请注意,例如 是。所以也许使用例如Math.floor(-2.1)-3Math[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
0赞 Quinn Comendant 11/19/2015
为什么代替 ?floorround
31赞 Douglas 8/6/2010 #6

您正在寻找 JavaScript 的实现,以便您可以以您期望的格式写出带有小错误的浮点数(因为它们以二进制格式存储)。sprintf

试javascript-sprintf,你可以这样称呼它:

var yourString = sprintf("%.2f", yourNumber);

将您的数字打印为带有两位小数的浮点数。

如果您不想仅仅为了将浮点四舍五入到给定精度而包含更多文件,也可以使用 Number.toFixed() 进行显示。

评论

4赞 Long Ouyang 8/7/2010
我认为这是最干净的解决方案。除非你真的需要结果是 0.02,否则小误差可以忽略不计。听起来重要的是你的数字显示得很好,而不是你有任意的精度。
2赞 Not Available 8/11/2010
对于显示,这确实是最好的选择,对于复杂的计算,请查看 Borgwardt 的答案。
4赞 Robert 12/22/2010
但话又说回来,这将返回与 yourNumber.toFixed(2) 完全相同的字符串。
3赞 skalee 8/7/2010 #7

你是对的,原因是浮点数的精度有限。将您的有理数存储为两个整数的除法,在大多数情况下,您将能够在没有任何精度损失的情况下存储数字。在打印时,您可能希望将结果显示为分数。有了我提出的代表性,它就变得微不足道了。

当然,这对无理数没有多大帮助。但是,您可能希望以最少问题的方式优化计算(例如,检测 .sqrt(3)^2)

评论

0赞 detly 8/10/2010
你是对的,原因是浮点数的精度有限——实际上,OP 将其归结为不精确的浮点运算,这是错误的<pedant></pedant>
25赞 shawndumas 8/8/2010 #8
var times = function (a, b) {
    return Math.round((a * b) * 100)/100;
};

---或---

var fpFix = function (n) {
    return Math.round(n * 100)/100;
};

fpFix(0.1*0.2); // -> 0.02

---也---

var fpArithmetic = function (op, x, y) {
    var n = {
            '*': x * y,
            '-': x - y,
            '+': x + y,
            '/': x / y
        }[op];        

    return Math.round(n * 100)/100;
};

---如---

fpArithmetic('*', 0.1, 0.2);
// 0.02

fpArithmetic('+', 0.1, 0.2);
// 0.3

fpArithmetic('-', 0.1, 0.2);
// -0.1

fpArithmetic('/', 0.2, 0.1);
// 2

评论

6赞 Gertjan 8/9/2010
我认为这会带来同样的问题。你返回一个浮点,所以很有可能返回值也会“不正确”。
3赞 Jonatas Walker 10/13/2016
非常聪明和有用,+1。
11赞 Keith 8/9/2010 #9

在不同语言、处理器和操作系统的浮点实现中,你得到的结果是正确且相当一致的 - 唯一改变的是浮点数实际上是双倍(或更高)时的不准确性程度。

二进制浮点数中的 0.1 就像十进制的 1/3(即 0.3333333333333......永远),只是没有准确的方法来处理它。

如果你在处理浮点数,总是会有一些小的舍入误差,所以你也必须始终将显示的结果舍入到合理的值。作为回报,您将获得非常非常快速和强大的算术,因为所有计算都在处理器的本机二进制中。

大多数时候,解决方案不是切换到定点算术,主要是因为它要慢得多,而且 99% 的时间你只是不需要精度。如果你正在处理确实需要这种精确度的东西(例如金融交易),那么 Javascript 可能不是最好的工具(因为你想强制执行定点类型,静态语言可能更好)。

您正在寻找优雅的解决方案,那么恐怕就是这样:浮点数速度很快,但舍入误差很小 - 在显示结果时总是舍入到合理的值。

8赞 Gertjan 8/9/2010 #10

为了避免这种情况,你应该使用整数值而不是浮点数。因此,当您想让 2 个位置精确使用值 * 100 时,对于 3 个位置,请使用 1000。显示时,使用格式化程序放入分隔符。

许多系统省略了以这种方式处理小数。这就是为什么许多系统使用美分(作为整数)而不是美元/欧元(作为浮点)的原因。

601赞 Michael Borgwardt 8/9/2010 #11

浮点指南

我能做些什么来避免这个问题?

这取决于什么样的 你正在做的计算。

  • 如果你真的需要你的结果加起来,尤其是当你 使用金钱:使用特殊小数 数据类型。
  • 如果您只是不想看到所有这些额外的小数位:只需 将结果格式四舍五入为固定 小数位数 显示它。
  • 如果没有可用的十进制数据类型,另一种方法是工作 使用整数,例如do money 完全以美分计算。但 这是更多的工作,并且有一些 缺点。

请注意,仅当您确实需要特定的精确十进制行为时,第一点才适用。大多数人不需要它,他们只是对他们的程序无法正确处理 1/10 这样的数字而感到恼火,而没有意识到如果它发生在 1/3 时,他们甚至不会在同样的错误中眨眼。

如果第一点真的适用于你,请使用 BigDecimal for JavaScriptDecimalJS,这实际上解决了问题,而不是提供不完美的解决方法。

评论

14赞 Jacksonkr 12/1/2011
我注意到你对BigDecimal的死链接,在寻找镜子时,我发现了一个名为BigNumber的替代方案:jsfromhell.com/classes/bignumber
5赞 Michael Borgwardt 7/25/2012
@bass-t:是的,但是浮点数可以精确地表示长度不超过有效数的整数,并且根据 ECMA 标准,它是一个 64 位浮点数。因此,它可以精确地表示最多 2^52 的整数
8赞 Michael Borgwardt 12/23/2014
@Karl:十进制分数 1/10 不能表示为以 2 为基数的有限二进制分数,这就是 Javascript 数字。所以这实际上是完全相同的问题。
29赞 mlathe 7/11/2015
我今天了解到,即使是整数在 javascript 中也存在精度问题。考虑到实际打印(即减少一个!console.log(9332654729891549)9332654729891548
24赞 GitaarLAB 3/3/2016
@mlathe: Doh.....在 = 和 = 之间,可表示的数字正好是整数。对于下一个范围,从 到 ,一切都乘以 2,因此可表示的数字是偶数,依此类推。 相反,对于前一个范围 从 到 ,间距为 ,以此类推。 这是由于简单地增加|减少 64 位浮点值中的基|基数 2|二进制指数(这反过来又解释了 和 之间很少记录的 for 值的“意外”行为)。 ;P2⁵²4,503,599,627,370,4962⁵³9,007,199,254,740,9922⁵³2⁵⁴2⁵¹2⁵²0.5toPrecision()01
57赞 Nate Zaugg 8/11/2010 #12

你只执行乘法吗?如果是这样,那么你可以利用一个关于十进制算术的巧妙秘密来发挥你的优势。就是这样.也就是说,如果我们有,那么我们知道会有 5 位小数,因为有 3 位小数,有 2 位。因此,如果 JavaScript 给我们一个这样的数字,我们可以安全地四舍五入到小数点后第 5 位,而不必担心失去精度。NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals0.123 * 0.120.1230.120.014760000002

评论

11赞 line-o 2/13/2013
...以及如何获得小数位数的确切数量。
8赞 Nate Zaugg 9/19/2014
0.5 * 0.2 = 0.10;您仍然可以截断小数点后 2 位(或更少)。但是,除了这个定律之外,永远不会有任何数学意义的数字。
4赞 Griffin 4/14/2015
你有这方面的引文吗?另请注意,除法并非如此。
2赞 GitaarLAB 3/3/2016
格里芬:文(更重要的是,一个易于理解的解释):mathsisfun.com/multiplying-decimals.htmlmath.com/school/subject1/lessons/S1U1L5DP.html 本质上:“因为当你(我的加法:在纸上手动)在没有小数点的情况下乘法时,你实际上是在将小数点向右移动以使其不碍事(我的加法:对于每个数字)”所以,#x 的移位加上 # y 的移位。
3赞 Lostfields 5/12/2017
@NateZaugg你不能截断溢出的小数点,你必须对金额进行四舍五入,因为 2090.5 * 8.61 是 17999.205,但在浮点数中是 17999.2049999999998
193赞 linux_mike 9/5/2010 #13

我喜欢佩德罗·拉达里亚(Pedro Ladaria)的解决方案,并使用类似的东西。

function strip(number) {
    return (parseFloat(number).toPrecision(12));
}

与 Pedros 解决方案不同,这将向上舍入 0.999...,重复,并且精确到最低有效数字的正/减 1。

注意:在处理 32 位或 64 位浮点数时,应使用 toPrecision(7) 和 toPrecision(15) 以获得最佳结果。有关原因的信息,请参阅此问题

评论

34赞 qwertymk 12/27/2015
你选择12个有什么原因吗?
33赞 SStanley 3/14/2016
toPrecision返回字符串而不是数字。这可能并不总是可取的。
12赞 Peter 5/27/2016
解析浮点数(1.005).toPrecision(3) => 1.00
5赞 Peter 7/9/2016
@user2428118,我知道,我的意思是显示舍入误差,结果是 1.00 而不是 1.01
20赞 ᴍᴇʜᴏᴠ 11/15/2018
@user2428118所说的可能不够明显:= 50 而不是 49.95,因为 toPrecision 计算整数,而不仅仅是小数。然后你可以使用 ,但如果你的结果是 >100,那么你又不走运了,因为它会允许前三个数字和一个小数点,这样就会移动点,并使其或多或少无法使用。我最终改用了(9.99*5).toPrecision(2)toPrecision(4)toFixed(2)
11赞 Tom 3/2/2012 #14

phpjs.org 的 round() 函数效果很好: http://phpjs.org/functions/round

num = .01 + .06;  // yields 0.0699999999999
rnum = round(num,12); // yields 0.07

评论

2赞 Mark A. Durham 6/20/2019
@jrg 按照惯例,以“5”结尾的数字四舍五入到最接近的偶数(因为总是向上或向下舍入会给结果带来偏差)。因此,四舍五入到小数点后两位的 4.725 确实应该是 4.72。
-1赞 TecHunter 9/15/2012 #15

解决方法:仅与 10E^x 相乘不适用于 1.1。

function sum(a,b){
    var tabA = (a + "").split(".");
    var tabB = (b + "").split(".");
    decA = tabA.length>1?tabA[1].length:0;
    decB = tabB.length>1?tabB[1].length:0;
    a = (tabA[0]+tabA[1])*1.0;
    b = (tabB[0]+tabB[1])*1.0;
    var diff = decA-decB;
    if(diff >0){
        //a has more decimals than b
        b=b*Math.pow(10,diff);
        return (a+b)/Math.pow(10,decA);
    }else if (diff<0){
        //a has more decimals than b
        a=a*Math.pow(10,-diff);
                return (a+b)/Math.pow(10,decB);
    }else{
        return (a+b)/Math.pow(10,decA);
    }       
}
-9赞 user2008398 6/27/2013 #16

您可以使用正则表达式来检查数字是否以一长串 0 结尾,后跟一小段余数:

// using max number of 0s = 8, maximum remainder = 4 digits
x = 0.1048000000000051
parseFloat(x.toString().replace(/(\.[\d]+[1-9])0{8,}[1-9]{0,4}/, '$1'), 10)
// = 0.1048

评论

6赞 benesch 3/15/2014
哦,上帝,请不要。任意接近某个阈值的数字的十进制表示集不是一种常规语言。
3赞 jmc 4/9/2014
对不起,但这太荒谬了。至少尝试了解为什么会出现错误并正确解决它。我的第一个反对票......4 年后,我想它必须在某个时候发生。
16赞 Qqwy 4/20/2014
你有问题。您决定使用正则表达式来修复它。现在你有两个问题。
0赞 Andrew 5/3/2020
哈哈哈哈哈哈哈哈哈
95赞 SheetJS 9/20/2013 #17

对于数学倾向:http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

推荐的方法是使用校正因子(乘以合适的 10 次幂,以便算术在整数之间进行)。例如,在 的情况下,校正因子为 ,并且您正在执行以下计算:0.1 * 0.210

> var x = 0.1
> var y = 0.2
> var cf = 10
> x * y
0.020000000000000004
> (x * cf) * (y * cf) / (cf * cf)
0.02

一个(非常快速的)解决方案如下所示:

var _cf = (function() {
  function _shift(x) {
    var parts = x.toString().split('.');
    return (parts.length < 2) ? 1 : Math.pow(10, parts[1].length);
  }
  return function() { 
    return Array.prototype.reduce.call(arguments, function (prev, next) { return prev === undefined || next === undefined ? undefined : Math.max(prev, _shift (next)); }, -Infinity);
  };
})();

Math.a = function () {
  var f = _cf.apply(null, arguments); if(f === undefined) return undefined;
  function cb(x, y, i, o) { return x + f * y; }
  return Array.prototype.reduce.call(arguments, cb, 0) / f;
};

Math.s = function (l,r) { var f = _cf(l,r); return (l * f - r * f) / f; };

Math.m = function () {
  var f = _cf.apply(null, arguments);
  function cb(x, y, i, o) { return (x*f) * (y*f) / (f * f); }
  return Array.prototype.reduce.call(arguments, cb, 1);
};

Math.d = function (l,r) { var f = _cf(l,r); return (l * f) / (r * f); };

在这种情况下:

> Math.m(0.1, 0.2)
0.02

我绝对推荐使用像 SinfulJS 这样的经过测试的库

评论

2赞 nicolallias 4/16/2014
我喜欢这个优雅的解决方法,但似乎并不完美: jsfiddle.net/Dm6F5/1 Math.a(76.65, 38.45) 返回 115.100000000000002
4赞 Shiva Komuravelly 7/15/2014
Math.m(10,2332226616) 给我“-19627406800”,这是一个负值......我希望必须有一个上限 - 可能是导致这个问题的原因。请建议
2赞 MrYellow 3/12/2015
这一切看起来都很棒,但似乎在某个地方有一两个错误。
14赞 Cozzbie 12/11/2015
他说,非常快速的解决方案......坏了,修复,没有人说过。
1赞 strix25 9/20/2022
这对性能非常不利
1赞 Antonio Max 10/4/2013 #18
function round_up( value, precision ) { 
    var pow = Math.pow ( 10, precision ); 
    return ( Math.ceil ( pow * value ) + Math.ceil ( pow * value - Math.ceil ( pow * value ) ) ) / pow; 
}

round_up(341.536, 2); // 341.54

评论

1赞 jrg 6/18/2014
不幸的是,round_up(4.15,2) => 4.16。
1赞 Júlio Paulillo 6/20/2014 #19

使用以下函数输出:

var toFixedCurrency = function(num){
    var num = (num).toString();
    var one = new RegExp(/\.\d{1}$/).test(num);
    var two = new RegExp(/\.\d{2,}/).test(num);
    var result = null;

    if(one){ result = num.replace(/\.(\d{1})$/, '.$10');
    } else if(two){ result = num.replace(/\.(\d{2})\d*/, '.$1');
    } else { result = num*100; }

    return result;
}

function test(){
    var x = 0.1 * 0.2;
    document.write(toFixedCurrency(x));
}

test();

注意输出。toFixedCurrency(x)

3赞 Harish.bazee 10/8/2014 #20

使用编号(1.234443).toFixed(2);它将打印 1.23

function test(){
    var x = 0.1 * 0.2;
    document.write(Number(x).toFixed(2));
}
test();
44赞 Ronnie Overby 12/2/2014 #21

我发现BigNumber.js满足了我的需求。

用于任意精度十进制和非十进制算术的 JavaScript 库。

它有很好的文档,作者非常勤奋地回应反馈。

同一作者还有 2 个其他类似的库:

Big.js

一个小型、快速的 JavaScript 库,用于任意精度的十进制算术。bignumber.js 的小妹妹。

十进制.js

JavaScript 的任意精度 Decimal 类型。

下面是一些使用 BigNumber 的代码:

$(function(){      
  var product = BigNumber(.1).times(.2);  
  $('#product').text(product);

  var sum = BigNumber(.1).plus(.2);  
  $('#sum').text(sum);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<!-- 1.4.1 is not the current version, but works for this example. -->
<script src="http://cdn.bootcss.com/bignumber.js/1.4.1/bignumber.min.js"></script>

.1 &times; .2 = <span id="product"></span><br>
.1 &plus; .2 = <span id="sum"></span><br>

评论

4赞 Anthony 7/12/2015
在我看来,使用库绝对是最佳选择。
1赞 vee 1/25/2020
从这个链接 github.com/MikeMcl/big.js/issues/45 bignumber.js -> 金融小数.js -> 科学大.js -> ???
3赞 mcgeo52 12/15/2014 #22

我在 mod 3 中遇到了舍入错误问题。有时,当我得到 0 时,我会得到 .000...01。只需测试 <= .01。但有时我会得到 2.999999999999998。

BigNumbers 解决了这个问题,但引入了另一个问题。当尝试将 8.5 加载到 BigNumbers 中时,它实际上是 8.4999......并且有超过 15 个有效数字。BigNumbers 无法接受。

溶液:

x = Math.round(x*100);
// I only need 2 decimal places, if I needed 3 I would use 1,000, etc.
x = x / 100;
xB = new BigNumber(x);
9赞 Олег Всильдеревьев 2/12/2015 #23

0.6 * 3 工作正常:

function dec( num )
{
    var p = 100;
    return Math.round( num * p ) / p;
}

评论

0赞 Paul Carlton 5/4/2016
这适用于类似的东西吗?8.22e-8 * 1.3
0赞 Zyo 6/30/2018
0.6 x 3 = 1.8,您给结果的代码 2...所以不好。
0赞 Andrew 5/3/2020
有趣。您可以在其中交换乘法和除法运算符,它也可以工作。
22赞 Softwareddy 3/4/2015 #24

您可以使用 and 如果您想绕过此问题进行小型操作:parseFloat()toFixed()

a = 0.1;
b = 0.2;

a + b = 0.30000000000000004;

c = parseFloat((a+b).toFixed(2));

c = 0.3;

a = 0.3;
b = 0.2;

a - b = 0.09999999999999998;

c = parseFloat((a-b).toFixed(2));

c = 0.1;

评论

0赞 Simon East 11/19/2023
也许这段代码可以使用一些注释,因为按原样遵循有点令人困惑。此外,仅当您确切知道当时需要多少个小数位时才有效。有时,您可能希望在不知道小数点后确切数量的情况下保留一定数量的有效数字。在这种情况下,请使用 代替 ..toFixed().toPrecision().toFixed()
3赞 Ashish Singhal 5/8/2015 #25

enter image description here

    You can use library https://github.com/MikeMcl/decimal.js/. 
    it will   help  lot to give proper solution. 
    javascript console output 95 *722228.630 /100 = 686117.1984999999
    decimal library implementation 
    var firstNumber = new Decimal(95);
    var secondNumber = new Decimal(722228.630);
    var thirdNumber = new Decimal(100);
    var partialOutput = firstNumber.times(secondNumber);
    console.log(partialOutput);
    var output = new Decimal(partialOutput).div(thirdNumber);
    alert(output.valueOf());
    console.log(output.valueOf())== 686117.1985
1赞 black_pottery_beauty 7/14/2016 #26

在添加两个浮点值时,它永远不会给出精确的值,因此我们需要将其固定为某个数字,这将有助于我们进行比较。

console.log((parseFloat(0.1) + parseFloat(0.2)).toFixed(1) == parseFloat(0.3).toFixed(1));
1赞 Jeeva 7/10/2017 #27

在不使用任何库或脚本的情况下:

var toAlgebraic = function(f1, f2) {
    let f1_base = Math.pow(10, f1.split('.')[1].length);
    let f2_base = Math.pow(10, f2.split('.')[1].length);
    f1 = parseInt(f1.replace('.', ''));
    f2 = parseInt(f2.replace('.', ''));

    let dif, base;
    if (f1_base > f2_base) {
        dif = f1_base / f2_base;
        base = f1_base;
        f2 = f2 * dif;
    } else {
        dif = f2_base / f1_base;
        base = f2_base;
        f1 = f1 * dif;
    }

    return (f1 * f2) / base;
};

console.log(0.1 * 0.2);
console.log(toAlgebraic("0.1", "0.2"));
7赞 Stefan Mondelaers 7/27/2017 #28

问题

浮点数不能精确存储所有十进制值。因此,当使用浮点格式时,输入值上总是会出现舍入错误。 当然,输入上的错误会导致输出上的错误。 对于离散函数或运算符,在函数或运算符离散的点附近,输出可能会有很大的差异。

浮点值的输入和输出

因此,在使用浮点变量时,您应该始终注意这一点。无论您希望从浮点计算中获得什么输出,在显示之前都应始终进行格式化/调节,并牢记这一点。
当仅使用连续函数和运算符时,通常可以四舍五入到所需的精度(不要截断)。用于将浮点数转换为字符串的标准格式设置功能通常会为您执行此操作。
由于舍入会增加一个误差,该误差可能导致总误差超过所需精度的一半,因此应根据输入的预期精度和输出的期望精度来校正输出。你应该

  • 将输入四舍五入到预期的精度,或确保无法以更高的精度输入任何值。
  • 在四舍五入/格式化输出之前,向输出添加一个小值,该值小于或等于所需精度的 1/4,并且大于输入和计算期间的舍入误差导致的最大预期误差。如果无法做到这一点,则所用数据类型的精度组合不足以为计算提供所需的输出精度。

这两件事通常不会完成,在大多数情况下,不做它们造成的差异太小,对大多数用户来说并不重要,但是我已经有一个项目,如果没有这些更正,用户就不会接受输出。

离散函数或运算符(如 modula)

当涉及离散运算符或函数时,可能需要进行额外的校正以确保输出符合预期。四舍五入并在四舍五入之前添加小的更正并不能解决问题。
在应用离散函数或运算符后,可能需要立即对中间计算结果进行特殊检查/校正。 对于特定情况(模运算符),请参阅我对问题的回答:为什么模运算符在 javascript 中返回小数?

最好避免出现问题

通过使用数据类型(整数或定点格式)进行此类计算,通常可以更有效地避免这些问题,这样可以存储预期的输入而不会出现舍入错误。 例如,切勿使用浮点值进行财务计算。

12赞 user10089632 9/16/2017 #29

请注意,对于常规用途,此行为可能是可以接受的。
在比较这些浮点值以确定适当的操作时,会出现问题。
随着 ES6 的出现,定义了一个新常量来确定可接受的误差范围:
因此,与其像这样进行比较,不如进行这样的比较
Number.EPSILON

0.1 + 0.2 === 0.3 // which returns false

您可以定义一个自定义比较函数,如下所示:

function epsEqu(x, y) {
    return Math.abs(x - y) < Number.EPSILON;
}
console.log(epsEqu(0.1+0.2, 0.3)); // true

来源 : http://2ality.com/2015/04/numbers-math-es6.html#numberepsilon

评论

0赞 Tom 1/18/2019
在我的情况下,Number.EPSILON 太小了,这导致例如0.9 !== 0.8999999761581421
0赞 Cem Kalyoncu 10/13/2022
Number.EPSILON 是无用的,因为该值随数字而变化。如果数字足够小,它就会起作用。在一个非常大的浮点数中,epsilon 甚至可以超过 1。
-4赞 blackmiaool 12/12/2017 #30

要处理任意浮点数:

function shorten(num) {
    num += 0.000000001;// to deal with "12.03999999997" form
    num += '';
    return num.replace(/(\.\d*?)0{5,}\d+$/, '$1') * 1;
}

console.log(1.2+1.9===1.3+1.8);// false
console.log(shorten(1.2+1.9)===shorten(1.3+1.8));// true
55赞 HelloWorldPeace 4/9/2018 #31

令人惊讶的是,这个功能还没有发布,尽管其他人也有类似的变体。它来自 MDN Web 文档。 它简洁明了,并允许不同的精度。Math.round()

function precisionRound(number, precision) {
    var factor = Math.pow(10, precision);
    return Math.round(number * factor) / factor;
}

console.log(precisionRound(1234.5678, 1));
// expected output: 1234.6

console.log(precisionRound(1234.5678, -1));
// expected output: 1230

var inp = document.querySelectorAll('input');
var btn = document.querySelector('button');

btn.onclick = function(){
  inp[2].value = precisionRound( parseFloat(inp[0].value) * parseFloat(inp[1].value) , 5 );
};

//MDN function
function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}
button{
display: block;
}
<input type='text' value='0.1'>
<input type='text' value='0.2'>
<button>Get Product</button>
<input type='text'>

更新: Aug/20/2019

刚刚注意到这个错误。我相信这是由于浮点精度误差造成的。Math.round()

precisionRound(1.005, 2) // produces 1, incorrect, should be 1.01

这些条件可以正常工作:

precisionRound(0.005, 2) // produces 0.01
precisionRound(1.0005, 3) // produces 1.001
precisionRound(1234.5, 0) // produces 1235
precisionRound(1234.5, -1) // produces 1230

修复:

function precisionRoundMod(number, precision) {
  var factor = Math.pow(10, precision);
  var n = precision < 0 ? number : 0.01 / factor + number;
  return Math.round( n * factor) / factor;
}

这只是在四舍五入小数时向右添加一位数字。 MDN 已经更新了页面,所以也许有人可以提供更好的解决方案。Math.round()

评论

0赞 Žilvinas 3/15/2019
错误的答案。10.2 将始终返回 10.19。jsbin.com/tozogiwide/edit?html,js,console,output
0赞 HelloWorldPeace 8/21/2019
@Žilvinas 您发布的 JSBin 链接没有使用上面列出的 MDN 功能。我认为你的评论是针对错误的人。
0赞 MrMesees 9/12/2020
Math.ceil 不会以同样的方式解释 0.01 吗(它使它成为一个整数,然后向下转换为浮点数 afaik)
0赞 mswieboda 6/9/2021
哇,谢谢,这非常适合我需要的东西,使用精度 about with 为我的用例提供了诀窍!12precisionRoundMod
0赞 Arthur Ronconi 6/15/2023
您可以将 替换为 .Math.pow(10, precision)10 ** precision
0赞 ShortFuse 2/7/2019 #32

如果您不想考虑每次都必须调用函数,则可以创建一个为您处理转换的类。

class Decimal {
  constructor(value = 0, scale = 4) {
    this.intervalValue = value;
    this.scale = scale;
  }

  get value() {
    return this.intervalValue;
  }

  set value(value) {
    this.intervalValue = Decimal.toDecimal(value, this.scale);
  }

  static toDecimal(val, scale) {
    const factor = 10 ** scale;
    return Math.round(val * factor) / factor;
  }
}

用法:

const d = new Decimal(0, 4);
d.value = 0.1 + 0.2;              // 0.3
d.value = 0.3 - 0.2;              // 0.1
d.value = 0.1 + 0.2 - 0.3;        // 0
d.value = 5.551115123125783e-17;  // 0
d.value = 1 / 9;                  // 0.1111

当然,在处理 Decimal 时,有一些注意事项:

d.value = 1/3 + 1/3 + 1/3;   // 1
d.value -= 1/3;              // 0.6667
d.value -= 1/3;              // 0.3334
d.value -= 1/3;              // 0.0001

理想情况下,您希望使用高比例(如 12),然后在需要呈现或将其存储在某个地方时将其转换为低比例。就个人而言,我确实尝试过创建一个 UInt8Array 并尝试创建一个精度值(很像 SQL Decimal 类型),但由于 Javascript 不允许你重载运算符,所以不能使用基本的数学运算符(、、、)并使用像 、 这样的函数来代替 、 会变得有点乏味。对于我的需求,这是不值得的。+-/*add()substract()mult()

但是,如果您确实需要这种级别的精度,并且愿意忍受使用函数进行数学运算,那么我推荐 decimal.js 库。

11赞 Erikas Pliauksta 8/7/2019 #33

decimal.jsbig.jsbignumber.js 可用于避免 Javascript 中的浮点操作问题:

0.1 * 0.2                                // 0.020000000000000004
x = new Decimal(0.1)
y = x.times(0.2)                          // '0.2'
x.times(0.2).equals(0.2)                  // true

Big.js:极简主义;易于使用;以小数位表示的精度;精度仅适用于除法。

bignumber.js:基数 2-64;配置选项;南;无限;以小数位表示的精度;精度仅适用于除法;基本前缀。

十进制.js:基数 2-64;配置选项;南;无限;非整数幂、exp、ln、log;以有效数字表示的精度;始终应用精度;随机数。

链接到详细比较

评论

0赞 Tchakabam 1/25/2022
“非整数幂”如何成为特定功能?似乎是原生的,即已经处理了?Math.pow**
6赞 Bernesto 9/12/2019 #34

优雅、可预测且可重用

让我们以一种优雅的方式处理这个问题,可重用。以下七行可让您通过附加到数字、公式或内置函数的末尾来访问任何数字所需的浮点精度。.decimalMath

// First extend the native Number object to handle precision. This populates
// the functionality to all math operations.

Object.defineProperty(Number.prototype, "decimal", {
  get: function decimal() {
    Number.precision = "precision" in Number ? Number.precision : 3;
    var f = Math.pow(10, Number.precision);
    return Math.round( this * f ) / f;
  }
});


// Now lets see how it works by adjusting our global precision level and
// checking our results.

console.log("'1/3 + 1/3 + 1/3 = 1' Right?");
console.log((0.3333 + 0.3333 + 0.3333).decimal == 1); // true

console.log(0.3333.decimal); // 0.333 - A raw 4 digit decimal, trimmed to 3...

Number.precision = 3;
console.log("Precision: 3");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0.001

Number.precision = 2;
console.log("Precision: 2");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 1;
console.log("Precision: 1");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 0;
console.log("Precision: 0");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

评论

6赞 Bernesto 10/1/2019
如果您选择投反对票,至少要提供一个理由。
5赞 BobRodes 3/13/2021
我没有投反对票,但是虽然这很优雅且可重用,但 JavaScript 原始类型对象的猴子补丁不太可能是可预测的。其中一些担忧似乎适用。
0赞 trincot 4/15/2021
尝试:((0.1*3)*1e14).decimal
0赞 Bernesto 4/29/2021
@BobRodes 我完全同意这是一个猴子补丁,由于相关原因,它不适合某些项目。但对许多人来说,这种解决方案是两害相权取其轻的理想之选。
1赞 Estus Flask 4/15/2022
@Bernesto 这是两害相权取其轻,正是出于特定的原因。当页面上的任何脚本是由另一个开发人员编写的,他们认为使用通用属性名称(如 和)来满足自己的需求是个好主意时,问题就出现了。在模块化 JS 时代考虑这个选项也很奇怪。 可以是一个辅助函数,并在需要时导入,这种方法是正确的,不会收到反对票。该解决方案本身看起来非常可靠,除了它是浮点的,不是定点精度,也没有在更大的数字上进行测试。decimalprecisiondecimal
3赞 DavidTaubmann 9/22/2019 #35

避免在使用整数的操作过程中处理浮点数

正如迄今为止得票最多的答案所述,您可以使用整数,这意味着将您正在处理的每个小点的所有因子乘以 10,然后将结果除以使用的相同数字。

例如,如果使用小数点后 2 位,则在执行运算之前将所有因子乘以 100,然后将结果除以 100。

下面是一个示例,Result1 是通常的结果,Result2 使用解决方案:

var Factor1="1110.7";
var Factor2="2220.2";
var Result1=Number(Factor1)+Number(Factor2);
var Result2=((Number(Factor1)*100)+(Number(Factor2)*100))/100;
var Result3=(Number(parseFloat(Number(Factor1))+parseFloat(Number(Factor2))).toPrecision(2));
document.write("Result1: "+Result1+"<br>Result2: "+Result2+"<br>Result3: "+Result3);

第三个结果是显示改用 parseFloat 时会发生什么,这在我们的例子中造成了冲突。

评论

0赞 hal9000 5/1/2021
我喜欢这个,因为它很简单。但是,您仍然需要担心任何大数字。“1120003000600.126” * 1 仍然显示为 1120003000600.126 “11200030006000.126” * 1 仍然显示为 1120003000600.127,这使得任何解决方案都很痛苦,任何超过 13 位数字的东西都会被破坏
8赞 Simon 9/30/2019 #36

我首先将两个数字都变成整数,执行表达式,然后将结果除以返回小数位来解决它:

function evalMathematicalExpression(a, b, op) {
    const smallest = String(a < b ? a : b);
    const factor = smallest.length - smallest.indexOf('.');

    for (let i = 0; i < factor; i++) {
        b *= 10;
        a *= 10;
    }

    a = Math.round(a);
    b = Math.round(b);
    const m = 10 ** factor;
    switch (op) {
        case '+':
            return (a + b) / m;
        case '-':
            return (a - b) / m;
        case '*':
            return (a * b) / (m ** 2);
        case '/':
            return a / b;
    }

    throw `Unknown operator ${op}`;
}

多个操作的结果(排除的数字来自):eval

0.1 + 0.002   = 0.102 (0.10200000000000001)
53 + 1000     = 1053 (1053)
0.1 - 0.3     = -0.2 (-0.19999999999999998)
53 - -1000    = 1053 (1053)
0.3 * 0.0003  = 0.00009 (0.00008999999999999999)
100 * 25      = 2500 (2500)
0.9 / 0.03    = 30 (30.000000000000004)
100 / 50      = 2 (2)

评论

0赞 jmp 6/5/2023
对我有用,当 AMBUSDT 对由于 0.00001*10000000 返回 100.0000000000000001 而无法正常工作时,修复了具有此功能的 TradingView 轻量级图表,谢谢 Simon
0赞 Simon East 11/19/2023
你知道为什么整数工作正常但只有浮点数有这个不精确问题的理论吗?我很想更好地理解这一点。
0赞 Simon East 11/19/2023
更新:在阅读了 IEEE-754 之后(我的大脑现在很痛),我发现并非所有整数都准确表示,只有那些低于 9007199254740993 的整数(因为 JS 使用 64 位精度)。JS 会将该数字向下舍入 1。再往高,你只能增加 2。但这对于 99% 的用例来说已经绰绰有余了。
0赞 Ste 11/6/2019 #37

我正在寻找相同的修复程序,我发现如果您将整数加到 1 中并计算它.这导致 .这可用于将原始变量四舍五入到正确的量。console.log(0.1 * 0.2 + 1);1.02x

在您的示例中检索到小数位数的长度后,我们可以将其与函数一起使用,以正确地舍入原始变量。2toFixed()x

在代码内部查看此函数在注释部分中的作用。

var myX= 0.2 * 0.1;
var myX= 42.5-42.65;
var myX= 123+333+3.33+33333.3+333+333;

console.log(myX);
// Outputs (example 1): 0.020000000000000004
// Outputs (example 2): -0.14999999999999858
// Outputs (example 3): 34458.630000000005
// Wrong

function fixRoundingError(x) {
// 1. Rounds to the nearest 10
//    Also adds 1 to round of the value in some other cases, original x variable will be used later on to get the corrected result.
var xRound = eval(x.toFixed(10)) + 1;
// 2. Using regular expression, remove all digits up until the decimal place of the corrected equation is evaluated..
var xDec = xRound.toString().replace(/\d+\.+/gm,'');
// 3. Gets the length of the decimal places.
var xDecLen = xDec.length;
// 4. Uses the original x variable along with the decimal length to fix the rounding issue.
var x = eval(x).toFixed(xDecLen);
// 5. Evaluate the new x variable to remove any unwanted trailing 0's should there be any.
return eval(x);
}

console.log(fixRoundingError(myX));
// Outputs (example 1): 0.02
// Outputs (example 2): -0.15
// Outputs (example 3): 34458.63
// Correct

在我尝试过的每种情况下,它都会返回与窗口中的计算器相同的值,并且如果有任何尾随,它也会自动返回结果的四舍五入。0

评论

0赞 ifbamoq 11/4/2020
不要使用 evals。
5赞 Franck Freiburger 11/29/2019 #38

从我的角度来看,这里的想法是将浮点数四舍五入,以获得一个漂亮/简短的默认字符串表示形式。

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

例如。

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;
}

评论

0赞 dehart 4/8/2020
这个答案被低估了。PS:我觉得是因为是签字替身?结果仍然是52 * Math.log10(2)1e15
0赞 Elie G. 6/7/2021
为什么不直接做呢?Math.round(num * 1e15) / 1e15
-1赞 John 2/27/2020 #39

我根据 SheetJS 的答案把它放在一起,我喜欢:

  getCorrectionFactor(numberToCheck: number): number {
    var correctionFactor: number = 1;

    if (!Number.isInteger(numberToCheck)) {
      while (!Number.isInteger(numberToCheck)) {
        correctionFactor *= 10;
        numberToCheck *= correctionFactor;
      }
    }

    return correctionFactor;
  }

0赞 Christiyan 4/8/2021 #40

我喜欢使用校正因子的方法,这是我对 ES6 和 ES5 标准的缩短决定。与该方法相比,它的优点是它不会在数字末尾留下不必要的零,如果我们想四舍五入到百,但结果数字是第十个数字:toFixed

ES6 型号:

// .1 + .2
((a, b, crr) => (a*crr + b*crr)/crr)(.1, .2, 100 /* Correction factor */ ); // 0.3
// .1 * .2
((a, b, crr) => a*crr*b/crr)(.1, .2, 100); // 0.02

ES5 型号:

// .1 + .2
(function(a, b, crr){ return (a*crr + b*crr)/crr; })(.1, .2, 100 /* Correction factor */ ); // 0.3
// .1 * .2
(function(a, b, crr){ return a*crr*b/crr; })(.1, .2, 100); // 0.02
1赞 Novarender 4/22/2021 #41

我通常使用这样的东西。

function pf(n) {
    return Math.round(n * 1e15) / 1e15;
}

我并不声称这在任何方面都是最佳的,但我喜欢它的简单性。它将数字四舍五入到小数点后 15 位左右。我没有看到它返回不准确的浮点数,尽管奇怪的是,当我最后使用时,它已经这样做了,但不是使用这种方法。* 1e-15

此解决方案可能更适合随意使用,而不是精确的数学使用,因为精度错误会弄乱您的代码。

4赞 Karlsson 6/15/2021 #42

我找不到使用内置解决方案来帮助解决此类问题,所以这是我的解决方案:Number.EPSILON

function round(value, precision) {
  const power = Math.pow(10, precision)
  return Math.round((value*power)+(Number.EPSILON*power)) / power
}

这使用 1 和大于 1 的最小浮点数之间的已知最小差值来修复舍入误差,最终仅比舍入阈值低 1。EPSILONEPSILON

64 位浮点的最大精度为 15,32 位浮点的最大精度为 6。您的 JavaScript 可能是 64 位。

评论

0赞 d0rf47 9/28/2021
多么优雅的解决方案,谢谢。
0赞 Biró Dani 12/5/2021 #43

如果你需要进行任意精度的浮点计算,你可以使用我的 NPM 库 gmp-wasm,它基于 GMP + MPFR 库。您可以轻松设置所需的任何精度,并以固定精度返回结果。

<script src="https://cdn.jsdelivr.net/npm/gmp-wasm"></script>
<script>
  gmp.init().then(({ getContext }) => {
    const ctx = getContext({ precisionBits: 100 });
    const result = ctx.Float('0.1').mul(ctx.Float('0.2'));
    document.write(`0.1 * 0.2 = ` + result.toFixed(2));
    ctx.destroy();
  });
</script>

3赞 Mr_NAIF 1/21/2022 #44

这是我的解决方案:

function float(equation, precision = 9) {
    return Math.floor(equation * (10 ** precision)) / (10 ** precision);
}

console.log(float(0.1 * 0.2)); // => 0.02
console.log(float(0.2 + 0.4)); // => 0.6
console.log(float(1 / 3));     // => 0.333333333
console.log(float(1 / 3, 2));  // => 0.33

评论

3赞 Tchakabam 1/25/2022
好答案。但是在这里使用不是很固执己见吗?只是想知道,还是我错过了什么?Math.floorMath.round
1赞 Arthur Ronconi 6/15/2023
我认为是正确的答案,因为应该是 506.67,但它返回了错误的值 506.66。代码需要修复。Math.roundfloat(606.67-100, 2)
0赞 Peter Mortensen 12/4/2023
解释是有序的。例如,想法/要点是什么?从帮助中心:“......始终解释为什么您提出的解决方案是合适的,以及它是如何工作的”。请通过编辑(更改)您的答案来回应,而不是在评论中(*** *** *** 没有 *** *** “编辑:”、“更新:”或类似内容 - 答案应该看起来像今天写的一样)。
0赞 Leslie Wong 6/21/2022 #45

这个 npm 库就是为了解决这个问题而构建的,从我自己的用例来看,并且已经部署在大规模生产中。

npm i jsbi-calculator

它基于 GoogleChromeLabs/jsbi 项目,并使用字符串化表达式来执行任意有理计算,与 Internet Explorer 11 兼容。

浏览器的用法:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Jsbi-calculator Test</title>
    <script src="https://cdn.jsdelivr.net/npm/jsbi-calculator/dist/jsbi-calculator-umd.js"></script>
  </head>
  <body></body>
  <script type="text/javascript">
    const expressionOne = "((10 * (24 / ((9 + 3) * (-2)))) + 17) + 5.2";
    const resultOne = JBC.calculator(expressionOne);
    console.log(resultOne);
    // -> '12.2'

    const userAgent = navigator.userAgent;
    const isIE11 =
      userAgent.indexOf("Trident") > -1 && userAgent.indexOf("rv:11.0") > -1;
    let max;
    // MAX_SAFE_INTEGER not available in IE11
    max = isIE11 ? "9007199254740991" : String(Number.MAX_SAFE_INTEGER);

    console.log(max);
    // -> '9007199254740991'
    const expressionTwo = max + " + 2.2";
    const resultTwo = JBC.calculator(expressionTwo);
    console.log(resultTwo);
    // -> '9007199254740993.2'
  </script>
</html>

3赞 willy wonka 10/1/2022 #46

浮点值编程缺乏精度

浮点值编程缺乏精确度是不同编程语言中的已知问题。JavaScript 是那些在使用浮点值进行数学运算时遇到问题的算法之一。这个问题主要是由于这些编程语言在内存中对浮点数进行二进制表示。

让我们希望编程语言和编译器以及硬件开发人员和工程师能够一劳永逸地解决这些问题,至少解决到 1024 浮点数......可能这足以让 99.999% 的所有其他开发人员在下一个世纪的编程中高枕无忧......

一个可能的解决方案:转换为整数,计算,而不是返回

一个可能的解决方案可能是将浮点数转换为整数,执行计算,然后将结果转换回浮点数。大多数时候,它似乎有效。有很多方法可以进行。原则上:

function calculate()
{
  var x = 0.1 * 0.2; // Original approach
  var y = ((0.1*10) * (0.2*10))/100; // The working possible solution
  document.write(x + '<br>' + y);
}
calculate();

当然,如果需要,可以相应地调整功能。例如:

function calculate()
  {
    var x = 0.12 * 0.23; // Original approach
    var y = ((0.12*100) * (0.23*100))/10000; // The working possible solution
    document.write(x + '<br>' + y);
  }
calculate();

它也可以是自动化的:

function toInt(v)
{
  let i = 1;
  let r;
  while(r % 1 != 0)
  {
    i = i * 10;
    r = v * i;
  }
  return [r, i];
}

function calculate()
{
  let x = 0.11111111;
  let y = 0.22222222;
  let a = x * y; // Original approach

  [x, i] = toInt(x);
  [y, j] = toInt(y);
  let d = i * j;

  // Another working possible solution
  let b = x * y / d;
  console.log(a + '\n' + b)
  document.write(a + '<br>' + b);
}
calculate();

考虑到这一点,我们可以执行以下操作:

function toInt(v)
{
  let i = 1;
  let r;
  while(r % 1 != 0)
  {
    i = i * 10;
    r = v * i;
  }
  return [r, i];
}

function calculate()
{
  let x = 14898
  let y = 10.8
  let z = 100
  let a = x * y / z ; // Original approach

  // Another working possible solution
  [y, i] = toInt(y);
  let b = (x * y) / (z * i);
  console.log(a + '\n' + b)
  document.write(a + '<br>' + b);
}
calculate();

当然,通过一些代码重构,我们也可以删除 while 循环。所以最后我们有了:

function toInt(v)
{
  let d = v.toString().split('.')[1].length;
  let p = Math.pow(10, d);
  let r = v * p;
  return [r, p];
}

function calculate()
{
  let x = 14898
  let y = 10.8
  let z = 100
  let a = x * y / z ; // Original approach

  // Another working possible solution
  [y, i] = toInt(y);
  let b = (x * y) / (z * i);
  console.log(a + '\n' + b)
  document.write(a + '<br>' + b);
}
calculate();

评论

1赞 0stone0 10/1/2022
这将在小数点后 1 位以上的浮点数上中断,例如0.11
0赞 willy wonka 10/4/2022
@0stone0,你是对的:我刚刚展示了原理,但我认为没有必要说如果需要,该功能可以调整甚至推广......多亏了你的建议,我现在改进了答案。它现在是否像您正确地观察到的那样在小数点后 1 位以上的浮点上制动,例如 0.11?