7

次の C# コードを検討してください。

double result1 = 1.0 + 1.1 + 1.2;
double result2 = 1.2 + 1.0 + 1.1;

if (result1 == result2)
{
    ...
}

result1 は常に result2 と等しくなければなりませんよね?問題は、そうではないということです。result1 は 3.3 で、result2 は 3.3000000000000003 です。唯一の違いは、定数の順序です。

double は、丸めの問題が発生する可能性がある方法で実装されていることを知っています。絶対精度が必要な場合は、代わりに小数を使用できることを認識しています。または、if ステートメントで Math.Round() を使用できること。私は、C# コンパイラが何をしているかを理解したい単なるオタクです。誰でも教えてもらえますか?

編集:

これまでに浮動小数点演算について読むことを提案したり、CPU が double を処理する方法に固有の不正確さについて話したりしたすべての人に感謝します。しかし、私の質問の要点はまだ答えられていないと感じています。それを正しく表現していないのは私のせいです。次のように言いましょう。

上記のコードを分解すると、次の操作が行われると予想されます。

double r1 = 1.1 + 1.2;
double r2 = 1.0 + r1
double r3 = 1.0 + 1.1
double r4 = 1.2 + r3

上記の加算のそれぞれに丸め誤差 (番号 e1..e4) があると仮定しましょう。したがって、r1 には丸め誤差 e1 が含まれ、r2 には丸め誤差 e1 + e2 が含まれ、r3 には e3 が含まれ、r4 には e3 + e4 が含まれます。

さて、丸め誤差がどのように発生するのか正確にはわかりませんが、e1 + e2 が e3 + e4 に等しいと予想していました。明らかにそうではありませんが、それは私にはどういうわけか間違っているようです。もう 1 つのことは、上記のコードを実行すると、丸めエラーが発生しないことです。それが、CPU ではなく、C# コンパイラが奇妙なことをしていると私に思わせる理由です。

私は多くのことを尋ねていることを知っています。おそらく、誰もが与えることができる最良の答えは、CPU 設計で PHD を実行することですが、私は尋ねたいと思いました。

編集 2

元のコード サンプルの IL を見ると、これを行っているのは CPU ではなくコンパイラであることは明らかです。

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
    .maxstack 1
    .locals init (
        [0] float64 result1,
        [1] float64 result2)
    L_0000: nop 
    L_0001: ldc.r8 3.3
    L_000a: stloc.0 
    L_000b: ldc.r8 3.3000000000000003
    L_0014: stloc.1 
    L_0015: ret 
}

コンパイラは私のために数字を合計しています!

4

7 に答える 7

10

e1+e2 が e3+e4 に等しいと思っていたでしょう。

それは期待とまったく違うわけではありません

 floor( 5/3 ) + floor( 2/3 + 1 )

等しくする

 floor( 5/3 + 2/3 ) + floor( 1 )

ただし、発言権を得る前に 2^53 を掛けていることを除きます。

12 ビット精度の浮動小数点を使用し、値を切り捨てる:

1.0 = 1.00000000000
1.1 = 1.00011001100
1.2 = 1.00110011001

1.0 + 1.1 = 10.00011001100 // 合計時に拡張
r1 = 1.0 + 1.1 = 10.0001100110 // 12 ビットに切り捨て
r1 + 1.2 = 11.01001100101 // 合計中に拡張
r2 = r1 + 1.2 = 11.0100110010 // 12 ビットに切り捨て

1.1 + 1.2 = 10.01001100110 // 合計時に拡張
r3 = 1.1 + 1.2 = 10.0100110011 // 12 ビットに切り捨て
r3 + 1.0 = 11.01001100110 // 合計中に拡張
r4 = r3 + 1.0 = 11.0100110011 // 12 ビットに切り捨て

したがって、操作/切り捨ての順序を変更すると、エラーが変更され、r4 != r2. このシステムで 1.1 と 1.2 を追加すると、最後のビットがキャリーされるため、切り捨てで失われません。1.0 を 1.1 に加算すると、1.1 の最後のビットが失われるため、結果は同じではありません。

1 つの順序付けでは、丸め (切り捨てによる) によって末尾の が削除され1ます。

他の順序では、丸めにより0両方の回で末尾が削除されます。

1 は 0 ではありません。したがって、エラーは同じではありません。

double にはより多くの精度があり、C# ではおそらく切り捨てではなく丸めが使用されますが、この単純なモデルが、同じ値の異なる順序で異なるエラーが発生する可能性があることを示していることを願っています。

fp と maths の違いは、+ は単に加算するのではなく、'add then round' の省略形であることです。

于 2009-03-30T13:31:45.093 に答える
6

C# コンパイラは何もしていません。CPUは。

CPUレジスタにAがあり、次にBを追加すると、そのレジスタに格納される結果はA + Bになり、使用される浮動小数点精度に近似されます

次に C を追加すると、エラーが加算されます。このエラーの追加は推移的な操作ではないため、最終的な違いです。

于 2009-03-30T11:09:15.403 に答える
4

この件については、古典的な論文 (すべてのコンピューター科学者が浮動小数点演算について知っておくべきこと)を参照してください。この種のものは、浮動小数点演算で起こることです。1/3+1/3+1/3 が 1 に等しくないことを計算機科学者が教えてくれる...

于 2009-03-30T11:18:32.667 に答える
2

浮動小数点演算の順序は重要です。あなたの質問に直接答えるものではありませんが、浮動小数点数の比較には常に注意する必要があります。公差を含めるのが通常です。

double epsilon = 0.0000001;
if (abs(result1 - result2) <= epsilon)
{
    ...
}

これは興味深いかもしれません:すべてのコンピュータ科学者が浮動小数点演算について知っておくべきこと

于 2009-03-30T11:08:46.013 に答える
1

順序によってエラーが同じにならない理由は、別の例で説明できます。

たとえば、10未満の数値の場合、すべての数値を格納できるため、10までは1、2、3などを格納できますが、10を超えると、内部損失のために1つおきの数値しか格納できません。つまり、10、12、14などしか保存できません。

ここで、その例を使用すると、次の結果が異なる理由がわかります。

1 + 1 + 1 + 10 = 12 (or 14, depending on rounding)
10 + 1 + 1 + 1 = 10

浮動小数点数の問題は、それらを正確に表すことができず、エラーが常に同じように進むとは限らないため、順序が重要になることです。

たとえば、3.00000000003 + 3.00000000003は6.00000000005(最後に6ではないことに注意)になる可能性がありますが、3.00000000003 + 2.99999999997は6.00000000001になる可能性があり、次のようになります。

step 1: 3.00000000003 + 3.00000000003 = 6.00000000005
step 2: 6.00000000005 + 2.99999999997 = 9.00000000002

ただし、順序を変更します。

step 1: 3.00000000003 + 2.99999999997 = 6.00000000001
step 2: 6.00000000001 + 3.00000000003 = 9.00000000004

だからそれは重要です。

もちろん、上記の例のバランスが取れているという点で幸運かもしれません。最初の例は.xxx1だけ上に、もう1つは.xxx1だけ下に振れて、両方で.xxx3になりますが、保証はありません。

于 2009-03-30T15:54:45.077 に答える
1

result1 は常に result2 と等しくなければなりませんよね?

違う。これは数学では当てはまりますが、浮動小数点演算では当てはまりません。

数値解析の入門書を読む必要があります。

于 2009-03-30T11:16:43.700 に答える
0

中間結果が異なるため、実際には同じ値を使用していません。

double result1 = 2.1 + 1.2;
double result2 = 2.2 + 1.1;

double は 10 進数値を正確に表すことができないため、異なる結果が得られます。

于 2009-03-30T11:18:05.007 に答える