なにがあったか
8桁のゼロパディングされた10進数の数字文字列について、値が本当に数値かどうかをチェックしたかった。数値かどうかの判定はInteger()
に文字列を投げて例外がでるかどうかでチェックする方法があるようなので、やってみた。
$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]
def integer_string?(str)
Integer(str)
true
rescue ArgumentError
false
end
integer_string?("00004820") #=> false
true
になることを期待していたが、数値ではないと判定されてしまった。
なぜそうなった?
"00004820"
は8進数として認識されていたからだった。
調べていくとなるほど当然の挙動であったが、普段あまり10進数以外を扱わないので、最初は面食らってしまった。
Integer("00000010") #=> 8
Integer("00000008") #=> ArgumentError、8進数で表現できない
Ruby実装を追っていくと、どうもこの辺で文字列から基数を判断している様子?
https://github.com/ruby/ruby/blob/ruby_2_3/bignum.c#L4013
実装全部をおいかけたわけではないが、次のような理解をした。
# 文字列の先頭が0の場合は接頭辞として扱われ、2文字目で基数が決定される
Integer("0x10") #=> 16 (16進数)
Integer("0b10") #=> 2 (2進数)
Integer("0o10") #=> 8 (8進数)
Integer("0d10") #=> 10 (10進数)
# 先頭が0で特定の接頭辞がとして判断できなければ、8進数として扱う
Integer("010") #=> 8 (8進数)
# 文字列の先頭が0じゃなければ10進数として扱う
Integer("123") #=> 123 (10進数)
こうすればいい
ちゃんと基数を設定してあげると良さそう。
でも代わりに接頭辞表現を受け付けなくなったりするので、使い所によるかも。
def decimal_integer_string?(str)
Integer(str, 10)
true
rescue ArgumentError
false
end
decimal_integer_string?("00004820") #=> true
decimal_integer_string?("0000482a") #=> false *不要な文字が混じっている
decimal_integer_string?("0x01") #=> false *この表記は受け付けなくなるので注意
integer_string?("0x01") #=> true *基数を指定しなければtrue扱い
感想: 難しいし奥深いなぁ(小並感)