我有以下虚拟测试脚本:

函数测试(){变量x=0.1*0.2;document.write(x);}测试();

这将打印结果0.020000000000000004,而它应该只打印0.02(如果您使用计算器)。据我所知,这是由于浮点乘法精度的错误。

有没有人有一个好的解决方案,在这种情况下,我得到了正确的结果0.02?我知道还有一些函数,比如toFixed或舍入,这是另一种可能,但我真的希望在不进行任何切割和舍入的情况下打印整个数字。我只是想知道你们中的一个人是否有一些好的、优雅的解决方案。

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


当前回答

我在MOD3中遇到了一个严重的舍入错误问题。有时当我应该得到0时,我会得到.000…01。这很容易处理,只需测试<=0.01即可。但有时我会得到2.99999999999998。哎哟!

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

其他回答

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

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

> 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这样的测试库

你只是在做乘法吗?如果是这样的话,那么你可以利用一个关于十进制算术的简单秘密。即NumberOfDecimals(X)+NumberOfDecimals(Y)=预期的NumberOf小数。也就是说,如果我们有0.123*0.12,那么我们知道会有5位小数,因为0.123有3位小数,0.12有2位小数。因此,如果JavaScript给我们一个像0.014760000002这样的数字,我们可以安全地四舍五入到小数点后的第五位,而不用担心丢失精度。

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

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

eg.

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

要处理任意浮点数,请执行以下操作:

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

浮点值编程缺乏精度

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

让我们希望编程语言、编译器、硬件开发人员和工程师能够一次性解决这些问题,至少解决1024个浮点数字。。。也许这将足以让99.999%的其他开发人员在下一个世纪的编程中感到安心。。。

一种可能的解决方案:转换为整数,计算,然后返回

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

函数测试(){变量x=0.1*0.2;//原始方法变量y=((0.1*10)*(0.2*10))/100;//可行的解决方案document.write(x+'<br>'+y);}测试();

当然,在需要的情况下,可以相应地调整功能,例如:

函数测试(){变量x=0.12*0.23;//原始方法变量y=((0.12*100)*(0.23*100))/10000;//可行的解决方案document.write(x+'<br>'+y);}测试();

它也可以是自动化的:

函数myToInt(v){设i=1;设r;而(r%1!=0){i=i*10;r=v*i;}返回[r,i];}函数测试(){设x=0.11111111;设y=0.22222222;设a=x*y;//原始方法[x,i]=myToInt(x);[y,j]=myToInt(y);设d=i*j;设b=x*y/d;//另一种可行的解决方案console.log(a+'\n'+b)document.write(a+'<br>'+b);}测试();

考虑到这一点,我们可以这样做:

函数myToInt(v){设i=1;设r;而(r%1!=0){i=i*10;r=v*i;}返回[r,i];}函数测试(){设x=14898设y=10.8设z=100设a=x*y/z;//原始方法[y,i]=myToInt(y);设b=(x*y)/(z*i);//另一种可行的解决方案console.log(a+'\n'+b)document.write(a+'<br>'+b);}测试();