読者です 読者をやめる 読者になる 読者になる

Rubyメモ2

[mfham@localhost work]$ ruby -v
ruby 2.1.8p440 (2015-12-16 revision 53160) [x86_64-linux]

[mfham@localhost work]$ irb
2.1.8 :009 > sprintf('%05d', '10101')
 => "10101" 
2.1.8 :010 > sprintf('%05d', '02101')
 => "01089" 
2.1.8 :011 > sprintf('%05d', '08101')
ArgumentError: invalid value for Integer(): "08101"
        from (irb):11:in `sprintf'
        from (irb):11
        from /home/mfham/.rvm/rubies/ruby-2.1.8/bin/irb:11:in `<main>'

2.1.8 :017 > sprintf('%02d%03d', '10','101')                                                                                    
 => "10101" 
2.1.8 :018 > sprintf('%02d%03d', '02','101')                                                                                    
 => "02101" 
2.1.8 :019 > sprintf('%02d%03d', '08','101')                                                                                    
ArgumentError: invalid value for Integer(): "08"
        from (irb):19:in `sprintf'
        from (irb):19
        from /home/mfham/.rvm/rubies/ruby-2.1.8/bin/irb:11:in `<main>'


# Kernel.#Integer
2.1.8 :028 > Integer('10')                                                                                                      
 => 10 
2.1.8 :029 > Integer('02')                                                                                                      
 => 2 
2.1.8 :030 > Integer('08')
ArgumentError: invalid value for Integer(): "08"
        from (irb):30:in `Integer'
        from (irb):30
        from /home/mfham/.rvm/rubies/ruby-2.1.8/bin/irb:11:in `<main>'

# String#to_i
2.1.8 :021 > '10'.to_i(8)                                                                                                       
 => 8 
2.1.8 :022 > '02'.to_i(8)                                                                                                       
 => 2 
2.1.8 :023 > '08'.to_i(8)
 => 0 

2.1.8 :040 > '10'.to_i(0)
 => 10 
2.1.8 :041 > '02'.to_i(0)
 => 2 
2.1.8 :042 > '08'.to_i(0)
 => 0 

都道府県・市区町村コードを処理する何かがあり、都道府県コードがゼロ埋めされたもの、されていないもの、両方くる可能性があるのに上記のような感じで使っていたらやばい。

メモ

Rubyドキュメント

※実行は2.1.8なのにドキュメントは2.3.0見てるっていうのは許してください。

sprintf フォーマット (Ruby 2.3.0)

d
i
引数の数値を10進表現の整数として出力します。
引数が整数でなければ関数 Kernel.#Integer と同じ規則で整数に 変換されます。

module function Kernel.#Integer (Ruby 2.3.0)

引数が数値の場合は直接変換し(小数点以下切り落とし)、 文字列の場合は、進数を表す接頭辞を含む整数表現とみなせる文字列のみ 変換します。

なるほど、sprintfでd(i)を指定したら、Kernel.#Integerと同様の動きをするのか。
String#to_iを使ってみたけど、これとは挙動が違うみたい。なぜ?

気になるなーるね

調べる。
Kernel.#IntegerとString#to_iのソース。
module Kernel - Documentation for Ruby 2.3.0
class String - Documentation for Ruby 2.3.0

Kernel.#Integer

最終的にこれを返す。

rb_convert_to_integer(arg, base);

Integer('08')ならrb_convert_to_integer('08', 0)かな?
rb_convert_to_integerをもう少し追ってみると、
https://github.com/ruby/ruby/blob/4915ce691d147a460e14531f4031d6934c5e8c5c/object.c#L2701
第一引数のタイプがT_STRINGならrb_str_to_inum(val, base, TRUE)をreturnしている。

String#to_i

最終的にこれを返す。

rb_str_to_inum(str, base, FALSE);

'08.to_i(8)ならrb_str_to_inum('08', 8, FALSE)かな?
'08.to_i(0)ならrb_str_to_inum('08', 0, FALSE)かな?

違い

rb_str_to_inumの第三引数がTRUEとFALSEで違うみたい。
ちなみにsprintfはこれかな?
https://github.com/ruby/ruby/blob/00fcd967d9900713fa7f617d9a5077ec178e073f/sprintf.c#L842
rb_str_to_inum(val, 0, TRUE)
(base=0だから、プリフィクスから基数を判断するのかな)

rb_str_to_inumは何するの?

https://github.com/ruby/ruby/blob/772fd010b6a76887d9be9389f60cc024bd34f83b/bignum.c#L4207
rb_cstr_parse_inumの結果を受けて、NIL_P(ret)が真なら第三引数(TRUE, FALSEのやつ)をチェックして、TRUEならエラー(https://github.com/ruby/ruby/blob/772fd010b6a76887d9be9389f60cc024bd34f83b/bignum.c#L3706)、FALSEなら0(macro INT2FIX (Ruby 2.3.0))を返している。
rb_cstr_parse_inumを追うのは今はやめておこう・・・
https://github.com/ruby/ruby/blob/772fd010b6a76887d9be9389f60cc024bd34f83b/bignum.c#L4018

まとめ

Kernel.#Integer、Kernel.#sprintfはrb_str_to_inumの第三引数がTRUEになっておりエラーを投げる。
String#to_iはrb_str_to_inumの第三引数がFALSEになっており、0を返す。
使い方気をつけよう・・・

docs.ruby-lang.orgのKernel.#Integerの引数、第二引数base=0つけたほうがいいんやない?