次のコードを書いたらどうなるでしょうか。
int x = 0;
for(int i = 0; i< 100; i++)
{
x = x + 3.5;
NSLog(@"%d",x);
}
3.5
印刷されなく7.0
ても驚かないでしょう10.5
か? 「int は不正確」だと思いますか? もちろん違います。
あなたの例ではまったく同じことが起こっています。3.5
整数として表現できないのと同じよう0.01
に、double として表現できません。実際に得られる値は次のとおりです。
0.01000000000000000020816681711721685132943093776702880859375
ここで、 double への丸めから最初の表現誤差が蓄積されるだけでなく、すべての中間合計が表現可能ではないため、丸め誤差も発生します。この 2 番目のエラーの原因は、この例では発生しません。計算される実際の中間合計は次のとおりです。0.1
int
0.01000000000000000020816681711721685132943093776702880859375
0.0200000000000000004163336342344337026588618755340576171875
0.0299999999999999988897769753748434595763683319091796875
0.040000000000000000832667268468867405317723751068115234375
0.05000000000000000277555756156289135105907917022705078125
0.060000000000000004718447854656915296800434589385986328125
...
0.990000000000000657252030578092671930789947509765625
1.0000000000000006661338147750939242541790008544921875
書式指定子を使用してこれらを小数点以下 6 桁%f
に丸めると、丸めの 3 番目のソースが得られますが、エラーはすべて十分に小さいため、「期待する」結果が出力されます。
float
の代わりにdouble
;を使用するとどうなるか見てみましょう。C 算術オペランド プロモーション規則により、加算はすべて で実行されますdouble
が、結果はfloat
各加算後に に丸められます。さらに丸めが行われます。中間値のシーケンスは次のとおりです。
0.00999999977648258209228515625
0.0199999995529651641845703125
0.02999999932944774627685546875
0.039999999105930328369140625
0.0500000007450580596923828125
0.060000002384185791015625
...
0.809999525547027587890625
0.819999516010284423828125
0.829999506473541259765625
この時点まで、エラーは十分に小さいため、10 進数 6 桁に丸めた場合でも、すべてのエラーが「期待される」値を生成します。ただし、次に計算される値は
0.839999496936798095703125
これは、小数点以下 6 桁に丸める正確な中間ケースよりも小さいためです。
0.8399995
切り捨てられ、出力される数値は次のとおりです。
0.8399999
さて、あなたはそれについて何ができますか?6 桁の 10 進数で印刷するとエラーが最終的に十分に大きくなる理由は、順次追加するたびにエラーを累積しているためです。この累積を避けることができれば、誤差は小さいままでこの問題は発生しません。この累積エラーを回避する簡単な方法がいくつかあります。おそらく最も簡単なのは次のとおりです。
for (int i=0; i<100; i++) {
float x = (i+1)/100.f;
NSLog(@"%d",x);
}
i + 1
との両方100.f
が で正確に表されているため、これは機能しfloat
ます。したがって、除算で発生する丸めは 1 回だけであるため、float
結果は可能な限り目的の数値に近くなります。これ以上近づく方法はありません。