概要
どちらの実装も、2 進浮動小数点数に関する同じ問題に直面しています。
Ruby は単純な演算 (10 の累乗、調整、切り捨て) で浮動小数点数を直接操作します。
Python は、バイナリ浮動小数点数と正確に等しい最短の 10 進数表現を生成する David Gay の洗練されたアルゴリズムを使用して、バイナリ浮動小数点数を文字列に変換します。これは追加の丸めを行わず、文字列への正確な変換です。
最短の文字列表現を使用して、Python は正確な文字列演算を使用して適切な小数点以下の桁数に丸めます。float から string への変換の目的は、バイナリ浮動小数点表現エラーの一部を「元に戻す」ことです (つまり、6.6 を入力すると、Python は 6.5999999999999996 ではなく 6.6 で丸めます。
さらに、Ruby は一部のバージョンの Python とは丸めモードが異なります: round-away-from-zero と round-half-even です。
詳細
Ruby はカンニングをしません。Python と同じように、単純な古い 2 進浮動小数点数で始まります。したがって、同じ問題がいくつかあります (3.35は 3.35 よりわずかに大きく、4.35 は 4.35 よりわずかに小さい)。
>>> Decimal.from_float(3.35)
Decimal('3.350000000000000088817841970012523233890533447265625')
>>> Decimal.from_float(4.35)
Decimal('4.3499999999999996447286321199499070644378662109375')
実装の違いを確認する最善の方法は、基になるソース コードを確認することです。
Ruby ソース コードへのリンクは次のとおりです: https://github.com/ruby/ruby/blob/trunk/numeric.c#L1587
Python ソースはここから始まります: http://hg.python.org/cpython/file/37352a3ccd54/Python/bltinmodule.c
そしてここで終わります: http://hg.python.org/cpython/file/37352a3ccd54/Objects/ floatobject.c#l1080
後者には、2 つの実装の違いを明らかにする広範なコメントがあります。
基本的な考え方は非常に単純です。_Py_dg_dtoa を使用して double を 10 進数の文字列に変換して丸め、次に _Py_dg_strtod を使用してその 10 進数の文字列を double に変換します。ちょっとした問題が 1 つあります。Python 2.x では、round がゼロから半分離れて丸められることを想定しているのに対し、_Py_dg_dtoa では半分から偶数に丸められます。そのため、中途半端なケースを検出して修正する方法が必要です。
検出: 途中の値は、奇数の整数 k に対して k * 0.5 * 10**-n 桁の形式をとります。言い換えると、有理数 x は、その 2 値が正確に -n 桁 -1 であり、その 5 値が少なくとも -n 桁である場合、10**-n 桁の 2 つの倍数のちょうど中間にあります。ndigits >= 0 の場合、2 進浮動小数点数 x については後者の条件が自動的に満たされます。0 > ndigits >= -22 の場合、x は 5**-ndigits の整数倍である必要があります。これは fmod を使用して確認できます。-22 > ndigits の場合、中途半端なケースはありません。5**23 を正確に表すには 54 ビットが必要です。そのため、n >= 23 の 0.5 * 10**n の奇数の倍数を正確に表すには、少なくとも 54 ビットの精度が必要です。
訂正: 中途半端なケースに対処するための簡単な戦略は、(中途半端なケースのみ) _Py_dg_dtoa を ndigits ではなく ndigits+1 の引数で呼び出し (したがって、10 進数への正確な変換を行う)、結果の文字列を手動で丸め、次に変換することです。 _Py_dg_strtod を使用して戻します。
要するに、Python 2.7 はゼロからの丸めの規則に正確に従うために多大な努力を払っています。
Python 3.3 では、偶数への丸めの規則に正確に従うために、同様に多大な労力が費やされます。
_Py_dg_dtoa関数について少し詳しく説明します。Python は float から string 関数を呼び出します。これは、等しい選択肢の中から可能な限り短い文字列表現を与えるアルゴリズムを実装しているためです。たとえば、Python 2.6 では、数値 1.1 は 1.1000000000000001 として表示されますが、Python 2.7 以降では、単に 1.1 です。David Gay の洗練された dtoa.c アルゴリズムは、正確さを犠牲にすることなく「人々が期待する結果」をもたらします。
この文字列変換アルゴリズムは、2 進浮動小数点数に対する round() の実装を悩ませるいくつかの問題を補う傾向があります (つまり、4.35 の丸めが少なくなり、4.349999999999996447286321199499070644378662109375 ではなく 4.35 で始まります)。
それと丸めモード (round-half-even と round-away-from-zero) が、Python と Ruby の round() 関数の本質的な違いです。