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

2020/09/23 01:01 · javascript ·  · 0评论

我有以下虚拟测试脚本:

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

这将打印结果,0.020000000000000004而仅打印结果0.02(如果使用计算器)。据我了解,这是由于浮点乘法精度的错误。

有没有人有一个好的解决方案,这样在这种情况下我可以获得正确的结果0.02我知道还有类似的函数,toFixed或者四舍五入是另一种可能性,但是我真的想在不进行任何四舍五入的情况下打印出完整的数字。只是想知道你们中的一个人是否有一些不错的,优雅的解决方案。

当然,否则我将四舍五入到大约10位数字。

浮点指南

我应该怎么做才能避免这个问题?

这取决于您正在执行哪种计算。

  • 如果确实需要精确地将结果相加,特别是在使用金钱时,请使用特殊的十进制数据类型。
  • 如果您只是不想看到所有这些多余的小数位:只需在显示结果时将结果的格式四舍五入到固定的小数位数即可。
  • 如果没有可用的十进制数据类型,则替代方法是使用整数,例如,完全用美分进行货币计算。但这是更多的工作,并且有一些缺点。

请注意,只有在您确实需要特定的精确十进制行为时,第一点才适用大多数人并不需要它,只是因为他们的程序无法正确处理1/10之类的数字而感到恼火,却没有意识到如果1/3出现相同的错误,他们甚至都不会眨眼。

如果第一点确实适用于您,请对JavaScript使用BigDecimal,这一点都不优雅,但实际上可以解决问题,而不是提供不完善的解决方法。

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

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

与Pedros解决方案不同的是,此运算将四舍五入为0.999 ...重复,并且精确到最低有效数字的正负一一。

注意:处理32或64位浮点数时,应使用toPrecision(7)和toPrecision(15)以获得最佳结果。有关此问题的信息,请参见此问题

对于数学上的偏爱: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.122 个小数位因此,如果JavaScript为我们提供了一个数字,例如0.014760000002我们可以安全地舍入到小数点后第五位,而不必担心失去精度。

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

尝试javascript-sprintf,您将这样称呼它:

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

以小数点后两位小数的形式打印您的数字。

如果您不想仅出于浮点舍入到给定精度的目的而添加更多文件,也可以将 Number.toFixed()用于显示目的。

我发现BigNumber.js符合我的需求。

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

它具有良好的文档,作者非常努力地回应反馈。

同一位作者有2个其他类似的库:

Big.js

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

Decimal.js

JavaScript的任意精度的十进制类型。

这是一些使用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>
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); 
}

令人惊讶的是,此功能尚未发布,尽管其他功能也有类似的变化。它来自MDN网络文档中的Math.round()。简洁明了,并允许不同的精度。

function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}

console.log(precisionRound(1234.5678,1)); //预期输出:1234.6

console.log(precisionRound(1234.5678,-1)); //预期输出: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 function
function 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'>

更新:Aug / 20/2019刚刚注意到此错误。我相信这是由于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页面,因此也许有人可以提供更好的解决方案。

您只需要确定自己实际想要多少个小数位-不能再吃蛋糕了:-)

每次执行进一步的操作时,都会累积数值误差,如果您不尽早将其切断,它将不断增加。数字库显示的结果看起来很干净,每一步都将最后两位数字截断,数字协处理器出于相同的原因也具有“正常”和“完全”长度。Cuf-off对处理器而言很便宜,但对您而言,在脚本中非常昂贵(乘法,除法和使用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 /比较,并且不希望减少,则还需要一个小的常数,通常称为eps,该常数比最大期望误差高一个小数位。假设您的截止数是最后两位小数-那么您的eps在最后一位的第三位(最低有效位第三),您可以使用它来比较结果是否在预期的eps范围内(0.02 -eps <0.1 * 0.2 <0.02 + eps)。

您可以使用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;

phpjs.org上的round()函数很好地工作:http ://phpjs.org/functions/round

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

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

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

非常非常简单))

请注意,对于一般用途,此行为可能是可以接受的。

比较那些浮点值以确定适当的操作时会出现问题。


随着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.1的二进制浮点数就像1/3的十进制数(即永远是0.3333333333333 ...),没有精确的处理方法。

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

大多数情况下,解决方案不是切换到定点算法,主要是因为它要慢得多,而且99%的时间中您根本不需要精度。如果您要处理的确需要这种级别的准确性(例如财务交易),则Javascript可能不是最佳的使用工具(因为您要强制使用定点类型,因此静态语言可能会更好) )。

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

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

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

问题

浮点不能完全存储所有十进制值。因此,当使用浮点格式时,在输入值上将始终存在舍入误差。输入上的错误当然会导致输出上的错误。如果是离散函数或运算符,则在函数或运算符离散点附近的输出上可能会有很大差异。

浮点值的输入和输出

因此,在使用浮点变量时,您应该始终意识到这一点。考虑到要从浮点计算中获得的任何输出,都应在格式化之前进行格式化/调整。

当仅使用连续函数和运算符时,通常会舍入到所需的精度(不要截断)。
用于将浮点数转换为字符串的标准格式设置功能通常会为您完成此操作。


由于舍入会增加一个误差,该误差可能导致总误差大于所需精度的一半,因此应根据预期的输入精度和所需的输出精度对输出进行校正。
你应该

  • 将输入取整到期望的精度,或者确保不能以更高的精度输入任何值。
  • 在对输出进行四舍五入/格式化之前,将一个较小的值添加到输出中,该值小于或等于所需精度的1/4,并且大于由输入和计算期间的舍入误差引起的最大预期误差。如果不可能,则所用数据类型的精度的组合不足以为计算提供所需的输出精度。

通常不会完成这两件事,并且在大多数情况下,由于不做而导致的差异太小,对于大多数用户而言并不重要,但是我已经有一个项目,如果不进行这些更正,用户将不会接受输出。

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

当涉及离散运算符或函数时,可能需要进行其他更正以确保输出符合预期。四舍五入并在四舍五入之前添加小的更正不能解决问题。

在应用离散函数或运算符之后,可能需要立即对中间计算结果进行特殊检查/校正。
对于特定情况(模运算符),请参阅我对以下问题的回答:
为什么模数运算符会在JavaScript中返回小数?

最好避免出现问题

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

看一下定点算法如果您要操作的数字范围较小(例如,货币),则可能会解决您的问题。我会将其四舍五入为几个十进制值,这是最简单的解决方案。

尝试一下我的Chiliadic算术库,您可以在这里看到如果您想要更高的版本,我可以帮助您。

您不能完全用二进制浮点类型(这是ECMAScript用来表示浮点值)来精确表示大多数小数部分。因此,除非您使用任意精度的算术类型或基于十进制的浮点类型,否则没有一个好的解决方案。例如,Windows附带的Calculator应用程序现在使用任意精度算术来解决此问题

在此处输入图片说明

    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

没错,原因是浮点数的精度有限。将有理数存储为两个整数的除数,在大多数情况下,您将能够存储数字而不会造成任何精度损失。在打印时,您可能希望将结果显示为分数。通过我提出的代表性,它变得微不足道。

当然,这对于无理数不会有太大帮助。但是,您可能希望以它们引起最少问题的方式来优化计算(例如,检测诸如的情况)sqrt(3)^2)

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

使用编号(1.234443).toFixed(2); 它将打印1.23

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

decimal.jsbig.jsbignumber.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为基数;配置选项;NaN; 无限; 小数点后指定的精度;精度仅适用于除法;基本前缀。

小数.js:以2-64为基数;配置选项;NaN; 无限; 非整数幂,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); // 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("Precision: 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("Precision: 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("Precision: 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

干杯!

从我的角度来看,这里的想法是对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

例如。

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

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
本文地址:http://javascript.askforanswer.com/ruhechulijavascriptzhongdefudianshujingdu.html
文章标签: ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!