크로스 플랫폼 코드를 만들기 위해 JavaScript로 간단한 금융 애플리케이션을 개발하고 싶습니다. 필요한 계산에는 복리와 비교적 긴 십진수가 포함됩니다. JavaScript를 사용하여 이러한 유형의 수학을 수행 할 때 피해야 할 실수가 무엇인지 알고 싶습니다. 가능하다면!
크로스 플랫폼 코드를 만들기 위해 JavaScript로 간단한 금융 애플리케이션을 개발하고 싶습니다. 필요한 계산에는 복리와 비교적 긴 십진수가 포함됩니다. JavaScript를 사용하여 이러한 유형의 수학을 수행 할 때 피해야 할 실수가 무엇인지 알고 싶습니다. 가능하다면!
답변:
십진수 값을 100으로 조정하고 모든 통화 값을 전체 센트로 표시해야합니다. 이는 부동 소수점 논리 및 산술 문제 를 방지하기위한 것입니다 . JavaScript에는 10 진수 데이터 유형이 없습니다. 유일한 숫자 데이터 유형은 부동 소수점입니다. 따라서 일반적으로 돈 2550
을 25.50
달러 대신 센트로 처리하는 것이 좋습니다 .
JavaScript에서 고려하십시오.
var result = 1.0 + 2.0; // (result === 3.0) returns true
그러나:
var result = 0.1 + 0.2; // (result === 0.3) returns false
표현식은를 0.1 + 0.2 === 0.3
반환 false
하지만 다행스럽게도 부동 소수점의 정수 산술은 정확하므로 10 진수 표현 오류는 스케일링 1 로 피할 수 있습니다 .
실수 세트는 무한하지만 한정된 수 (정확히 18,437,736,874,454,810,627) 만 JavaScript 부동 소수점 형식으로 정확하게 표현 될 수 있습니다. 따라서 다른 숫자의 표현은 실제 숫자 2 의 근사치입니다 .
1 Douglas Crockford : JavaScript : 좋은 부분 : 부록 A-끔찍한 부분 (페이지 105) .
2 David Flanagan : JavaScript : The Definitive Guide, Fourth Edition : 3.1.3 부동 소수점 리터럴 (페이지 31) .
3000.57 + 0.11 === 3000.68
반환 하기 때문에 false
.
모든 값을 100으로 조정하는 것이 해결책입니다. 손으로하는 것은 아마도 쓸모가 없을 것입니다. 그렇게 해주는 라이브러리를 찾을 수 있기 때문입니다. ES6 애플리케이션에 적합한 기능적 API를 제공하는 moneysafe를 권장합니다.
const { in$, $ } = require('moneysafe');
console.log(in$($(10.5) + $(.3)); // 10.8
https://github.com/ericelliott/moneysafe
Node.js와 브라우저에서 모두 작동합니다.
in$, $
값 이름은 이전에 패키지를 사용하지 않은 사람에게는 모호합니다. 나는 그렇게 이름을 짓는 것이 Eric의 선택이라는 것을 알고 있지만, 여전히 import / destructured require 문에서 이름을 바꾸는 것으로 충분하다고 생각합니다.
Money$afe has not yet been tested in production at scale.
. 사람이 그 자신의 사용 사례에 적합한의 경우 고려할 수 있도록 그냥 그 밖을 가리키는
소수 두 자릿수로 인해 "정확한"재무 계산과 같은 것은 없지만 더 일반적인 문제입니다.
JavaScript에서는 모든 값을 100으로 확장 Math.round()
하고 분수가 발생할 때마다 사용할 수 있습니다.
객체를 사용하여 숫자를 저장하고 프로토 타입 valueOf()
메서드에 반올림을 포함 할 수 있습니다 . 이렇게 :
sys = require('sys');
var Money = function(amount) {
this.amount = amount;
}
Money.prototype.valueOf = function() {
return Math.round(this.amount*100)/100;
}
var m = new Money(50.42355446);
var n = new Money(30.342141);
sys.puts(m.amount + n.amount); //80.76569546
sys.puts(m+n); //80.76
이렇게하면 Money 개체를 사용할 때마다 소수점 이하 두 자리로 반올림하여 표시됩니다. 반올림되지 않은 값은 여전히를 통해 액세스 할 수 있습니다 m.amount
.
원하는 Money.prototype.valueOf()
경우 자체 반올림 알고리즘을으로 빌드 할 수 있습니다 .
Money(0.1)
JavaScript 렉서가 소스에서 문자열 "0.1"을 읽고 이진 부동 소수점으로 변환 한 다음 이미 의도하지 않은 반올림을 수행했음을 의미합니다. 문제는 정밀도가 아닌 표현 (이진 대 십진수)에 관한 것 입니다.
use decimaljs ... 문제의 가혹한 부분을 해결하는 아주 좋은 라이브러리입니다 ...
모든 작업에 사용하십시오.
문제는 부동 소수점 계산의 부정확성으로 인해 발생합니다. 이 문제를 해결하기 위해 반올림 만 사용하는 경우 곱하고 나눌 때 더 큰 오류가 발생합니다.
해결책은 다음과 같습니다. 설명은 다음과 같습니다.
이를 이해하려면이 뒤에있는 수학에 대해 생각해야합니다. 1/3과 같은 실수는 끝이 없기 때문에 10 진수 값으로 수학에서 표현할 수 없습니다 (예 : .333333333333333 ...). 소수의 일부 숫자는 이진수로 올바르게 표현할 수 없습니다. 예를 들어 0.1은 제한된 자릿수로 이진수로 올바르게 표현 될 수 없습니다.
자세한 설명은 여기를 참조 하십시오 : http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
솔루션 구현을 살펴보십시오. http://floating-point-gui.de/languages/javascript/
인코딩의 이진 특성으로 인해 일부 십진수는 완벽한 정확도로 표현할 수 없습니다. 예를 들면
var money = 600.90;
var price = 200.30;
var total = price * 3;
// Outputs: false
console.log(money >= total);
// Outputs: 600.9000000000001
console.log(total);
순수한 자바 스크립트를 사용해야한다면 모든 계산에 대한 해결책을 생각해야합니다. 위 코드의 경우 소수를 정수로 변환 할 수 있습니다.
var money = 60090;
var price = 20030;
var total = price * 3;
// Outputs: true
console.log(money >= total);
// Outputs: 60090
console.log(total);
훌륭한 문서와 함께 재무 계산을위한 전용 라이브러리가 있습니다. Finance.js
불행히도 지금까지 모든 답변은 모든 통화가 100 개의 하위 단위를 가지고 있지 않다는 사실을 무시합니다 (예 : 센트는 미국 달러 (USD)의 하위 단위). 이라크 디나르 (IQD)와 같은 통화에는 1000 개의 하위 단위가 있습니다. 이라크 디나르에는 1000 개의 필이 있습니다. 일본 엔 (JPY)에는 하위 단위가 없습니다. 따라서 "정수 연산을 위해 100을 곱한다"는 것이 항상 정답은 아닙니다.
또한 통화 계산을 위해 통화를 추적해야합니다. 인도 루피 (INR)에 미국 달러 (USD)를 추가 할 수 없습니다 (먼저 하나를 다른 루피로 변환하지 않고).
JavaScript의 정수 데이터 유형으로 표현할 수있는 최대량에도 제한이 있습니다.
화폐 계산에서 돈은 유한 한 정밀도 (일반적으로 소수점 이하 0 ~ 3 점)를 가지며 특정 방식으로 반올림해야합니다 (예 : "정상"반올림 대 은행가의 반올림). 수행 할 반올림 유형은 관할권 / 통화에 따라 다를 수도 있습니다.
자바 스크립트에서 돈을 처리하는 방법 은 관련 요점에 대한 좋은 토론을 가지고 있습니다.
내 검색에서 나는 금전적 계산과 관련된 많은 문제 를 해결 하는 dinero.js 라이브러리를 발견했다 . 아직 프로덕션 시스템에서 사용하지 않았으므로 정보에 입각 한 의견을 제시 할 수 없습니다.