如何处理JavaScript中的浮点数精度?
我有以下虚拟testing脚本:
function test(){ var x = 0.1 * 0.2; document.write(x); } test();
这将打印结果0.020000000000000004
而它应该只打印0.02
(如果您使用您的计算器)。 据我了解这是由于浮点乘法精度的错误。
有没有人有一个很好的解决scheme,以便在这种情况下,我得到正确的结果0.02
? 我知道有这样的function,如固定或四舍五入是另一种可能性,但我希望的是真正打印完整的数字,没有任何切割和舍入。 只是想知道你们中的一个是否有一些漂亮,优雅的解决scheme。
当然,否则我会轮到10个左右的数字。
从浮点指南 :
我能做些什么来避免这个问题?
这取决于你在做什么样的计算。
- 如果你真的需要你的结果加起来,特别是当你使用金钱:使用一个特殊的十进制数据types。
- 如果您不想看到所有这些额外的小数位:只需在显示时将结果格式化为固定的小数位数。
- 如果你没有可用的小数数据types,另一种方法是使用整数,例如完全以美分计算。 但这是更多的工作,并有一些缺点。
请注意,第一点只适用于您确实需要特定的精确小数行为。 大多数人不需要这样做,他们只是激怒了他们的程序不能正确地处理像1/10这样的数字,而没有意识到如果它以1/3出现,他们甚至不会眨眼同样的错误。
如果第一点确实适用于您,请使用BigDecimal for JavaScript ,这个方法不够高雅,但实际上解决了问题,而不是提供一个不完善的解决方法。
我喜欢Pedro Ladaria的解决scheme,并使用类似的东西。
function strip(number) { return (parseFloat(number).toPrecision(12)); }
不像佩德罗斯解决scheme,这将收集0.999 …重复,并准确的加/减1最不重要的数字。
注意:处理32或64位浮点数时,应使用toPrecision(7)和toPrecision(15)以获得最佳结果。 看到这个问题的原因信息。
对于math上的倾斜: 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
(非常快)解决scheme看起来像这样:
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这样的testing库
你只是在执行乘法? 如果是这样,那么你可以利用你的优势一个关于十进制算术的整洁的秘密。 那就是NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals
。 也就是说,如果我们有0.123 * 0.12
那么我们知道将会有5位小数,因为0.123
有3位小数, 0.12
有两位小数。 因此,如果JavaScript给了我们一个像0.014760000002
这样的数字,我们可以安全地舍入到小数点后第五位,而不用担心丢失精确度。
您正在寻找JavaScript的sprintf
实现,以便您可以使用您所期望的格式写出错误较less的浮动内容(因为它们以二进制格式存储)。
试试javascript-sprintf ,你可以这样调用它:
var yourString = sprintf("%.2f", yourNumber);
打印出您的号码作为一个有两位小数的浮点数。
你也可以使用Number.toFixed()作为显示的目的,如果你不想把更多的文件只包含浮点四舍五入到一个给定的精度。
该函数将根据两个浮点数相乘来确定所需的精度,并以适当的精度返回结果。 优雅虽然不是。
function multFloats(a,b){ var atens = Math.pow(10,String(a).length - String(a).indexOf('.') - 1), btens = Math.pow(10,String(b).length - String(b).indexOf('.') - 1); return (a * atens) * (b * btens) / (atens * btens); }
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
你只需要决定你真正想要的小数位数是多less – 不能吃蛋糕,也不能吃。
数值误差会随着每一次进一步的操作而累积,如果不尽早切断,它将会增长。 数据库提供的结果看起来很干净,在每一步都简单地切断了最后两位数字,数字协处理器也出于同样的原因具有“正常”和“全部”长度。 Cuf-offs对于处理器来说是便宜的,但对于你来说在脚本中是非常昂贵的(乘法和分割和使用pov(…))。 好的math库将提供floor(x,n)来为你做切断。
所以至less你应该使用pov(10,n)使全局variablesvar / constant – 意味着你决定了你需要的精度:-)然后:
Math.floor(x*PREC_LIM)/PREC_LIM // floor - you are cutting off, not rounding
你也可以继续做math,最后只是截断 – 假设你只是显示而不是做结果的if -s。 如果你能做到这一点,那么.toFixed(…)可能会更有效率。
如果你正在进行if / s比较,并且不想削减,那么你还需要一个小的常量,通常叫eps,它比最大预期误差小一个位。 假设您的截止点是最后两位小数 – 那么您的eps在最后一个(第三个最低有效位)的第三位有1,您可以用它来比较结果是否在预期的eps范围内(0.02 -eps <0.1 * 0.2 <0.02 + eps)。
phpjs.org的round()函数很好用: http ://phpjs.org/functions/round
num = .01 + .06; // yields 0.0699999999999 rnum = round(num,12); // yields 0.07
我发现BigNumber.js符合我的需求。
用于任意精度十进制和非十进制算术的JavaScript库。
它有很好的文档 ,作者非常勤快的回应反馈。
同一作者有两个其他类似的图书馆:
Big.js
用于任意精度十进制算术的小型快速JavaScript库。 bignumber.js的小妹妹。
和Decimal.js
JavaScript的任意精度十进制types。
以下是使用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 × .2 = <span id="product"></span><br> .1 + .2 = <span id="sum"></span><br>
你得到的结果在不同语言,处理器和操作系统的浮点实现中是正确和相当一致的 – 唯一改变的是浮点数实际上是双精度(或更高)时的不准确度。
在二进制浮点0.1是像十进制1/3(即0.3333333333333 …永远),只是没有准确的方法来处理它。
如果你处理的是花车, 总是会有小的舍入误差,所以你总是需要将显示的结果舍入到合理的位置。 作为回报,你得到非常快速和强大的算术,因为所有的计算都在处理器的本地二进制。
大多数情况下,解决scheme不是切换到定点算术,主要是因为速度慢得多,99%的时间你不需要精度。 如果你正在处理那些确实需要这种准确性的东西(例如金融交易),那么Javascript可能不是最好的工具(因为你想要强制执行定点types,静态语言可能更好)。
你正在寻找优雅的解决scheme,恐怕就是这样:浮动速度快,但有小的舍入误差 – 总是在显示结果时合理化。
为了避免这种情况,你应该使用整数值而不是浮点数。 所以当你想要2个位置的精度与值* 100的工作,3个职位使用1000.显示时,您使用格式化器放入分隔符。
许多系统以这种方式省略了小数。 这就是为什么许多系统使用美分(作为整数)而不是美元/欧元(作为浮点)的原因。
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((ab).toFixed(2)); c = 0.1;
看看定点算术 。 如果你想操作的数字范围很小(例如,货币),它可能会解决你的问题。 我会把它舍弃到几个十进制值,这是最简单的解决scheme。
试试我的辣椒算术库,你可以在这里看到。 如果你想要一个更新的版本,我可以帮你一个。
您不能用二进制浮点types(这是ECMAScript用来表示浮点值)精确地表示大多数小数部分。 因此,除非使用任意的精度算术types或基于十进制的浮点types,否则不存在优雅的解决scheme。 例如, Windows附带的计算器应用程序现在使用任意精度algorithm来解决此问题 。
你是对的,原因是浮点数的精度有限。 将有理数存储为两个整数的一部分,在大多数情况下,您可以存储数字而不会造成任何精度损失。 打印时,可能需要将结果显示为分数。 用我提出的代表,它变得微不足道。
当然这对于不合理的数字没有多大帮助。 但是您可能希望以最less的问题(例如,检测sqrt(3)^2)
等情况来优化计算。
使用数字(1.234443).toFixed(2); 它会打印1.23
function test(){ var x = 0.1 * 0.2; document.write(Number(x).toFixed(2)); } test();
我有一个令人讨厌的四舍五入错误问题与模3。有时当我应该得到0我会得到.000 … 01。 这很容易处理,只是testing<= 0.01。 但是有时候我会得到2.99999999999998。 哎哟!
BigNumbers解决了这个问题,但引入了另一个有点讽刺意味的问题。 当试图加载到BigNumbers 8.5时,我被告知,这是真正的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);
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
使用
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
这适用于我:
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
使用以下function输出:
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)
。
同时增加两个浮点值,它永远不会给出精确的值,所以我们需要将其固定到某个数字,这将有助于我们进行比较。
console.log((parseFloat(0.1)+ parseFloat(0.2))。toFixed(1)== parseFloat(0.3).toFixed(1));
我并不擅长编程,但是对这个话题真的很感兴趣,所以我试图理解如何解决这个问题,而不使用任何库或脚本
我在scratchpad上写了这个
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"));
你可能需要重构这段代码,因为我不擅长编程:)
问题
浮点不能完全保存所有十进制值。 所以当使用浮点格式时,input值总会有舍入误差。 input上的错误会导致输出上的错误。 在离散function或操作员的情况下,function或操作员离散点的输出可能会有很大差异。
input和输出浮点值
所以,当使用浮点variables时,您应该始终注意到这一点。 无论你想从浮点计算中得到什么输出,都应该在显示之前进行格式化/调节。
当只使用连续函数和运算符时,往往会舍入到所需的精度(不要截断)。 用于将浮动转换为string的标准格式化function通常会为您执行此操作。
由于舍入增加了一个错误,可能导致总误差超过所需精度的一半,应根据input的期望精度和输出的期望精度来校正输出。 你应该
- 将input舍入到预期的精确度,或确保没有数值可以更高的精度input。
- 在对输出进行舍入/格式化之前,在输出中添加一个较小的值,该值小于或等于所需精度的1/4,并且大于input和计算过程中舍入误差导致的最大预期误差。 如果这是不可能的,则所使用的数据types的精度的组合不足以为计算提供期望的输出精度。
这两件事通常没有完成,在大多数情况下,不做这些事情造成的差异对于大多数用户来说太小而不重要,但是我已经有一个项目,没有这些更正的用户不接受输出。
离散function或运算符(如modula)
当涉及离散的操作符或函数时,可能需要进行额外的更正,以确保输出符合预期。 在四舍五入之前舍入和添加小的更正不能解决问题。
中间计算结果的特殊检查/校正,可能需要在应用离散函数或操作符后立即进行。 对于一个特定的情况(modula操作符),请参阅我的问题的答案: 为什么模数运算符在javascript中返回小数?
最好避免出现问题
通过使用数据types(整数或固定点格式)来避免这些问题通常会更有效率,这样可以存储预期的input而不会产生舍入误差。 其中的一个例子是,您不应该使用浮点值进行财务计算。
请注意,对于一般用途,这种行为可能是可以接受的。
比较这些浮点值以确定适当的操作时,会出现问题。
随着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
有一个更好的方法,保持精度,也剥离零。 这需要一个input数字,并通过一些魔法铸造将拉掉任何尾随零。 我发现16是我的精度限制,这是相当不错的,如果你不冥王星上的卫星。 复制和粘贴代码的演示在这里:
function convertToFixed(inputnum) { var mynum = inputnum.toPrecision(16); //adds zeros for demonstration purposes alert(mynum); var mynumstr = mynum.toString(); var final = parseFloat(mynumstr); alert (final); return final; } convertToFixed(1); convertToFixed(6/9); //use an infinite to show without trailing zeros
一个简洁的版本将是
function convertToFixed(inputnum) { var mynumstr = inputnum.toString(); var final = parseFloat(mynumstr); return final; } alert(convertToFixed(1.00000));
更短,不需要额外的库。 不明白这一个继续downvote,我真的不知道。