※当サイトの記事には、広告・プロモーションが含まれます。

Javaでjava.lang.Stringからjava.math.BigDecimalの変換は罠が多いように思う

www.itmedia.co.jp

⇧ そもそも論としてゲームの門外漢が監修もなしにってところに無理がある気がするし、講談社は何故に出版のGOサインを切れると考えたのか...

Javajava.lang.Stringからjava.math.BigDecimalの変換は罠が多いように思う

Oracleさんの標準のJava APIのドキュメントで、

docs.oracle.com

BigDecimalについて確認すると、

  1. コンストラクタを使う方法
    1. BigDecimal(BigInteger val)
    2. BigDecimal(BigInteger unscaledVal, int scale)
    3. BigDecimal(BigInteger unscaledVal, int scale, MathContext mc)
    4. BigDecimal(BigInteger val, MathContext mc)
    5. BigDecimal(char in)
    6. BigDecimal(char in, int offset, int len)
    7. BigDecimal(char in, int offset, int len, MathContext mc)
    8. BigDecimal(char in, MathContext mc)
    9. BigDecimal(double val)
    10. BigDecimal(double val, MathContext mc)
    11. BigDecimal(int val)
    12. BigDecimal(int val, MathContext mc)
    13. BigDecimal(long val)
    14. BigDecimal(long val, MathContext mc)
    15. BigDecimal(String val)
    16. BigDecimal(String val, MathContext mc)
  2. メソッドを使う方法
    1. valueOf(double val)
    2. valueOf(long val)
    3. valueOf(long unscaledVal, int scale)

とあって、大きく分けて、「コンストラクタを使う方法」「メソッドを使う方法」の2つに分けられそうなのですが、

stackoverflow.com

⇧ stackoverflowにあるように、そもそも、StringからDoubleに変換する際に小数点の精度が維持できないという...

なので、このことを知らずに、

String→Double→BigDecimal

と変換すると、正確な精度でBigDecimalに変換できないということになると思う。

なので、標準のAPIを使ってStringからBigDecimalの変換については、BigDecimalのコンストラクタを使うしかないんじゃないかな...

BigDecimal以外については、

tadashi.hatenablog.com

Doubleを例にしたが、IntegerやFloatなども非推奨になっているので注意が必要。
下記リンク先にコンストラクタの書き方で非推奨になったものがまとまっているのでチェックしておくとよさそう。 Deprecated List (Java SE 9 & JDK 9 )

Java9で意外なものが非推奨(Deprecated)になっていた - 聞こえないJavaエンジニアが適当に書き連ねていく

Java 9の段階で、いろんなクラスでコンストラクタを使う方法が非推奨になってるから、BigDecimalもコンストラクタを使わない方が良いのかと勘違いしやすいので要注意ですかね。

実際に、StringからBigDecimalの変換で、「コンストラクタを使う方法」「メソッドを使う方法」のそれぞれを実装してみると、

■コンストラクタを使う方法

		String strId = "1001.00";
		if (Objects.isNull(strId)) {
			return;
		}
		BigDecimal id =new BigDecimal(strId);    

■メソッドを使う方法

		String strId = "1001.00";
		if (Objects.isNull(strId)) {
			return;
		}
		
		String[] strIdArr = strId.split("\\.");
		List<String> strIdList = Arrays.asList(strIdArr);
		int scale = strIdList.get(strIdList.size()-1).length();

		String strIdExcludeDot = strIdList.stream().collect(Collectors.joining());
		long longId = Double.valueOf(strIdExcludeDot).longValue();
		BigDecimal id = BigDecimal.valueOf(longId, scale);    

⇧ という感じで、メソッドを使う方法だと、かなり無理やり感が出てしまう...

他のクラスでコンストラクタを使う方法が非推奨になってるからと言って、当てはまらないクラスもあるという罠ですかね...

ちなみに、Micorosft Bingに、Double.valueOf(String s)が、末尾の0を除外するのは何故か、聞いてみたところ、

IEEE 754のドキュメントは、以下のウェブサイト1で購入することができます。
このドキュメントでは、浮動小数点数の表現形式や演算方法、例外処理などについて詳しく説明されています1。
同じ値として扱うという記載は、第3章の「3.4 Rounding-direction attributes」にあります1。この章では、浮動小数点数の丸め方向を指定する属性について説明されています1。丸め方向には、最近接偶数への丸め(roundTiesToEven)や最近接奇数への丸め(roundTiesToAway)などがあります1。最近接偶数への丸めでは、1.00と1.0は同じ値として扱われます1。
回答に至った一次情報は、以下のウェブサイトです。
1: 754-2008 - IEEE Standard for Floating-Point Arithmetic | IEEE Standard | IEEE Xplore』

とのこと。

つまり、IEEE 754 に準拠してるだけだから、ってことを言ってるようですと。

teratail.com

Java Language Specification
4.2.4. Floating-Point Operations

The Java programming language requires that floating-point arithmetic behave as if every floating-point operator rounded its floating-point result to the result precision. Inexact results must be rounded to the representable value nearest to the infinitely precise result; if the two nearest representable values are equally near, the one with its least significant bit zero is chosen. This is the IEEE 754 standard's default rounding mode known as round to nearest.

Javaで10/3.0はなぜ3.3333333333333335と表示されるのか

⇧ 上記サイト様で、Javaの仕様でも、確かに、IEEE 754 の標準のデフォルトの丸めモードに準拠するとありますと。

となると、IEEE 754 の標準のデフォルトの丸めモードを無視しちゃってるBigDecimalのコンストラクタの方が特殊ということになるんですかね?

まぁ、Javaの標準APIで、Stringのフォーマットそのままの形でBigDecimalに変換するにはコンストラクタを使うしかないってことなんですかね...

ちなみに、

kidotaka.hatenablog.com

⇧ 上記サイト様によりますと、「IEEE754-2008」に関わってた人が、「java.math.BigDecimal」にも関わってたらしい。

で、

qiita.com

⇧ 上記サイト様が分かりやすく説明してくれているのですが、「float」や「double」は「2進浮動小数点数」、「java.math.BigDecimal」は「10進浮動小数点数」として扱われるから、Javaの仕様で言及されてたIEEE 754 の標準の丸めモードが「java.math.BigDecimal」には適応されなかったということなんですかね?

だとすると、Javaの仕様が正確ではなくなってしまっているということですかね。

実際、new BigDecimal(String val)は、1.00を1.00とするので、Javaの仕様で言及されてたIEEE 754 の丸めが適応されてないので、Javaの仕様の記載は語弊を招くような気がする。

Microsoft Bingに聞いてみた。

とのこと。

Microsoft BingにBigDecimal(String val)メソッドって確認しちゃったけど、BigDecimal(String val)コンストラクタって確認するのが正解でしたね。

By the way、「IEEE 754-2008」が「主に10進浮動小数点数の強化」だったらしいので、「java.math.BigDecimal」のnew BigDecimal(String val)が「10進浮動小数点数」に準拠してるんなら「IEEE 754」の規則に従ってるということにならないのか疑問だけど...

毎度モヤモヤ感が半端ない...

今回はこのへんで。