在javascript中的简单数学中,在循环中将数字X次相加会产生与事先乘以X并在之后相加不同的结果

Simple math in javascript, adding number X times in a loop gives different result from multiplying by X beforehand and adding it afterwards

本文关键字:结果 之后 循环 数字 简单 javascript      更新时间:2023-09-26

作为参考,此代码对应于此 codecademy 练习。
这是一个简单的购物车/结帐程序,用于注册商品并将其成本添加到总数中,它还需要商品数量值。

注意:我以前读过关于javascript对数学很奇怪的文章,所以我没有被吓到,但我非常好奇,有兴趣了解这种特殊情况下的行为是什么,所以也许我会得到一些见解,以便能够防止这种情况在未来发生。

所以,我有 2 个练习解决方案,第一个是好的 - 第二个没有问题,我想。他们在这里:

第一条。
与第一种方法效率低下的事实无关(多次遍历所有可能的情况并一遍又一遍地添加项目成本)。引起我注意的是返回的额外十进制值。

var cashRegister = {
    total:0,
    add: function(itemCost){
        this.total += itemCost;
    },
    scan: function(item,quantity) {
        for (var i = 1 ; i <= quantity ; i++ ){
            switch (item) {
            case "eggs": this.add(0.98); break;
            case "milk": this.add(1.23); break;
            case "magazine": this.add(4.99); break;
            case "chocolate": this.add(0.45); break;
            }
        }
    }
};
// scan each item 4 times
cashRegister.scan("eggs",4);
console.log('Your bill is '+ cashRegister.total);
cashRegister.scan("milk",4);
console.log('Your bill is '+ cashRegister.total);
cashRegister.scan("magazine",4);
console.log('Your bill is '+ cashRegister.total);
cashRegister.scan("chocolate",4);
console.log('Your bill is '+ cashRegister.total);

控制台输出:

Your bill is 3.92
Your bill is 8.840000000000002
Your bill is 28.800000000000004
Your bill is 30.6
Your bill is 30.6

第二个
此解决方案将物料数量乘以碰巧出现的任何情况。

var cashRegister = {
    total:0,
    add: function(itemCost){
        this.total += itemCost;
    },
    scan: function(item,quantity) {
        switch (item) {
        case "eggs": this.add(0.98*quantity); break;
        case "milk": this.add(1.23*quantity); break;
        case "magazine": this.add(4.99*quantity); break;
        case "chocolate": this.add(0.45*quantity); break;
        }
    }
};
// scan each item 4 times
cashRegister.scan("eggs",4);
console.log('Your bill is '+cashRegister.total);
cashRegister.scan("milk",4);
console.log('Your bill is '+cashRegister.total);
cashRegister.scan("magazine",4);
console.log('Your bill is '+cashRegister.total);
cashRegister.scan("chocolate",4);
console.log('Your bill is '+cashRegister.total);

控制台输出干净:

Your bill is 3.92
Your bill is 8.84
Your bill is 28.8
Your bill is 30.6
Your bill is 30.6

那么,第一个解决方案是怎么回事,以便返回小数盛会?
提前非常感谢你。

编辑:感谢您尝试帮助我解决我的疑问。虽然我意识到我需要明确表示我知道"JavaScript 使用浮点值作为其数字类型,精度损失是造成这种情况的原因"。

真正的问题是:
为什么相同的计算在第一种解上会失去精度,而在第二种解上却不会丢失精度?

JavaScript 为其number类型使用浮点值,精度损失是造成这种情况的原因。精度损失是由于浮点数使用二进制编码格式(很可能是IEEE 754)这一事实造成的,并且它不能精确地表示大多数数字。例如 0.1不能在 IEEE 754 中表示:

  • 十进制:0.1
  • 二进制版本:00111101110011001100110011001101
  • 十六进制代表: 0x3dcccccd
  • 实际值:0.10000000149011612

我已经使用了这个在线转换器来获得这些结果。

所以直接回答你的问题:浮点数上的更多运算(多重加法,而不是乘法)会导致误差的累积

一个例子

我最喜欢的精度损失示例之一是以下代码:

var a = 0, b = 0;
for (var i = 1; i < 100; i++) {
    a += Math.pow(10, -9*i);
}
for (var j = 100; j < 1; j--) {
    b += Math.pow(10, -9*i);
}
alert(a == b);

这会false发出警报,因为b由于精度损失而为零,而a则不是。

格式

如果你只关心"不错"的输出,你可以使用 #toFixed 方法:

var num = 5.56789;
var n = num.toFixed(2); 

这会产生"5.57".

ECMAScript 标准

根据 ECMAScript 标准(ECMA-262,版本 5.1)第 4.3.19 节。 数字值的定义如下:

对应于双精度 64 位二进制格式 IEEE 754 值的基元值

因此,这就是标准投诉实施为您提供的内容。其他任何可能在后台的内容都是实现细节。

这是由于浮点数固有的不精确性。并非所有有理数都可以用二进制浮点数表示为精确值,因此您所看到的是这种近似的结果。维基百科页面上的这一部分很好地解释了这种转换是如何进行的。

如果你真的需要精确的值,你可以使用 BigDecimal.js 或其他类似的库。