文字列から数値へ

量は多いけれども小ネタです。

Javaの数値クラスが文字列をパースする規則

Javaの数値を表すクラスとして、代表的なものにInteger・Double・BigInteger・BigDecimalがあります。
これらのクラスはそれぞれ、文字列で表現された数値をパースするコンストラクタなりファクトリメソッドなりを持っているのですが、微妙にパース規則に違いがあるので、まとめてみました。

APIドキュメントでの記載

  • BigIntegerコンストラク

BigInteger の 10 進 String 表現を BigInteger に変換します。String 表現は、任意のマイナス符号とそれに続く 1 つ以上の 10 進数字の列で構成されます。文字型から数値型へのマッピングは Character.digit で提供されています。

http://java.sun.com/javase/ja/6/docs/ja/api/java/math/BigInteger.html#BigInteger(java.lang.String)

BigDecimalの文字列表現を BigDecimal に変換します。文字列表現は、任意の符号「+」(「\u002B」) または「-」(「\u002D」) と、それに続く 0 桁以上の 10 進数字 (「整数部」) の列で構成され、任意で小数部または指数が付随します。

http://java.sun.com/javase/ja/6/docs/ja/api/java/math/BigDecimal.html#BigDecimal(java.lang.String)
  • Integer#parseInt()

文字列の引数を符号付き整数として構文解析します。文字列にある文字はすべて、指定された基数の桁に使う数字でなければなりません。 これは、Character.digit(char, int) が負ではない値を返すかどうかによって調べることができます。 ただし、1 番目の文字だけは、負の値を表すためにマイナス記号の ASCII 文字「-」(「\u002D」) であってもかまいません。

http://java.sun.com/javase/ja/6/docs/ja/api/java/lang/Integer.html#parseInt(java.lang.String)
  • Double#valueOf()

s 内の先頭と末尾の空白文字は無視されます。(中略)s の残りの文字が、次の字句構文規則*1に従って FloatValue を構成します。

    FloatValue:
Signopt NaN
Signopt Infinity
Signopt FloatingPointLiteral
Signopt HexFloatingPointLiteral
SignedInteger
…(略)

http://java.sun.com/javase/ja/6/docs/ja/api/java/lang/Double.html#valueOf(java.lang.String)


違いは3点です。

  • 文字列の先頭に + 符号があっても良いか
  • 「数字」として、ASCIIの0-9だけではなくCharacter.isDigit()がtrueになる文字を全て使えるか
  • 文字列の先頭と末尾に空白が来ても良いか

表に整理すると次のようになっています。

先頭の+符号 non-ASCII数字 前後の空白
BigInteger NG OK NG
BigDecimal OK OK NG
Integer NG OK NG
Double OK NG OK

動作確認

試してみました。

public static void main(String args[]){
  BigInteger bigInt;
  BigDecimal bigDec;
  Integer i;
  Double d;

  System.out.println("先頭の+符号");
  try {bigInt = new BigInteger("+1");
  }catch(NumberFormatException e){bigInt = null;}
  try {bigDec = new BigDecimal("+1.0");
  }catch(NumberFormatException e){bigDec = null;}
  try {i = Integer.parseInt("+1");
  }catch(NumberFormatException e){i = null;}
  try {d = Double.valueOf("+1.0");
  }catch(NumberFormatException e){d = null;}

  System.out.println("BigInteger : " + bigInt);
  System.out.println("BigDecimal : " + bigDec);
  System.out.println("Integer : " + i);
  System.out.println("Double : " + d);
  System.out.println();

  System.out.println("non-ASCII数字"); // 全角数字
  try {bigInt = new BigInteger("1");
  }catch(NumberFormatException e){bigInt = null;}
  try {bigDec = new BigDecimal("1.0");
  }catch(NumberFormatException e){bigDec = null;}
  try {i = Integer.parseInt("1");
  }catch(NumberFormatException e){i = null;}
  try {d = Double.valueOf("1.0");
  }catch(NumberFormatException e){d = null;}

  System.out.println("BigInteger : " + bigInt);
  System.out.println("BigDecimal : " + bigDec);
  System.out.println("Integer : " + i);
  System.out.println("Double : " + d);
  System.out.println();

  System.out.println("前後の空白"); // 文字列の先頭にスペース一つ
  try {bigInt = new BigInteger(" 1");
  }catch(NumberFormatException e){bigInt = null;}
  try {bigDec = new BigDecimal(" 1.0");
  }catch(NumberFormatException e){bigDec = null;}
  try {i = Integer.parseInt(" 1");
  }catch(NumberFormatException e){i = null;}
  try {d = Double.valueOf(" 1.0");
  }catch(NumberFormatException e){d = null;}

  System.out.println("BigInteger : " + bigInt);
  System.out.println("BigDecimal : " + bigDec);
  System.out.println("Integer : " + i);
  System.out.println("Double : " + d);
}

出力

先頭の+符号
BigInteger : null
BigDecimal : 1.0
Integer : null
Double : 1.0

non-ASCII数字
BigInteger : 1
BigDecimal : 1.0
Integer : 1
Double : null

前後の空白
BigInteger : null
BigDecimal : null
Integer : null
Double : 1.0

当然ですが、APIの記載通り。


たまにハマりそうなこの仕様、一体どういう経緯でこんな複雑なことになっているのでしょう? やっぱり歴史的経緯なのかな。

*1:構文規則の詳細は、Java言語仕様の3.10.2 Floating-Point Literalsあたりを