diary

I like Hatena Star with a text selection.

2022-05-06

github.com

Active Record Validation のRails Guideを読んでいて、気になったところがいくつかあったのでPRした。


上記Rails Guideを読んでいて、NumericalityValidatorがよくわからなかったので調べていた。

Rails Guideで次のように書かれているところに引っかかった。

それ以外の場合は、Floatを用いる数値の変換を試みます。Floatは、カラムの精度または15を用いてBigDecimalにキャストされます。

https://railsguides.jp/active_record_validations.html#numericality

原文だと以下のように書かれている

Otherwise, it will try to convert the value to a number using Float. Floats are casted to BigDecimal using the column's precision value or 15.

https://guides.rubyonrails.org/active_record_validations.html#numericality

ここでは、「値をFloatに変換したあと、BigDecimalに変換する」と書かれている。 Floatに変換した段階で情報が落ちるので、なぜ直接BigDecimalに変換しないのだろうと気になった。

この実装は次のあたりにある。

github.com

どうやら'0.12345'のような値がこのvalidatorに渡ってきたときに、この「FloatにしてからBigDecimalにする」という処理を通りそうということがわかった。

経緯を追っていると、次のPRが見つかった。

github.com

このPRでは、もともとKernel.BigDecimalを呼んでいたところで、Kernel.Floatを呼んだあとto_dを呼ぶように変更されている。その意図は、to_fを定義したPOROをこのvalidatorに渡したときにうまく動くようにするため、のよう。

この挙動については、意図した挙動のように見える。次のドキュメントでは「Precision of Kernel.Float values are guaranteed up to 15 digits.」と言っていて、15以上の精度が保証されているとは言っていない。

github.com

また、Active Record側のコードを見ると、精度をFloat::DIG( == 15)で打ち切っている。

github.com

ということで、まあこれは意図した挙動なのではないかという気がしている。

実際これが問題になるのは、'0.1234567890123456789'のような15桁を超える値がユーザーから送られてきて、かつそれを15桁を超える精度で比較したいときだけだと思う。validationという文脈ではそこまで細かい精度が必要になることはあまりない気がするので、問題にはなりづらい気がしている。

ちなみに、DB上ではこの精度は15より大きい値が許されているので(Floatより高い精度で計算したいのだからそれはそうか)、例えば20桁の精度で定義したカラムを持っているRails appとかはなにか問題を踏んだりしないのだろうか、とかはちょっと気になる。

www.postgresql.jp

dev.mysql.com