LoginSignup
7
4

More than 5 years have passed since last update.

浮動小数点数の正確な値を割り出す

Posted at

ふだん「0.1」だと思っている値が、実は何者なのか、Rubyを使って調べてみることにしました。

浮動小数点数の内部構造

「Decimal型」のように、十進法による浮動小数点型を提供している環境もありますが、多くのパソコン環境でデフォルトとして使われる浮動小数点数は、IEEE 754形式のものです(Javaなどでは、IEEE 754で実装することが必須となっています)。

これは、指数が「2の累乗」となるので、小さい方は1/2、1/4のような「2の累乗が分母に来る分数」のときだけ厳密な値を表すことができます。つまり、「0.1=1/10」のときすら、正確な値を入れることができません。いちばん近い、「2の累乗が分母に来る分数」に丸めているのです。

Rubyの場合、言語仕様上はIEEE 754が必須ではありませんが、以下ではIEEE 754など「基数が2の累乗」の浮動小数点数を使っていることを前提とします。

桁数指定で文字列化

Float#to_sは「人間が読みやすい形の文字列表現」とだけなっていて、細かな制御は不可能ですが、C言語から由緒正しく続いているsprintfであれば、小数点以下の桁数を指定して文字列化できます。

sprintf('%.100f', 0.1)
# => 0.1000000000000000055511151231257827021181583404541015625000000000000000000000000000000000000000000000

ご覧のように、「0.1」だと思っていた値の、下位が表示されるようにはなってきましたが、えんえん0が続いてしまっています。あとは「100」決め打ちではなく、必要な桁数を取れば完成、ということになります。

有理数化してみる

Float#to_rというメソッドがありますが、一般的な用途では0.1.to_r1/10として認識されないので、使い勝手が悪いです。…が、今回は「厳密に変換してくれる」ことを利用します。

0.1.to_r
# => (3602879701896397/36028797018963968)

IEEE 754など、基数が2の累乗の浮動小数点数では、この分母は必ず2の累乗となります(Ruby 1.9以降では、Rationalは必ず既約分数となります)。そして、10=2*5なので、分母のビット数=この数を小数で表した場合の、小数点以下の桁数となります。それがわかれば、あとはコードに起こすだけです。

実際のコード

便宜上、Float#to_exact_sとオープンクラスしています。

class Float
  def to_exact_s
    digits = to_r.denominator.bit_length - 1
    sprintf('%.*f', digits, self)
  end
end

せっかくなので、0.1~0.9までを展開してみました。

irb(main):009:0> 0.1.to_exact_s
=> "0.1000000000000000055511151231257827021181583404541015625"
irb(main):010:0> 0.2.to_exact_s
=> "0.200000000000000011102230246251565404236316680908203125"
irb(main):011:0> 0.3.to_exact_s
=> "0.299999999999999988897769753748434595763683319091796875"
irb(main):012:0> 0.4.to_exact_s
=> "0.40000000000000002220446049250313080847263336181640625"
irb(main):013:0> 0.5.to_exact_s
=> "0.5"
irb(main):014:0> 0.6.to_exact_s
=> "0.59999999999999997779553950749686919152736663818359375"
irb(main):015:0> 0.7.to_exact_s
=> "0.6999999999999999555910790149937383830547332763671875"
irb(main):016:0> 0.8.to_exact_s
=> "0.8000000000000000444089209850062616169452667236328125"
irb(main):017:0> 0.9.to_exact_s
=> "0.90000000000000002220446049250313080847263336181640625"

大きい方、小さい方、振れ方もそれぞれだということがよくわかりました。

7
4
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4