2022-05-18 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 を解いた。
- A - White Cells
https://atcoder.jp/contests/abc121/tasks/abc121_a
- 提出: https://atcoder.jp/contests/abc121/submissions/31781350
- すべての行・列から塗りつぶしている行・列を除くので
(H - h) * (W - w)
- B - Can you solve this?
https://atcoder.jp/contests/abc121/tasks/abc121_b
- 提出: https://atcoder.jp/contests/abc121/submissions/31781412
- 素直に指示通りの計算をする
- C - Energy Drink Collector
https://atcoder.jp/contests/abc121/tasks/abc121_c
- 提出: https://atcoder.jp/contests/abc121/submissions/31781465
- 単価 (
A_i
) の安い店から順に貪欲に買えるだけ買えば良い
- D - XOR World
https://atcoder.jp/contests/abc121/tasks/abc121_d
- 提出: https://atcoder.jp/contests/abc121/submissions/31781725
- 排他的論理和 (XOR) の時点でビット単位で見るとどうなるかを考える
A
,A+1
, ...B
の各位ごとの1
の個数を考える1
の個数が奇数なら1
偶数なら0
になるA <= B <= 10^12
なので前から順に走査すると間に合わない- 周期を考えて何周期あったかから計算する
1 (1)
の位は0101...
と 2 個周期2 (10)
の位は00110011...
と 4 個周期4 (100)
の位は0000111100001111...
と 8 個周期- 周期の前半は
0
で後半は1
になる - 周期ごとに周期の半分の数の
1
がある B
までの周期の回数と周期の残りの部分から1
の個数を求めるA - 1
までを同様に求めて、引くとA..=B
の1
の個数を求められる- 下位ビットから順にこれを求めれば答えのビット列が得られる
- あとはビット列を数値に戻しておしまい
今日のコミット。