10

重複の可能性:
Haskellの範囲とフロート

haskelで次の出力が発生するのはなぜですか。

[0.1,0.3..1]
[0.1,0.3,0.5,0.7,0.8999999999999999,1.0999999999999999]
  1. 1.0999999999999999(有用な場合は64ビットLinuxマシンを使用しています)の背後にある数学は何ですか?
  2. 0.8999999999999999明らか1.0999999999999999に範囲外のときに停止しないのはなぜですか?
4

2 に答える 2

19

なぜオーバーシュート?

[0.1,0.3..1]の略ですenumFromThenTo 0.1 0.3 1.0

Haskellのレポートによると

FloatおよびDoubleの場合、enumFromファミリーのセマンティクスは、上記のIntのルールによって与えられます。ただし、要素が正の増分iに対してe3 + i ∕ 2より大きくなるか、e3 + iより小さくなると、リストは終了します。負のiの場合は∕2。

ここでe3=1.0、増分i= 0.2、つまりe3 + i∕2=1.1です。それより大きくなったときにのみ停止することになっています。

1で停止するように要求しましたが、0.9または1.1でしか停止できません。丸め誤差があり(フローティングタイプは本質的に不正確です)、1.1は1.09999999999になりました。したがって、これは1.0 + i / 2以下であるため、許可されます。

実際、1.0 + i / 2に等しい場合でも、正確な[0.1,0.3..1]::[Rational](インポート後)を使用して確認できるため、許可されますData.Ratio

目標とする上限0.9を計算し、次のように指定することで、問題を回避できます[0.1,0.3..0.9]。増分が小さく、数値が大きい場合、つまり、数値が大きい場合はDoubleの精度を超えて作業している場合を除いて、丸め誤差が発生することはありません。

なぜ不正確なのですか?

1.09の繰り返しは数学的に1.1と区別できませんが、ここでは9の数が有限であり、これは厳密に1.1未満です。

浮動小数点数は、4.563347x10 ^ -7などの科学的記数法であるかのように格納されますが、01.1001110101x2^01101110のように2進数で格納されます。

つまり、10の累乗で表現できる場合にのみ数値を小数で記述できるのと同様に、2の累乗で表現できる場合にのみ、数値をFloatとして完全に正確に格納できます。

あなたの例の0.2はバイナリで0.001100110011であり、0011は永久に繰り返され、1.1は再び1.0001100110011であり、0011は永久に繰り返されます。

それらの有限部分のみが保存されるため、表示するために10進数に戻すと、少し外れます。多くの場合、違いは非常に小さいため、再び丸められますが、ここのように表示される場合もあります。

この固有の不正確さenumFromThenToにより、上位の数値を超えることができます。丸め誤差のために、数値が少なすぎるのを防ぐことができます。

于 2012-11-02T22:14:31.787 に答える
9

簡単な答え

この動作を理解するには、式がクラスのメソッドである場所[a,b..c]に脱糖されることを知っておく必要があります。enumFromThenTo a b cenumFromThenToEnum

Haskell規格は次のように述べています

Floatおよびの場合、ファミリDoubleのセマンティクスは上記のルールによって与えられます。ただし、要素が正の増分の場合よりも大きくなるか、負の増分の場合よりも小さくなると、リストは終了します。enumFromInte3 + i∕2ie3 + i∕2i

結局のところ、標準は標準です。しかし、それはあまり満足のいくものではありません。

深くなる

DoubleインスタンスはEnumモジュールGHC.Floatで定義されているので、そこで見てみましょう。我々は気づく:

instance Enum Double where
  enumFromThenTo = numericFromThenTo

それは信じられないほど役に立ちませんが、グーグルですばやく検索すると、 GHC.RealnumericFromThenToで定義されていることがわかります。そこで行きましょう。

numericEnumFromThenTo e1 e2 e3 = takeWhile pred (numericEnumFromThen e1 e2)
                                where
                                 mid = (e2 - e1) / 2
                                 pred | e2 >= e1  = (<= e3 + mid)
                                      | otherwise = (>= e3 + mid)

それは少し良いです。の賢明な定義を仮定するとnumericEnumFromThen

numericEnumFromThenTo 0.1 0.3 1.0

結果として

takeWhile pred [0.1, 0.3, 0.5, 0.7, 0.9, 1.1, 1.3 ...]

以来e2 > e1、の定義pred

pred = (<= e3 + mid)
  where
    mid = (e2 - e1) / 2

xしたがって、を満たす限り、このリストから要素を取得します(それらを呼び出します) x <= e3 + mid。その値が何であるかGHCiに聞いてみましょう:

>> let (e1, e2, e3) = (0.1, 0.3, 1.0)
>> let mid = (e2 - e1) / 2
>> e3 + mid
1.1

そのため1.09999...、結果のリストに表示されます。

1.0999...代わりに表示される理由は、がバイナリで正確に表現できない1.1ためです。1.1

推論

なぜ標準はそのような奇妙な振る舞いを規定するのでしょうか?さて、あなたが満足する数だけを取った場合に何が起こるかを考えてください(<= e3)。浮動小数点エラーまたは表現不能のためにe3、生成された数値のリストにまったく表示されない場合があります。これは、次のような無害な式を意味する可能性があります。

[0.0,0.02 .. 0.1]

結果として

[0.0, 0.02, 0.04, 0.06, 0.08]

これは少し奇妙に思えます。の修正numericFromThenToにより、この(おそらくより一般的な)ユースケースで期待される結果が得られることを確認します。

于 2012-11-02T22:13:26.700 に答える