如何处理JavaScript中的浮点数精度?

我有以下虚拟测试脚本:

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

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

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

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

619623 次浏览

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

使用

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

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

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

试试我的Chiliadic算术库,你可以看到这里。如果你想要一个新的版本,我可以给你一个。

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

你只需要决定你真正想要多少位小数——不能吃蛋糕:-)

数值误差随着每次进一步的操作而积累,如果你不早点将其切断,它只会增长。呈现看起来干净的结果的数值库在每一步简单地切断最后2位数字,数值协处理器也有“正常”和“完整”的长度,出于同样的原因。截止对于处理器来说很便宜,但对于脚本来说非常昂贵(乘除和使用pov(…))。好的数学库会提供楼层(x, n)为你做截止。

因此,至少你应该使用pov(10, n)制作全局var/常数-这意味着你决定了你需要的精度:-)然后执行:

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

你也可以继续做数学运算,只在最后截止——假设你只显示结果,而不对结果做if-s。如果你能做到这一点,那么. to固定(…)可能会更高效。

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

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

尝试javascript-print intf,你会这样称呼它:

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

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

您也可以使用Number.to固定()用于显示目的,如果您不希望仅包含更多文件以将浮点舍入到给定精度。

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

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

var times = function (a, b) {return Math.round((a * b) * 100)/100;};

---or---

var fpFix = function (n) {return Math.round(n * 100)/100;};
fpFix(0.1*0.2); // -> 0.02

---also---

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

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

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

如果您正在处理浮点数总是,则需要很小的舍入误差,因此您还必须始终将显示的结果舍入为合理的结果。作为回报,您将获得非常快速且强大的算术,因为所有计算都在处理器的本机二进制文件中。

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

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

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

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

浮点指南

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

这要看是什么样的你正在做的计算。

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

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

如果第一点真的适用于您,请使用JavaScript的BigDecimal,它一点也不优雅,但实际上解决了问题,而不是提供不完美的解决方法。

你只执行乘法运算吗?如果是这样,那么你可以利用一个关于小数运算的巧妙秘密。那就是NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals。也就是说,如果我们有0.123 * 0.12,那么我们知道会有5个小数位,因为0.123有3个小数位,0.12有2个小数位。因此,如果JavaScript给我们一个像0.014760000002这样的数字,我们可以安全地四舍五入到小数第5位,而不用担心失去精度。

我喜欢Pedro Ladaria的解决方案,并使用类似的东西。

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

与Pedros解决方案不同,这将舍入0.999…重复,并且准确到最低有效数字的加/减1。

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

phpjs.org的round()函数运行良好:http://phpjs.org/functions/round

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

我这里有一个变通方法。例如,仅乘以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 bb=b*Math.pow(10,diff);return (a+b)/Math.pow(10,decA);}else if (diff<0){//a has more decimals than ba=a*Math.pow(10,-diff);return (a+b)/Math.pow(10,decB);}else{return (a+b)/Math.pow(10,decA);}}

可怕但工作:)

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

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

对于数学倾向: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 * y0.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这样的测试库

这对我有用:

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

使用以下功能输出:

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)

使用Number(1.234443). to固定(2);它将打印1.23

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

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

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

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

同一作者还有另外两个类似的库:

Big.js

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

Decimal.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>

我在mod 3中遇到了一个严重的舍入错误问题。有时当我应该得到0时,我会得到.000…01。这很容易处理,只需测试<=.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);

0.6*3太棒了!))对我来说,这很好:

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

非常简单))

如果您想绕过此问题进行小操作,您可以使用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;

输入图片描述

    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.1984999999decimal library implementationvar 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

当添加两个浮点值时,它永远不会给出精确的值,所以我们需要将其固定为某个数字,以帮助我们进行比较。

console.log((parseFloat(0.1)+parseFloat(0.2)))解析浮点数(0.3).固定(1));

我不擅长编程,但对这个主题非常感兴趣,所以我试图了解如何在不使用任何库或脚本的情况下解决这个问题

我在便签本上写的

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

你可能需要重构这段代码,因为我不擅长编程:)

问题

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

浮点值的输入和输出

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

  • 将输入舍入到预期精度,或确保不能以更高精度输入任何值。
  • 在舍入/格式化输出之前向输出添加一个小值,该值小于或等于所需精度的1/4,并且大于输入和计算过程中舍入错误引起的最大预期误差。如果这是不可能的,则所用数据类型的精度组合不足以为您的计算提供所需的输出精度。

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

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

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

最好避免这个问题

通过使用数据类型(整数或定点格式)进行这样的计算来避免这些问题通常更有效,这样可以存储预期的输入而不会出现舍入错误。一个例子是,您永远不应该在财务计算中使用浮点值。

请注意,对于一般用途,这种行为可能是可以接受的。
当比较这些浮点值以确定适当的操作时,会出现问题。
随着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

处理任意浮点数:

function shorten(num) {num += 0.000000001;// to deal with "12.03999999997" formnum += '';return num.replace(/(\.\d*?)0{5,}\d+$/, '$1') * 1;}
console.log(1.2+1.9===1.3+1.8);// falseconsole.log(shorten(1.2+1.9)===shorten(1.3+1.8));// true

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

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 functionfunction 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'>

更新:2019年8月20日

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

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

这些条件正常工作:

precisionRound(0.005, 2) // produces 0.01precisionRound(1.0005, 3) // produces 1.001precisionRound(1234.5, 0) // produces 1235precisionRound(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()页面,所以也许有人可以提供更好的解决方案。

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

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.3d.value = 0.3 - 0.2;              // 0.1d.value = 0.1 + 0.2 - 0.3;        // 0d.value = 5.551115123125783e-17;  // 0d.value = 1 / 9;                  // 0.1111

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

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

理想情况下,你想使用高比(比如12),然后在需要呈现或存储时将其向下转换。就我个人而言,我确实尝试创建了一个UInt8Array并尝试创建一个精度值(很像SQLDecimal类型),但是由于Javascript不允许你重载运算符,不能使用基本的数学运算符(+-/*)而使用add()substract()mult()等函数会变得有点乏味。出于我的需要,这不值得。

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

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

0.1 * 0.2                                // 0.020000000000000004x = 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为基数;配置选项;NaN;无穷大;小数点后指定的精度;仅适用于除法的精度;基数前缀。

decimal.js:以2-64为基数;配置选项;NaN;Infinity;非整数幂,exp,ln,log;以有效数字指定的精度;始终应用的精度;随机数。

链接到详细的比较

优雅、可预测、可重用

让我们以一种优雅的、可重用的方式来处理这个问题。下面的七行代码将允许你在任何数字上访问你想要的浮点精度,只需将.decimal附加到数字、公式或内置的Math函数的末尾。

// 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); // 1console.log((0.08 + 0.02).decimal); // 0.1console.log((0.008 + 0.002).decimal); // 0.01console.log((0.0008 + 0.0002).decimal); // 0.001
Number.precision = 2;console.log("Precision: 2");console.log((0.8 + 0.2).decimal); // 1console.log((0.08 + 0.02).decimal); // 0.1console.log((0.008 + 0.002).decimal); // 0.01console.log((0.0008 + 0.0002).decimal); // 0
Number.precision = 1;console.log("Precision: 1");console.log((0.8 + 0.2).decimal); // 1console.log((0.08 + 0.02).decimal); // 0.1console.log((0.008 + 0.002).decimal); // 0console.log((0.0008 + 0.0002).decimal); // 0
Number.precision = 0;console.log("Precision: 0");console.log((0.8 + 0.2).decimal); // 1console.log((0.08 + 0.02).decimal); // 0console.log((0.008 + 0.002).decimal); // 0console.log((0.0008 + 0.0002).decimal); // 0

干杯!

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

正如迄今为止投票最多的答案所述,您可以处理整数,这意味着您正在使用的每个小数将所有因子乘以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时会发生什么,这在我们的案例中造成了冲突。

通过首先使两个数字整数,执行表达式,然后除以结果以获取小数位来解决它:

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)

我正在寻找相同的修复,我发现如果你在1中添加一个整数并评估console.log(0.1 * 0.2 + 1);。这导致1.02。这可用于将原始x变量舍入到正确的数量。

一旦在您的示例中检索到小数位数2的长度,我们就可以将其与toFixed()函数一起使用,以正确舍入原始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

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

从我的角度来看,这里的想法是舍入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)。这些位如下所示…wikipedia

(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 ) / 1e150.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 digitsreturn Math.round(num * mult) / mult;}

根据@SheetJs的回答将其组合在一起,我喜欢:

  getCorrectionFactor(numberToCheck: number): number {var correctionFactor: number = 1;    
if (!Number.isInteger(numberToCheck)) {while (!Number.isInteger(numberToCheck)) {correctionFactor *= 10;numberToCheck *= correctionFactor;}}
return correctionFactor;}

我喜欢带修正因子的方法,这里是我对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

我通常使用这样的东西。

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

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

此解决方案可能更适合随意使用-而不是精确的数学使用-其中精度错误会扰乱您的代码。

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

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

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

最大精度是15 for 64bit浮点和6 for 32bit浮点。您的javascript可能是64位的。

如果您需要进行任意精度的浮点计算,您可以使用我的名为gmp-wasm的NPM库,它基于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>

我的回答可能会迟到,但这是我的解决方案:

function float(equation, precision = 9) {return Math.floor(equation * (10 ** precision)) / (10 ** precision);}
console.log(float(0.1 * 0.2)); // => 0.02console.log(float(0.2 + 0.4)); // => 0.6console.log(float(1 / 3));     // => 0.333333333console.log(float(1 / 3, 2));  // => 0.33

这个npm库是从我自己的用例中为解决这个问题而构建的,并已部署在大规模生产中。希望它能对其他人有所帮助。

npm i jsbi计算器

它基于GoogleChromeLabs/jsbi项目,使用字符串表达式执行任意有理计算,ie11兼容。

浏览器用法:

<!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 IE11max = 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>

浮点值编程缺乏精度

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

让我们希望编程语言和编译器以及硬件开发人员和工程师能够处理这些问题,一劳永逸地解决它们,至少解决到1024个浮点数……这可能足以让99.999%的其他开发人员在下个世纪的编程中感到安心。

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

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

function test(){var x = 0.1 * 0.2; // Original approachvar y = ((0.1*10) * (0.2*10))/100; // The working possible solutiondocument.write(x + '<br>' + y);}test();

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

function test(){var x = 0.12 * 0.23; // Original approachvar y = ((0.12*100) * (0.23*100))/10000; // The working possible solutiondocument.write(x + '<br>' + y);}test();

它也可以是自动化的:

function myToInt(v){let i = 1;let r;while(r % 1 != 0){i = i * 10;r = v * i;}return [r, i];}
function test(){let x = 0.11111111;let y = 0.22222222;let a = x * y; // Original approach
[x, i] = myToInt(x);[y, j] = myToInt(y);let d = i * j;
let b = x * y / d; // Another working possible solutionconsole.log(a + '\n' + b)document.write(a + '<br>' + b);}test();

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

function myToInt(v){let i = 1;let r;while(r % 1 != 0){i = i * 10;r = v * i;}return [r, i];}
function test(){let x = 14898let y = 10.8let z = 100let a = x * y / z ; // Original approach
[y, i] = myToInt(y);let b = (x * y) / (z * i); // Another working possible solutionconsole.log(a + '\n' + b)document.write(a + '<br>' + b);}test();