"일꾼이 일을 잘하려면 먼저 도구를 갈고 닦아야 한다." - 공자, 『논어』.
첫 장 > 프로그램 작성 > JavaScript의 재정적 정확성: 한 푼도 잃지 않고 돈을 처리하세요

JavaScript의 재정적 정확성: 한 푼도 잃지 않고 돈을 처리하세요

2024-08-30에 게시됨
검색:691

Financial Precision in JavaScript: Handle Money Without Losing a Cent

빠르게 변화하는 금융 세계에서는 정확성이 가장 중요합니다. 반올림/정밀도 오류로 인해 백만 달러의 손실을 피하는 것이 항상 더 좋습니다.

돈에 관해서는 데이터 유형을 생각해 보세요.

이 글의 출발점은 돈이 바구니에 담긴 사과의 수를 세는 데 사용할 수 있는 평균적인 기본 숫자가 아니라는 사실을 깨닫는 것입니다. 10€에 10$를 곱하면 무엇을 얻게 되나요? 힘들죠… 낡은 재킷 주머니에서 기적의 1,546달러를 발견한 적이 있나요? 네, 알아요. 이것도 실제로는 불가능해요. 이 어리석은 예는 돈이 그 자체의 특별한 규칙을 갖고 있으며 단순한 숫자로만 모델링될 수 없다는 사실을 설명하기 위해 여기에 있습니다. 나는 그것을 깨달은 최초의 사람이 아니라는 것을 확신합니다. (아마도 당신은 나보다 훨씬 먼저 그것을 깨달았을 것입니다). 2002년에 프로그래머 Martin Fowler는 Patterns of Enterprise Application Architecture에서 특정 속성과 피연산자 규칙을 사용하여 돈을 표현하는 방법을 제안했습니다. 그에게 돈 데이터 유형에 필요한 두 가지 최소한의 실행 가능한 속성은 다음과 같습니다.

amount
currency

이 매우 기본적인 표현은 간단하지만 강력한 통화 모델을 구성하는 출발점이 될 것입니다.

금액, 표현 방법

금액은 확실히 특정 숫자입니다. 고정된 정밀도를 갖습니다(다시 말하지만 주머니에 4.376$을 넣을 수는 없습니다). 이 제약 조건을 존중하는 데 도움이 되는 표현 방법을 선택해야 합니다.

순진한 접근 방식, 기본 숫자 JavaScript 데이터 유형

스포일러 경고, 부동 소수점 숫자 표현의 어두운 세계에서 몇 센트(달러가 아닌 경우)가 사라지는 것을 보고 싶지 않다면 이는 확실히 좋은 생각이 아닙니다.

비용이 많이 드는 정밀도 오류

JavaScript 코딩 경험이 있다면 가장 간단한 계산이라도 처음에는 예상하지 못한 정밀도 오류가 발생할 수 있다는 것을 알고 계실 것입니다. 이 현상을 강조하는 가장 명확하고 잘 알려진 예는 다음과 같습니다.

0.1   0.2 !== 0.3 // true
0.1   0.2 // 0.30000000000000004

이 예가 완전히 이해되지 않는다면 JavaScript 기본 숫자 유형으로 작업할 때 발생할 수 있는 모든 문제가 있는 계산 결과에 대해 좀 더 자세히 알아보는 이 기사를 살펴보시기 바랍니다.

결과의 이 약간의 델타는 사용자에게 무해한 것처럼 보일 수 있지만(약 10^-16 크기) 중요한 금융 애플리케이션에서는 이러한 오류가 빠르게 퍼질 수 있습니다. 각 거래에 유사한 계산이 포함되는 수천 개의 계좌 간에 자금을 이체하는 것을 고려해보세요. 약간의 부정확성이 더해지고, 당신이 그것을 알기도 전에 당신의 재무제표는 수천 달러의 손실을 입게 됩니다. 그리고 솔직히 말해서, 돈과 관련하여 법적으로나 고객과 신뢰 관계를 구축하기 위해 오류가 허용되지 않는다는 데 우리 모두 동의할 수 있습니다.

왜 그런 오류가 발생합니까?

내 프로젝트 중 하나에서 문제가 발생했을 때 제가 스스로에게 던진 첫 번째 질문은  ?입니다. 문제의 원인은 JavaScript가 아니며 이러한 부정확성은 다른 최신 프로그래밍 언어(Java, C, Python,…)에도 영향을 미친다는 사실을 발견했습니다.

// In C
#include 

int main() {
    double a = 0.1;
    double b = 0.2;
    double sum = a   b;
    if (sum == 0.3) {
        printf("Equal\n");
    } else {
        printf("Not Equal\n"); // This block is executed
    }
    return 0;
}

// > Not equal
// In Java
public class doublePrecision {
    public static void main(String[] args) {

        double total = 0;
        total  = 5.6;
        total  = 5.8;
        System.out.println(total);
    }
}

// > 11.399999999999

사실 근본 원인은 부동 소수점 숫자를 표현하기 위해 이러한 언어에서 사용하는 표준, 즉 IEEE 754 표준에서 지정한 배정밀도(또는 단정밀도) 부동 소수점 형식에 있습니다.

IEEE 754 표준: 비트 표현 이야기

Javascript에서 기본 유형 숫자는 배정밀도 부동 소수점 숫자에 해당합니다. 즉, 숫자가 64비트로 인코딩되어 세 부분으로 나누어집니다.

  • 기호용 1비트
  • 지수용 11비트
  • 0에서 1 사이의 가수(또는 분수)에 대한 52비트

Financial Precision in JavaScript: Handle Money Without Losing a Cent

Financial Precision in JavaScript: Handle Money Without Losing a Cent

그런 다음 다음 공식을 사용하여 비트 표현을 10진수 값으로 변환해야 합니다.

Financial Precision in JavaScript: Handle Money Without Losing a Cent

배정밀도 부동 소수점 숫자 표현의 예(약 1/3):

0 01111111101 0101010101010101010101010101010101010101010101010101

= (-1)^0 x (1   2^-2   2^-4   2^-6   ...   2^-50   2^-52) x 2^(1021-1023)
= 0.333333333333333314829616256247390992939472198486328125
~ 1/3

이 형식을 사용하면 광범위한 값을 표현할 수 있지만 가능한 모든 숫자를 절대적인 정밀도로 표현할 수는 없습니다(0과 1 사이에서만 무한한 숫자를 찾을 수 있습니다...). 많은 숫자는 이진 형식으로 정확하게 표현될 수 없습니다. 첫 번째 예를 반복하면 0.1과 0.2의 문제입니다. 이중점 부동 표현은 이러한 값의 근사치를 제공하므로 이 두 가지 부정확한 표현을 추가하면 결과도 정확하지 않습니다.

가능한 해결책: 임의의 소수 연산

이제 기본 JavaScript 숫자 유형으로 금액을 처리하는 것이 나쁜 생각이라는 것을 완전히 확신했으므로(적어도 이에 대해 의심하기 시작하길 바랍니다) 1Billion$ 질문은 어떻게 진행해야 합니까 ? 해결책은 JavaScript에서 사용할 수 있는 강력한 고정 정밀도 산술 패키지 중 일부를 활용하는 것입니다. 예를 들어 Decimal.js(인기 있는 ORM Prisma에서 Decimal 데이터 유형을 나타내는 데 사용됨) 또는 Big.js.

이 패키지는 위에서 설명한 정밀도 오류를 제거하면서 계산을 수행할 수 있는 특수 데이터 유형을 제공합니다.

// Example using Decimal.js
const Decimal = require('decimal.js');
const a = new Decimal('0.1');
const b = new Decimal('0.2');
const result = a.plus(b);

console.log(result.toString()); // Output: '0.3'

이 접근 방식은 또 다른 이점을 제공합니다. 표현할 수 있는 최대 값을 대폭 확장하여 예를 들어 암호화폐를 다룰 때 매우 유용할 수 있습니다.
그것이 정말 강력하더라도 웹 애플리케이션에서 구현하기를 선호하는 것은 아닙니다. 정수 값만 처리하려면 스트라이프 전략을 적용하는 것이 더 쉽고 명확하다고 생각합니다.

전문가에게 배우기: 부동 소수점 없는 전략인 스트라이프

저희 테오도 핀테크는 실용주의를 중요시합니다! 우리는 업계에서 가장 성공한 기업으로부터 영감을 받는 것을 좋아합니다. 수십억 달러 규모의 결제 서비스 전문 기업으로 잘 알려진 Stripe은 부동 숫자가 아닌 정수를 사용하여 금액을 처리하기로 결정했습니다. 이를 위해 통화의 가장 작은 단위를 사용하여 금액을 표시합니다.

// 10 USD are represented by
{
 "amount": 1000,
 "currency": "USD"
}

통화 최소 단위… 일관성이 없습니다!

많은 분들이 이미 알고 계실 것 같습니다. 모든 통화에는 동일한 크기의 최소 단위가 없습니다. 대부분은 "2진수" 통화(EUR, USD, GBP)입니다. 이는 가장 작은 단위가 통화의 1/100임을 의미합니다. 그러나 일부는 "3진수" 통화(KWD)이거나 심지어 "0진수" 통화(JPY)입니다. (ISO4217 표준을 따르면 이에 대한 자세한 정보를 찾을 수 있습니다.) 이러한 차이를 처리하려면 가장 작은 단위로 표시된 금액을 해당 통화로 변환하는 곱셈 요소를 돈 데이터 표현에 통합해야 합니다.

Financial Precision in JavaScript: Handle Money Without Losing a Cent

금액을 표시하는 방법을 선택했습니다… 좋습니다. 그런데 이제 어떻게 반올림하나요?

이미 이해하셨을 것 같습니다. 기본 숫자, 타사 임의 정밀도 패키지 또는 정수로 작업할 수 있으며 계산을 통해 금전적으로 반올림해야 하는 부동 소수점 결과를 얻을 수 있습니다. 유한한 정밀도. 빠른 예는 결코 많지 않기 때문에 정수 값을 처리하고 8.5413%의 매우 정확한 이자율로 16,000달러의 대출을 계약했다고 가정해 보겠습니다. 그런 다음 16,000달러와 추가 금액
을 환불해야 합니다.

1600000 * 0.085413 // amount in cents
//Output in cents: 136660.8

핵심은 계산 후 금액의 반올림 처리를 적절하게 처리하는 것입니다. 대부분의 경우 세 가지 종류의 반올림 중에서 선택해야 합니다.

  • 기본 반올림: 가장 가까운 값으로 반올림하고 두 값의 중간이면 반올림합니다.
  • 뱅커 반올림: 가장 가까운 값으로 반올림하고 두 값의 중간에 있으면 숫자가 짝수이면 내림하고 숫자가 홀수이면 반올림합니다(이렇게 하면 많은 작업을 수행해야 할 때 수치적 안정성을 얻을 수 있습니다). 반올림)
  • 사용자 정의 반올림: 귀하가 사용하는 통화에 대한 특정 법률 및 처리 중인 사용 사례에 따라

반올림에 관해서는 실제로 "마법의 소스"가 없습니다. 상황에 따라 중재해야 합니다. 새로운 통화 및 새로운 반올림 사용 사례(환전, 돈 분할, 크레딧 이자율 등)를 다룰 때는 항상 법률을 확인하는 것이 좋습니다. 추가 문제를 방지하려면 즉시 규정을 따르는 것이 좋습니다. 예를 들어, 전환율과 관련하여 대부분의 통화에는 필요한 정밀도 및 반올림 규칙에 대한 규칙이 결정되어 있습니다(여기에서 EUR 전환율 규칙을 확인할 수 있습니다).

결론

이 문서는 JavaScript에서 금액을 처리할 수 있는 모든 기존 가능성을 철저하게 다루지 않으며 완전하고 완벽한 금액 데이터 모델을 제공하기 위한 것도 아닙니다. 저는 일관되고 회복력이 있으며 핀테크 업계의 주요 행위자들이 선택한 표현을 구현하기 위한 충분한 힌트와 지침을 제공하려고 노력했습니다. 나는 당신이 주머니에 돈 한 푼도 잊지 않고 향후 프로젝트에서 금액 계산을 수행할 수 있기를 바랍니다(그렇지 않으면 오래된 재킷을 살펴보는 것을 잊지 마십시오)!

릴리스 선언문 이 기사는 https://dev.to/benjamin_renoux/financial-precision-in-javaScript-money-without-losing-a-cent-1chc?1에서 재 인쇄되었습니다.
최신 튜토리얼 더>

부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.

Copyright© 2022 湘ICP备2022001581号-3