Help us understand the problem. What is going on with this article?

もう一つの金額計算のやり方

More than 3 years have passed since last update.

Javaで金額を扱う場合、「BigDecimalを使え」というのが定石になっています。

さもなくば、お馴染みの丸め誤差が発生します。
image
(@miyakawatakuさんの「金勘定のためのBigDecimalそしてMoney and Currency API」より)

しかし、

new BigDecimal(0.1)

みたいな初期化されると元も子もありませんし、複数回丸め処理を通っちゃうと、やはり誤差を産む可能性があります。

他によい案はないものでしょうか…

Ratio型

http://stackoverflow.com/questions/7109741/rounding-numbers-to-n-decimals-in-clojure

たいていの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を使いましょう。

kawasima
Clojure関連のことをブログがわりに書き綴ります。 ※ここでの発言はシステムエンジニアを代表するものであって、所属する組織は二の次です。
https://github.com/kawasima/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした