こんにちは 村上です。
先日Javaで書かれたロジックをRubyに移植しようとしてハマった内容です。
オーバーフロー
問題です。下記の実行結果はなんでしょう?
1 |
2147483647 + 1 |
JavaとRubyでは結果が異なります。
Java
1 2 |
2147483647 + 1 // => -2147483648 |
Ruby
1 2 |
2147483647 + 1 # => 2147483648 |
お気づきの通りJavaはオーバーフローを起こします。
Rubyの場合は型変換が行われ、自動でより大きい型に変換されます。
(実際「2147483648」ぐらいでは型変換が行われませんが)
オーバーフローを意図的に起こしているようなロジックではこの部分でハマります。
シフト演算
続きましてシフト演算です。
これもオーバーフローを起こす場合はJavaと結果が異なりますが、もう一つ異なることがあります。
右論理シフト
Javaには右シフトする場合、算術シフトと論理シフトがあります。
1 2 3 4 5 6 7 |
// 算術シフト -10 >> 1 // ⇒ -5 // 論理シフト -10 >>> 1 // ⇒ 2147483643 |
簡単に言うと符号ビットをシフト対象にするかしないかの違いですね。
Rubyには右論理シフト(>>>演算子)がない!?
Rubyの場合
1 2 3 4 5 6 7 |
# 算術シフト -10 >> 1 # ⇒ -5 // 論理シフト -10 >>> 1 # ⇒ syntax error, unexpected '>' |
となり論理シフトができません。
おそらく、Javaの場合は最上位bitが符号bitになるんで、シフト対象がわかりますが、Rubyの場合は最上位bitがわかりません。
たぶん型という概念がないんで、最上位bitが32bit目なのか、64bit目なのがわからないんでしょう。
Javaのコードを移植する場合は右論理シフトも要注意です。
どう対応したか?
簡単です。
オーバーフローさせてしまえばいい。
シフトの時は最上位bitを反転させてやればイイんです。
オーバーフロー
1 2 3 4 5 6 7 8 9 |
def overflow_int range_max = 2147483647 range_min = -2147483648 range_size = range_max - range_min + 1 # -2147483648 - 2147483647 + 1 rest = num % range_size rest <= range_max ? rest : rest - range_size end |
右論理シフト
1 2 3 |
def logic_right_shift(num, bit, range=32) (num + (num > 0 ? 0 : 2 ** range)) >> bit end |
かなり無理矢理ですね。。。
でもこんな感じでRubyへの移植は無事完了しました!!