我有以下虚拟测试脚本:
函数 test() { var x = 0.1 * 0.2; document.write(x); } 测试();
这将打印结果 0.020000000000000004
,而它应该只打印 0.02
(如果您使用计算器)。据我了解,这是由于浮点乘法精度的错误。
有没有人有一个好的解决方案,以便在这种情况下我得到正确的结果0.02
?我知道有像 toFixed
这样的函数,或者四舍五入是另一种可能性,但我想真正打印整数而不进行任何切割和四舍五入。只是想知道你们中的一个人是否有一些不错的、优雅的解决方案。
当然,否则我会四舍五入到 10 位数左右。
0.1
映射到有限的二进制浮点数。
我能做些什么来避免这个问题?这取决于你在做什么样的计算。如果您确实需要将结果精确地相加,尤其是在使用金钱时:使用特殊的十进制数据类型。如果您只是不想看到所有这些额外的小数位:只需在显示结果时将结果格式化为固定的小数位数。如果您没有可用的十进制数据类型,另一种方法是使用整数,例如完全以美分计算货币。但这是更多的工作并且有一些缺点。
请注意,仅当您确实需要特定的精确小数行为时才适用第一点。大多数人不需要这个,他们只是对他们的程序不能正确处理像 1/10 这样的数字感到恼火,却没有意识到如果发生 1/3,他们甚至不会因同样的错误而眨眼。
如果第一点确实适用于您,请使用 BigDecimal for JavaScript,它一点也不优雅,但实际上解决了问题,而不是提供不完美的解决方法。
我喜欢 Pedro Ladaria 的解决方案并使用类似的东西。
function strip(number) {
return (parseFloat(number).toPrecision(12));
}
与 Pedros 解决方案不同,这将四舍五入 0.999...重复,并且精确到最低有效数字的加/减一。
注意:处理 32 或 64 位浮点数时,应使用 toPrecision(7) 和 toPrecision(15) 以获得最佳结果。有关原因的信息,请参见 this question。
toPrecision
返回字符串而不是数字。这可能并不总是可取的。
(9.99*5).toPrecision(2)
= 50 而不是 49.95 因为 toPrecision 计算整数,而不仅仅是小数。然后你可以使用 toPrecision(4)
,但如果你的结果是 >100 那么你又不走运了,因为它允许前三个数字和一个小数,这样移动点,或多或少地呈现它无法使用。我最终改用 toFixed(2)
对于数学倾向: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) = ExpectedNumberOfDecimals
。也就是说,如果我们有 0.123 * 0.12
,那么我们知道会有 5 个小数位,因为 0.123
有 3 个小数位,而 0.12
有两个小数位。因此,如果 JavaScript 给我们一个像 0.014760000002
这样的数字,我们可以安全地四舍五入到小数点后 5 位,而不必担心失去精度。
令人惊讶的是,此功能尚未发布,尽管其他功能也有类似的变体。它来自 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 函数 functionprecisionRound(number,precision) { var factor = Math.pow(10,precision);返回 Math.round(数字 * 因子) / 因子; } 按钮{ 显示:块; }
更新:2019 年 8 月 20 日
刚刚注意到这个错误。我相信这是由于 Math.round()
的浮点精度错误。
precisionRound(1.005, 2) // produces 1, incorrect, should be 1.01
这些条件正常工作:
precisionRound(0.005, 2) // produces 0.01
precisionRound(1.0005, 3) // produces 1.001
precisionRound(1234.5, 0) // produces 1235
precisionRound(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()
页面,因此也许有人可以提供更好的解决方案。
12
和 precisionRoundMod
的精度可以解决我的用例!
我发现 BigNumber.js 满足我的需求。
用于任意精度十进制和非十进制算术的 JavaScript 库。
它具有良好的documentation,并且作者非常勤奋地响应反馈。
同一作者还有 2 个其他类似的库:
一个用于任意精度十进制算术的小型快速 JavaScript 库。 bignumber.js 的小妹妹。
JavaScript 的任意精度 Decimal 类型。
下面是一些使用 BigNumber 的代码:
$(function(){ var product = BigNumber(.1).times(.2); $('#product').text(product); var sum = BigNumber(.1).plus(.2); $ ('#sum').text(sum); }); .1 × .2 =
.1 + .2 =
您正在寻找 JavaScript 的 sprintf
实现,以便您可以以您期望的格式写出带有小错误的浮点数(因为它们以二进制格式存储)。
试试 javascript-sprintf,你会这样称呼它:
var yourString = sprintf("%.2f", yourNumber);
将您的数字打印为带有两位小数的浮点数。
如果您不想仅仅为了浮点舍入到给定精度而包含更多文件,您也可以将 Number.toFixed() 用于显示目的。
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
此函数将从两个浮点数相乘中确定所需的精度,并返回具有适当精度的结果。虽然不是很优雅。
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);
}
如果您想绕过此问题进行小型操作,可以使用 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;
你只需要决定你真正想要多少个十进制数字——不能吃蛋糕也不能吃:-)
数字错误随着每一次进一步的操作而累积,如果你不及早将其切断,它只会增长。呈现看起来干净的结果的数值库只是在每一步中截断最后 2 位数字,出于同样的原因,数值协处理器也具有“正常”和“完整”长度。 Cuf-offs 对于处理器来说很便宜,但在脚本中对你来说非常昂贵(乘法和除法以及使用 pov(...))。好的数学库会提供 floor(x,n) 来为你做截止。
所以至少你应该用 pov(10,n) 制作全局变量/常量——这意味着你决定了你需要的精度:-) 然后做:
Math.floor(x*PREC_LIM)/PREC_LIM // floor - you are cutting off, not rounding
你也可以继续做数学,只在最后切断 - 假设你只显示而不是用结果做 if-s。如果你能做到这一点,那么 .toFixed(...) 可能会更有效。
如果您正在执行 if-s/comparisons 并且不想削减,那么您还需要一个小常数,通常称为 eps,它比最大预期误差高一位小数。假设您的截止值是最后两位小数 - 那么您的 eps 在倒数第三位(第三位最低有效位)为 1,您可以使用它来比较结果是否在预期的 eps 范围内(0.02 -eps < 0.1 *0.2 < 0.02 +eps)。
Math.floor(-2.1)
是 -3
。所以也许使用例如 Math[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
floor
而不是 round
?
请注意,对于一般用途,这种行为可能是可以接受的。
在比较这些浮点值以确定适当的操作时会出现问题。
随着 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
0.9 !== 0.8999999761581421
phpjs.org 上的 round() 函数运行良好:http://phpjs.org/functions/round
num = .01 + .06; // yields 0.0699999999999
rnum = round(num,12); // yields 0.07
decimal.js、big.js 或 bignumber.js 可用于避免 Javascript 中的浮点操作问题:
0.1 * 0.2 // 0.020000000000000004
x = 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;配置选项;钠;无穷;以小数位指定的精度;精度仅适用于除法;基础前缀。
decimal.js:基数 2-64;配置选项;钠;无穷;非整数幂,exp,ln,log;以有效数字指定的精度;始终应用精度;随机数。
Math.pow
ie **
已经处理了吗?
您得到的结果在不同语言、处理器和操作系统的浮点实现中是正确且相当一致的——唯一改变的是当浮点数实际上是双精度(或更高)时的不准确程度。
二进制浮点数的 0.1 就像十进制数的 1/3(即 0.3333333333333 ......永远),只是没有准确的方法来处理它。
如果您正在处理浮点数,则总是期望小的舍入误差,因此您还必须始终将显示的结果舍入到合理的值。作为回报,您将获得非常快速且强大的算术,因为所有计算都在处理器的本机二进制文件中。
大多数情况下,解决方案不是切换到定点算术,主要是因为它慢得多,而且 99% 的时间你不需要精度。如果您正在处理确实需要这种准确性的东西(例如金融交易),Javascript 可能不是最好的工具(因为您想要强制执行定点类型,静态语言可能更好)。
您正在寻找优雅的解决方案,那么恐怕就是这样:浮点数很快但舍入误差很小 - 在显示结果时总是舍入到合理的值。
0.6 * 3 太棒了!))对我来说这很好用:
function dec( num )
{
var p = 100;
return Math.round( num * p ) / p;
}
非常非常简单))
8.22e-8 * 1.3
之类的东西?
为避免这种情况,您应该使用整数值而不是浮点数。因此,当您希望 2 个位置精度使用值 * 100 时,3 个位置使用 1000。显示时使用格式化程序放入分隔符。
许多系统忽略了以这种方式处理小数。这就是为什么许多系统使用美分(作为整数)而不是美元/欧元(作为浮点数)工作的原因。
不优雅,但可以完成工作(删除尾随零)
var num = 0.1*0.2;
alert(parseFloat(num.toFixed(10))); // shows 0.02
问题
浮点数不能准确存储所有十进制值。因此,当使用浮点格式时,输入值总是会出现舍入错误。输入上的错误当然会导致输出上的错误。在离散函数或运算符的情况下,在函数或运算符离散的点附近的输出可能会有很大差异。
浮点值的输入和输出
因此,在使用浮点变量时,您应该始终注意这一点。并且无论您想从浮点计算中获得什么输出,都应该在考虑到这一点之前进行格式化/调节。当只使用连续函数和运算符时,通常会舍入到所需的精度(不要截断)。用于将浮点数转换为字符串的标准格式化功能通常会为您执行此操作。因为舍入会增加一个误差,可能导致总误差超过所需精度的一半,所以应根据输入的预期精度和输出的预期精度来校正输出。你应该
将输入四舍五入到预期的精度,或者确保不能以更高的精度输入任何值。
在舍入/格式化输出之前向输出添加一个小值,该值小于或等于所需精度的 1/4,并且大于输入和计算期间舍入误差引起的最大预期误差。如果这不可能,则所用数据类型的精度组合不足以为您的计算提供所需的输出精度。
这两件事通常不做,在大多数情况下,不做它们造成的差异对大多数用户来说太小而不重要,但我已经有一个项目,如果没有这些更正,用户不接受输出。
离散函数或运算符(如模数)
当涉及离散运算符或函数时,可能需要进行额外的更正以确保输出符合预期。舍入和在舍入前添加小的修正无法解决问题。
可能需要在应用离散函数或运算符后立即对中间计算结果进行特殊检查/修正。对于特定情况(模运算符),请参阅我对问题的回答:Why does modulus operator return fractional number in javascript?
最好避免出现问题
通过使用数据类型(整数或定点格式)进行这样的计算,可以更有效地避免这些问题,这样可以存储预期的输入而不会出现舍入错误。一个例子是你不应该在财务计算中使用浮点值。
优雅、可预测和可重用
让我们以一种优雅的可重用方式来处理这个问题。以下七行将让您只需将 .decimal
附加到数字、公式或内置 Math
函数的末尾即可访问所需的任何数字的浮点精度。
// 首先扩展原生 Number 对象来处理精度。这将填充 // 所有数学运算的功能。 Object.defineProperty(Number.prototype, "decimal", { get: function decimal() { Number.precision = "precision" in Number ? Number.precision : 3; var f = Math.pow(10, Number.precision);返回 Math.round( 这个 * f ) / f; } }); // 现在让我们通过调整全局精度级别和//检查结果来看看它是如何工作的。 console.log("'1/3 + 1/3 + 1/3 = 1' 对吗?"); console.log((0.3333 + 0.3333 + 0.3333).decimal == 1); // true console.log(0.3333.decimal); // 0.333 - 原始的 4 位小数,修剪为 3... Number.precision = 3; console.log("精度:3"); console.log((0.8 + 0.2).decimal); // 1 console.log((0.08 + 0.02).decimal); // 0.1 console.log((0.008 + 0.002).decimal); // 0.01 console.log((0.0008 + 0.0002).decimal); // 0.001 Number.precision = 2; console.log("精度:2"); console.log((0.8 + 0.2).decimal); // 1 console.log((0.08 + 0.02).decimal); // 0.1 console.log((0.008 + 0.002).decimal); // 0.01 console.log((0.0008 + 0.0002).decimal); // 0 Number.precision = 1; console.log("精度:1"); console.log((0.8 + 0.2).decimal); // 1 console.log((0.08 + 0.02).decimal); // 0.1 console.log((0.008 + 0.002).decimal); // 0 console.log((0.0008 + 0.0002).decimal); // 0 Number.precision = 0; console.log("精度:0"); console.log((0.8 + 0.2).decimal); // 1 console.log((0.08 + 0.02).decimal); // 0 console.log((0.008 + 0.002).decimal); // 0 console.log((0.0008 + 0.0002).decimal); // 0
干杯!
((0.1*3)*1e14).decimal
通过首先将两个数字设为整数、执行表达式然后将结果除以返回小数位来解决它:
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)
看看Fixed-point arithmetic。如果您要操作的数字范围很小(例如货币),它可能会解决您的问题。我会将它四舍五入到几个十进制值,这是最简单的解决方案。
您不能用二进制浮点类型(ECMAScript 用来表示浮点值)精确地表示大多数小数。因此,除非您使用任意精度的算术类型或基于十进制的浮点类型,否则没有一个优雅的解决方案。例如,the Calculator app that ships with Windows now uses arbitrary precision arithmetic to solve this problem。
从我的角度来看,这里的想法是对 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
例如。
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;
}
52 * Math.log10(2)
因为它是签名的双精度数?结果仍然是 1e15
Math.round(num * 1e15) / 1e15
?
你是对的,原因是浮点数的精度有限。将有理数存储为两个整数的除法,在大多数情况下,您将能够存储数字而不会损失任何精度。在打印方面,您可能希望将结果显示为分数。通过我提出的表示,它变得微不足道。
当然,这对无理数没有多大帮助。但是您可能希望以它们会导致最少问题的方式优化您的计算(例如检测像 sqrt(3)^2)
.
<pedant>
实际上,OP 将其归结为不精确的浮点运算,这是错误的 </pedant>
我在使用 mod 3 时遇到了一个令人讨厌的舍入错误问题。有时当我应该得到 0 时,我会得到 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);
https://i.stack.imgur.com/gWE8D.png
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
避免在使用整数的操作期间处理浮点数
正如迄今为止投票最多的答案所述,您可以使用整数,这意味着对于您正在使用的每个小数,将所有因子乘以 10,然后将结果除以使用的相同数字。
例如,如果您使用 2 个小数,则在进行运算之前将所有因子乘以 100,然后将结果除以 100。
这是一个例子,Result1 是通常的结果,Result2 使用解决方案:
var Factor1="1110.7"; var Factor2="2220.2"; var 结果1=数字(因子1)+数字(因子2); var Result2=((数字(Factor1)*100)+(数字(Factor2)*100))/100; var Result3=(Number(parseFloat(Number(Factor1))+parseFloat(Number(Factor2))).toPrecision(2)); document.write("Result1: "+Result1+"
Result2: "+Result2+"
Result3: "+Result3);
第三个结果是显示在使用 parseFloat 时会发生什么,这在我们的案例中造成了冲突。
使用 Number(1.234443).toFixed(2);它将打印 1.23
function test(){
var x = 0.1 * 0.2;
document.write(Number(x).toFixed(2));
}
test();
console.log(9332654729891549)
实际上打印9332654729891548
(即减一!);P
...在2⁵²
=4,503,599,627,370,496
和2⁵³
=9,007,199,254,740,992
之间,可表示的数字是正好是整数。对于下一个范围,从2⁵³
到2⁵⁴
,所有内容都乘以2
,因此可表示的数字是 偶数、等。 相反,对于之前从2⁵¹
到2⁵²
的范围,间距是0.5
,等等。 这是由于简单地增加|减少基数|基数 2|二进制exponent 在/属于 64 位浮点值(这反过来解释了toPrecision()
对于0
和1
之间的值的很少记录的“意外”行为)。