ククログ

株式会社クリアコード > ククログ > Ruby 3.4でString#to_f/Kernel.#Floatの挙動がちょっと変わるよ

Ruby 3.4でString#to_f/Kernel.#Floatの挙動がちょっと変わるよ

Rubyの開発に参加している須藤です。Apache Arrowの開発中に、他のシステムでは受け付ける浮動小数点数の文字列表現をRubyでは受け付けないことを知ったので、Rubyでも受け付けるようにする?という話をしたら受け付けるようになりました。ということで、2024年12月にリリース予定のRuby 3.4.0からString#to_f/Kernel.#Floatの挙動が少し変わります。Ruby 3.4.0 preview2にも入っているので、これで試して問題がある場合は https://bugs.ruby-lang.org/ に報告してください。今ならまだこの挙動を元に戻せるかもしれません。

きっかけ

Apache Arrowにはdecimalという精度を維持したまま小数を表現できる型があります。これ自体は一般的な型で、各種データベースでもサポートされていますし、RubyにはBigDecimalというこの型をサポートするためのbundlded gemもあります。

Apache ArrowのRuby用ライブラリーであるRed ArrowでもBigDecimalを使ってdecimal型に対応していました。ということで、Rubyでも普通にdecimalを扱えるようにしてあるのですが、値が0のときにうまく扱えないという問題が報告されました。

小数はdecimalではなく浮動小数点数としても表現できますが、浮動小数点数で表現すると誤差が入るので、Apache ArrowでのdecimalデータをRubyのBigDecimalオブジェクトに変換するときに浮動小数点数を経由してはいけません。ではどうするかというと、文字列表現を経由します。1.1ではなく"1.1"を使うということです。(Apache Arrowのdecimalの内部表現とBigDecimalの内部表現は違うのでゼロコピーでの変換はできません。)

Apache Arrowのdecimal実装では、0.0の文字列表現に"0.E-9"のように小数点直後の0を省略した表現を用いていました。(省略しないと"0.0E-9"のようになります。)

BigDecimalは"0.0E-9"という表現は受け付けますが、"0.E-9"という表現は受け付けないので動かないというわけです。この挙動はRuby本体のString#to_f/Kernel.#Floatに合わせているということなので、Ruby本体ではどうする?と問うてみました。Red Arrow的にはどうとでも解決できるので、個人的には別にどっちでもよかったのですが、気づいてしまったので問いかけたくらいの気持ちです。

他のシステムでの状況

Ruby本体に問いかけるにあたり世の中ではどうなっているのかと思って少し調べたところ、世の中では結構"0.E-9"をサポートしていました。以下は、文字列から小数への変換ではなく、小数リテラルの結果なのでちょっとこのケースとは違うのですが、まぁ、リテラルでサポートしているなら文字列からの変換でもサポートしているんじゃない?くらいの気持ちで調べたものです。リテラルの0.E-9をサポートしない?という話をしたいわけではありません。

PostgreSQL:

=> select 0.E-9;
  ?column?   
-------------
 0.000000000
(1 row)

MySQL:

> select 0.E-9;
+-------+
| 0.E-9 |
+-------+
|     0 |
+-------+
1 row in set (0.00 sec)

Python:

$ python3 -c 'print(0.E-9)'
0.0

Node.js:

$ nodejs -e 'console.log(0.E-9)'
0

該当チケット上でなんやかんや意見が出て、"0.E-9"もサポートしていいんじゃない?ということになり、String#to_f/Kernel.#Floatの挙動が変わりました。あわせてBigDecimalの挙動も変更されました。(この変更が入ったBigDecimalはまだリリースされていません。)

互換性

Kernel.#Floatは変換できない場合にはエラーにする変換機能で、String#to_fはできる範囲で浮動小数点数に変換してエラーにはしない変換機能です。"0.E-9"の扱いが変わるので挙動が変わります。

Kernel.#Floatでは、これまではエラーになっていたものが0.0を返すようになります。

String#to_fでは、今までと違う浮動小数点数が返ることがあります。"0.E-9".to_fでは相変わらず0.0が返りますが、"1.E-9".to_fではこれまでは1.0でこれからは0.000000001が返ります。これが困る人は今のうちに https://bugs.ruby-lang.org/ に行ってあなたのユースケースを説明してください。

実装

報告したので一応実装したのですが、なかださんに"0xf.p0"とか16進数での小数表現もあるよ?と言われてそんなの見たことない!という気持ちになった + なかださんも実装を持っていたのでRuby 3.4にはなかださん実装が入っています。

Red Arrowでの対応

では、もともとのRed Arrowで0のdecimalを扱えない問題は、Red Arrow側ではなにもしなくても解決、としたのだろうと思っていることでしょう。が、実は別の解決方法をとりました。

Apache Arrowのdecimal実装での文字列表現を"0.0E-9"にしました。Apache Arrow界隈の人たちにRubyでは"0.E-9"をサポートしていないから"0.0E-9"にしていい?と相談して合意をとりつけて変更しました。

これだとこれまでのRubyでも動くようになります。

まとめ

この変更はまだリリースされていないので、もし、この変更で困る人は今のうちに報告してください!リリースしてからだと戻すハードルが高くなるはずです。

Rubyは30年以上も開発が続いている言語なので、多くのケースはいい感じになっていて、普段使いで困ることは少ないですが、今回のように改良点が見つかることもあります。みなさんも、ここらへんがこうなるといいかも?と思ったら提案してみてはいかがでしょうか。今回はたまたま通りましたが、rejectされることも多いでしょう。でも、そういうことの積み重ねで今のRubyができているので、悪くないんじゃないかな。ただ、提案するときはユースケースや妥当性など判断に必要そうな情報はできるだけ最初から提供したほうがよいです。