development

JavaScript에서 부동 소수점 숫자 정밀도를 처리하는 방법은 무엇입니까?

big-blog 2020. 2. 10. 22:20
반응형

JavaScript에서 부동 소수점 숫자 정밀도를 처리하는 방법은 무엇입니까?


다음과 같은 더미 테스트 스크립트가 있습니다.

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

이렇게하면 결과가 인쇄 0.020000000000000004되지만 인쇄하는 동안 0.02계산기가 사용됩니다. 내가 이해하는 한 이것은 부동 소수점 곱셈 정밀도의 오류 때문입니다.

그런 경우에 올바른 결과를 얻을 수있는 좋은 해결책이 0.02있습니까? 나는 toFixed반올림 과 같은 기능이 있거나 또 다른 가능성이 있다는 것을 알고 있지만 실제로는 절단과 반올림없이 정수를 인쇄하고 싶습니다. 당신 중 하나가 멋지고 우아한 해결책을 가지고 있는지 알고 싶었습니다.

물론 그렇지 않으면 약 10 자리 정도로 반올림합니다.


로부터 부동 소수점 가이드 :

이 문제를 피하려면 어떻게해야합니까?

그것은 당신이하고있는 계산의 종류에 달려 있습니다.

  • 특히 돈을 다룰 때 결과를 정확하게 합치려면 특별한 10 진수 데이터 유형을 사용하십시오.
  • 추가 소수 자릿수를 모두 표시하지 않으려면 결과를 표시 할 때 고정 소수점 이하 자릿수로 반올림하십시오.
  • 사용 가능한 10 진수 데이터 유형이없는 경우 다른 방법은 정수를 사용하는 것입니다 (예 : 돈 계산을 센트 단위로 수행). 그러나 이것은 더 많은 작업이며 몇 가지 단점이 있습니다.

첫 번째 요점은 특정한 정확한 십진수 동작 이 실제로 필요한 경우에만 적용됩니다 . 대부분의 사람들은 그것을 필요로하지 않으며 단지 1/3에서 발생하면 동일한 오류로 깜박이지 않을 것이라는 사실을 깨닫지 않고 1/10과 같은 숫자로 프로그램이 올바르게 작동하지 않는다는 것에 자극을 받았습니다.

첫 번째 요점이 실제로 적용되는 경우 전혀 우아하지는 않지만 불완전한 해결 방법을 제공하는 대신 문제를 해결하는 JavaScript에 BigDecimal for JavaScript를 사용 하십시오.


나는 Pedro Ladaria의 솔루션을 좋아하고 비슷한 것을 사용합니다.

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

Pedros 솔루션과 달리 이것은 0.999를 반올림합니다 ... 반복하며 가장 작은 자리에서 1을 더하기 / 빼기 정확합니다.

참고 : 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그렇다면 소수점 0.1233 자리와 0.122 자리 때문에 소수점 5 자리가 있음을 알 수 있습니다 . 따라서 JavaScript가 우리에게 숫자를 주면 0.014760000002정밀도를 잃을 염려없이 소수점 5 자리로 안전하게 반올림 할 수 있습니다.


sprintfJavaScript 에 대한 구현을 찾고 있으므로 작은 오류가있는 부동 소수점 (이진 형식으로 저장되어 있기 때문에)을 원하는 형식으로 작성할 수 있습니다.

javascript-sprintf를 시도 하면 다음과 같이 호출됩니다.

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

소수점 이하 두 자리의 부동 소수점으로 숫자를 인쇄합니다.

부동 소수점을 주어진 정밀도로 반올림하기 위해 더 많은 파일을 포함하지 않으려는 경우 표시 목적으로 Number.toFixed ()사용할 수도 있습니다 .


BigNumber.js 가 내 요구를 충족시키는 것으로 나타 났습니다 .

임의 정밀도 10 진수 및 비소수 산술을위한 JavaScript 라이브러리.

문서 가 훌륭하고 저자는 피드백에 부지런히 응답합니다.

동일한 저자는 2 개의 다른 유사한 라이브러리를 가지고 있습니다 :

Big.js

임의 정밀도 10 진수 산술을위한 작고 빠른 JavaScript 라이브러리입니다. bignumber.js의 여동생.

Decimal.js

JavaScript에 대한 임의 정밀도 10 진수 유형입니다.

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

당신은 당신이 실제로 원하는 소수 자릿수에 대해 생각해야합니다-케이크를 먹을 수 없으며 너무 먹습니다 :-)

추가 오류가 발생할 때마다 수치 오류가 누적되며 일찍 중단하지 않으면 점점 커질 것입니다. 깨끗하게 보이는 결과를 제공하는 숫자 라이브러리는 모든 단계에서 마지막 2 자리를 간단히 잘라냅니다. 숫자 보조 프로세서도 같은 이유로 "일반"및 "전체"길이를 갖습니다. Cuf-off는 프로세서에서는 저렴하지만 스크립트에서는 pov (...)를 곱하고 나누고 사용하는 것이 매우 비쌉니다. 좋은 수학 라이브러리는 floor (x, n)을 제공하여 컷오프를 수행합니다.

따라서 최소한 pov (10, n)을 사용하여 전역 var / constant를 만들어야합니다. 즉, 필요한 정밀도를 결정했음을 의미합니다.

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

결과를 표시하고 if-s를 수행하지 않는다고 가정하면 마지막에 수학을 계속하고 끝낼 수 있습니다. 그렇게 할 수 있다면 .toFixed (...)가 더 효율적일 수 있습니다.

if-s / comparisons를하고 있고 잘라 내고 싶지 않으면 보통 eps라고 불리는 작은 상수가 필요합니다.이 상수는 최대 예상 오차보다 소수점 이하 1 자리입니다. 컷오프가 소수점 이하 두 자릿수라고 가정하면 eps는 마지막 3 번째 자리 (1 번째 중요도)에서 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

놀랍게도,이 기능은 다른 기능과 유사하지만 아직 게시되지 않았습니다. Math.round ()에 대한 MDN 웹 문서에서 가져온 것입니다. 간결하고 다양한 정밀도를 허용합니다.

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

업데이트 : 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 페이지를 업데이트하여 더 나은 솔루션을 제공 할 수 있습니다.


얻은 결과는 다른 언어, 프로세서 및 운영 체제의 부동 소수점 구현에서 정확하고 일관성이 있습니다. 플로트가 실제로 두 배 이상인 경우의 부정확성 수준 만 변경됩니다.

이진 부동 소수점의 0.1은 소수점의 1/3과 같습니다 (즉, 0.3333333333333 ... 영원히). 그것을 처리하는 정확한 방법은 없습니다.

플로트를 처리하는 경우 항상 작은 반올림 오류가 예상되므로 항상 표시된 결과를 적절한 것으로 반올림해야합니다. 모든 계산이 프로세서의 기본 바이너리에 있기 때문에 매우 빠르고 강력한 산술을 얻을 수 있습니다.

대부분의 경우 솔루션은 고정 소수점 산술로 전환하지 않습니다. 주로 정확도가 필요하지 않은 시간의 99 %가 훨씬 느리기 때문입니다. 금융 거래와 같이 그 정도의 정확성이 필요한 것을 다루는 경우 Javascript는 어쨌든 사용하기에 가장 좋은 도구가 아닙니다 (고정 소수점 유형을 적용하려는 경우 정적 언어가 더 좋습니다) ).

우아한 솔루션을 찾고 있다면 이것이 두렵습니다. 수레는 빠르지 만 작은 반올림 오류가 있습니다-결과를 표시 할 때 항상 합리적인 것으로 반올림하십시오.


이를 피하려면 부동 소수점 대신 정수 값을 사용해야합니다. 따라서 * 100의 값으로 2 개의 위치에서 정밀하게 작업하려면 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((a-b).toFixed(2));

c = 0.1;

일반적인 용도로는이 동작이 허용 될 수 있습니다.
이러한 부동 소수점 값을 비교하여 적절한 조치를 결정할 때 문제가 발생합니다.
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


문제

부동 소수점은 모든 소수 값을 정확하게 저장할 수 없습니다. 따라서 부동 소수점 형식을 사용하면 입력 값에 항상 반올림 오류가 발생합니다. 물론 입력의 오류는 출력의 오류를 초래합니다. 불연속 기능 또는 작동 자의 경우 기능 또는 작동자가 불연속 인 지점 주변에서 출력에 큰 차이가있을 수 있습니다.

부동 소수점 값의 입력 및 출력

따라서 부동 소수점 변수를 사용할 때는 항상이를 알고 있어야합니다. 부동 소수점을 사용한 계산에서 원하는 출력은 항상이를 염두에두고 표시하기 전에 형식화 / 조건화되어야합니다.
연속 함수와 연산자 만 사용하는 경우 원하는 정밀도로 반올림하는 경우가 많습니다 (잘리지 않음). float를 문자열로 변환하는 데 사용되는 표준 형식 지정 기능이 일반적으로이 작업을 수행합니다.
반올림으로 인해 전체 오차가 원하는 정밀도의 절반 이상이 될 수있는 오차가 발생하므로 예상되는 입력 정밀도와 원하는 출력 정밀도를 기반으로 출력을 수정해야합니다. 당신은해야

  • 입력을 예상 정밀도로 반올림하거나 더 높은 정밀도로 값을 입력 할 수 없도록합니다.
  • 반올림 / 포맷하기 전에 원하는 정밀도의 1/4보다 작고 입력 및 반올림 오차로 인한 최대 예상 오차보다 큰 출력에 작은 값을 추가하십시오. 이것이 가능하지 않은 경우 사용 된 데이터 유형의 정밀도 조합이 계산에 필요한 출력 정밀도를 제공하기에 충분하지 않습니다.

이 두 가지 작업은 일반적으로 수행되지 않으며 대부분의 경우 이러한 작업을 수행하지 않아 발생하는 차이가 너무 작아 대부분의 사용자에게 중요하지는 않지만 이미 해당 수정없이 사용자가 출력을 수락하지 않은 프로젝트가있었습니다.

이산 함수 또는 연산자 (모듈라와 같은)

개별 연산자 또는 기능이 관련된 경우 출력이 예상대로 이루어 지도록 추가 수정이 필요할 수 있습니다. 반올림 전에 반올림 및 작은 수정을 추가해도 문제를 해결할 수 없습니다.
이산 함수 또는 연산자를 적용한 직후에 중간 계산 결과에 대한 특별한 점검 / 수정이 필요할 수 있습니다. 특정 경우 ( 모듈러스 연산자) 에 대한 질문에 대한 대답 은 모듈러스 연산자가 Javascript에서 소수를 반환하는 이유는 무엇입니까?

더 나은 문제를 피하십시오

반올림 오류없이 예상 입력을 저장할 수있는 이와 같은 계산에 데이터 유형 (정수 또는 고정 소수점 형식)을 사용하여 이러한 문제를 피하는 것이 종종 더 효율적입니다. 그 예로 금융 계산에 부동 소수점 값을 사용해서는 안됩니다.


고정 소수점 산술을 살펴보십시오 . 조작하려는 숫자 범위가 작은 경우 (예 : 통화) 문제를 해결할 수 있습니다. 소수로 소수를 반올림하여 가장 간단한 해결책입니다.


내 칠레 식 산술 라이브러리를 사용해보십시오 . 여기에서 볼 수 있습니다 . 나중 버전을 원하면 얻을 수 있습니다.


이진 부동 소수점 유형 (ECMAScript에서 부동 소수점 값을 나타내는 데 사용)을 사용하여 대부분의 소수를 정확하게 표현할 수 없습니다. 따라서 임의의 정밀 산술 유형 또는 10 진수 기반 부동 소수점 유형을 사용하지 않으면 우아한 솔루션이 없습니다. 예를 들어, Windows와 함께 제공되는 계산기 앱은 이제이 문제를 해결하기 위해 임의의 정밀 산술을 사용합니다 .


여기에 이미지 설명을 입력하십시오

    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

mod 3에서 불쾌한 반올림 오류 문제가 발생했습니다. 때로는 0을 받아야 할 때 .000 ... 01을 얻습니다. <= .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);

사용하다

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

우아하지는 않지만 작업을 수행합니다 (후행 0 제거)

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

당신이 맞습니다. 그 이유는 부동 소수점 숫자의 정밀도가 제한되어 있기 때문입니다. 유리수를 두 정수로 나눈 값을 저장하면 대부분의 경우 정밀한 손실없이 숫자를 저장할 수 있습니다. 인쇄 할 때 결과를 분수로 표시 할 수 있습니다. 내가 제안한 표현으로 그것은 사소한 것이된다.

물론 그것은 비이성적 인 숫자에는별로 도움이되지 않습니다. 그러나 가장 적은 문제 (예 :와 같은 상황 감지)를 유발하는 방식으로 계산을 최적화 할 수 있습니다 sqrt(3)^2).


사용 번호 (1.234443) .toFixed (2); 1.23을 인쇄합니다

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

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; 구성 옵션; NaN; 무한대; 소수점 이하 자릿수로 지정된 정밀도; 분할에만 적용되는 정밀도; 기본 접두사.

decimal.js :베이스 2-64; 구성 옵션; NaN; 무한대; 정수가 아닌 제곱, exp, ln, log; 유효 숫자로 지정된 정밀도; 항상 적용되는 정밀도; 난수.

자세한 비교 링크


이것은 나를 위해 작동합니다 :

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


두 개의 float 값을 추가하는 동안 정확한 값을 제공하지 않으므로 비교하기 위해 특정 숫자로 고정해야합니다.

console.log ((parseFloat (0.1) + parseFloat (0.2)). toFixed (1) == parseFloat (0.3) .toFixed (1));

참고 URL : https://stackoverflow.com/questions/1458633/how-to-deal-with-floating-point-number-precision-in-javascript



반응형