또 다른 금액 계산 방법

Java로 금액을 취급하는 경우, 「BigDecimal을 사용할 수 있다」라고 하는 것이 정석이 되어 있습니다.
  • h tp // w w. s에서 멋지다. 네 t / 미야카와 타쿠 / 비 g로 시마 l
  • ht tp // // 트얀 k. bgs포 t. jp/2014/10/비g로 해서 ㅔぁ. HTML

  • 그렇지 않으면 친숙한 반올림 오차가 발생합니다.

    ( @ 미야카와 타쿠의 "금 계정을위한 BigDecimal 그리고 Money and Currency API"에서)

    그러나
    new BigDecimal(0.1)
    

    같은 초기화되면 원래도 아이도 없고, 여러 번 반올림 처리를 통과하면 역시 오차를 낳을 가능성이 있습니다.

    그 밖에 좋은 방안은 없는 것일까요…

    Ratio 타입



    대부분의 Lisp은 분수형을 가지고 있다고 합니다. 조사해 보겠습니다.

    Clojure
    user=> (/ 22 7)
    22/7
    

    CommonLisp
    [1]> (/ 22 7)
    22/7
    

    gauche
    gosh> (/ 22 7)
    22/7
    

    MatzLisp
    irb(main):001:0> 22r / 7
    => (22/7)
    

    아무도 나눗셈은 분수로 취급합니다. 따라서, 반올림 처리는 계산 도중에 신경 쓸 필요가 없고, 마지막 마지막으로 1회 하면 된다.

    Clojure
    user=> (* 1000 (- 1 (/ 7 100)))
    930N
    

    ???「돈을 받는다면 Lisp이다!!」

    Java에서의 Ratio 구현



    Ratio 타입의 구현은 간단합니다.
    public class Ratio {
        public long numerator;
        public long denominator;
    
        public Ratio(long numerator, long denominator) {
            this.numerator = numerator;
            this.denominator = denominator;
            reduce();
        }
    
        public Ratio plus(Ratio x) {
            if (x.denominator == this.denominator) {
                this.numerator += x.numerator;
            } else {
                long d = this.denominator * x.denominator;
                this.numerator = this.numerator * x.denominator + x.numerator * this.denominator;
                this.denominator = d;
            }
    
            reduce();
            return this;
        }
    
        public Ratio minus(Ratio x) {
            if (x.denominator == this.denominator) {
                this.numerator -= x.numerator;
            } else {
                long d = this.denominator * x.denominator;
                this.numerator = this.numerator * x.denominator - x.numerator * this.denominator;
                this.denominator = d;
            }
            reduce();
            return this;
        }
    
        public Ratio multiply(Ratio x) {
            this.numerator *= x.numerator;
            this.denominator *= x.denominator;
            reduce();
            return this;
        }
    
        public Ratio devide(Ratio x) {
            this.numerator *= x.denominator;
            this.denominator *= x.numerator;
            reduce();
            return this;
        }
    
        public long quotient() {
            return numerator / denominator;
        }
    
        public Ratio remainder() {
            return new Ratio(numerator % denominator, denominator);
        }
    
        @Override
        public boolean equals(Object anothor) {
            return anothor != null
                    && anothor instanceof Ratio
                    && ((Ratio) anothor).numerator == numerator
                    && ((Ratio) anothor).denominator == denominator;
    
        }
    
        private void reduce() {
            long gcd = calcGcd(numerator, denominator);
            numerator /= gcd;
            denominator /= gcd;
        }
    
        private long calcGcd(long a, long b) {
            if (b == 0) return a;
            return calcGcd(b, a%b);
        }
    }
    

    (실제 사용할 때는 Number 형을 상속하거나 Comparable을 구현하는 것이 좋다고 생각합니다)

    아래와 같은 단점에 해당하는 경우는 numeratordenominator 의 형태를 BigInteger 로 해 주세요.

    단점



    Example의 코드는 매회 약분하고 있습니다만, 되풀이해 계산하는 경우, 약분의 오버헤드가 조금 걸립니다. 한층 더 복리 계산과 같이 같은 금리를 곱하는 경우, 분자·분모가 큰 숫자가 되어 메모리를 먹는 경우가 있습니다…

    장점



    앞에서 설명한 단점을 극복할 수 있다면 계산 과정에서의 오차를 신경 쓸 필요가 없어지므로 안심하고 사용할 수 있게 됩니다.

    요약



    BigDecimal을 사용해도 문제 일어날 것 같은 프로젝트는, 분수형을 베이스로 한 금액형을 만드는 것을 검토하면 좋은 것이 아닐까요.
    그렇지 않으면 Lisp을 사용합시다.

    좋은 웹페이지 즐겨찾기