bouzuya.hatenablog.com

ぼうずやのにっき

Java BigDecimal の落とし穴 / ABC121 を解いた

2019-10-12 にも書いていたのだけど、 java.math.BigDecimal の equals および toString をまた踏んだのでメモしておく。

Java には java.math.BigDecimal という任意精度の 10 進数を扱うクラスがある。 https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/math/BigDecimal.html

お金などを浮動小数点数で扱うと端数処理などの誤差が危ないのでこういったクラスを使うことが多い……はず。

で落とし穴っぽい動きがあるのでメモしておく。これらはドキュメントに明記してあるので読まないほうが悪い (が、ハマりがち) 。

java.math.BigDecimal は scale が異なると equals で false を返す。 "1000" からつくったものと "1000.0" からつくったものとでは scale が異なるため false になる。

回避策として stripTrailingZeros を呼び出して表現 (scale) の違いをなくしておく方法がある。

java.math.BigDecimal の toString は指数表記が使われる。 "1000" からつくったものは "1000" になるのだけど stripTrailingZeros を呼び出すと "1E+3" になる。

面倒なので桁数を制限・↑の差異などを吸収するよう wrap して使うのが良さそうだと思う。

また通貨は JSR 354: Money and Currency API https://jcp.org/en/jsr/detail?id=354というものもがあるらしい。メンテナンス状態になってしまっているので使う場合には注意が要る。

package com.example;

import org.junit.jupiter.api.Test;

import java.math.BigDecimal;

import static org.assertj.core.api.Assertions.assertThat;

class BigDecimalTest {
    @Test
    void test() {

        // 1000 != 1000.0
        assertThat(new BigDecimal("1000"))
                .isNotEqualTo(new BigDecimal("1000.0"));
        assertThat(new BigDecimal("1000").stripTrailingZeros())
                .isEqualTo(new BigDecimal("1000.0").stripTrailingZeros());

        // toString()
        assertThat(new BigDecimal("1000").toString())
                .isEqualTo("1000");
        assertThat(new BigDecimal("1000.0").toString())
                .isEqualTo("1000.0");
        assertThat(new BigDecimal("1000").stripTrailingZeros().toString())
                .isEqualTo("1E+3");
        assertThat(new BigDecimal("1000.0").stripTrailingZeros().toString())
                .isEqualTo("1E+3");

        // toPlainString()
        assertThat(new BigDecimal("1000").toPlainString())
                .isEqualTo("1000");
        assertThat(new BigDecimal("1000.0").toPlainString())
                .isEqualTo("1000.0");
        assertThat(new BigDecimal("1000").stripTrailingZeros().toPlainString())
                .isEqualTo("1000");
        assertThat(new BigDecimal("1000.0").stripTrailingZeros().toPlainString())
                .isEqualTo("1000");

    }
}

ABC121 : AtCoder Beginner Contest 121 の A, B, C, D を解いた。


今日のコミット。