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

供参考,此代码对应this codecademy exercise.
这是一个简单的 cart/checkout 程序,用于注册一个项目并将其成本添加到总数中,它还需要一个项目数量值。

注意:我之前看过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 uses floating point values for its number type and the loss of precision is the cause for this"。

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

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

  • 十进制:0.1
  • 二进制表示:00111101110011001100110011001101
  • 十六进制数:0x3dcccccd,
  • 实际值:0.10000000149011612

这些结果我用了this online converter

所以直接回答你的问题:对浮点数的更多操作(乘法而不是乘法)导致错误累积

一个例子

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

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 不是。

正在格式化

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

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

这会产生 "5.57"

ECMAScript 标准

根据 ECMAScript 标准(ECMA-262,版本 5.1),section 4.3.19.数值的定义如下:

primitive value corresponding to a double-precision 64-bit binary format IEEE 754 value

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

这是由于浮点数固有的不精确性。并非所有有理数都可以用二进制浮点数表示为精确值,因此您看到的是这种近似的结果。 This section on the Wikipedia page 对如何进行这种转换有很好的解释。

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