0

値の配列を作成しようとしています。これらの値は「2.4,1.6,.8,0」である必要があります。私はすべてのステップで.8を引いています。

これが私がやっている方法です(コードスニペット):

float mean = [[_scalesDictionary objectForKey:@"M1"] floatValue];  //3.2f
float sD = [[_scalesDictionary objectForKey:@"SD1"] floatValue];   //0.8f

nextRegion = mean;
hitWall = NO;
NSMutableArray *minusRegion = [NSMutableArray array];


while (!hitWall) {

    nextRegion -= sD;

if(nextRegion<0.0f){
    nextRegion = 0.0f;
    hitWall = YES;
}

[minusRegion addObject:[NSNumber numberWithFloat:nextRegion]];

}

私はこの出力を得ています:

minusRegion = (
    "2.4",
    "1.6",
    "0.8000001",
    "1.192093e-07",
    0
)

0.8 から 0 の間の信じられないほど小さい数値は必要ありません。これらの値を切り捨てる標準的な方法はありますか?

4

3 に答える 3

3

3.2 も .8 も 32 ビット浮動小数点として正確に表現できません。3.2 に最も近い表現可能な数値は 3.2000000476837158203125 (16 進浮動小数点では 0x1.99999ap+1) です。.8 に最も近い表現可能な数値は 0.800000011920928955078125 (0x1.99999ap-1) です。

3.2000000476837158203125 から 0.800000011920928955078125 を引くと、正確な数学的結果は 2.400000035762786865234375 (0x1.3333338p+1) になります。この結果も、32 ビット浮動小数点として正確に表現できるわけではありません。(これは 16 進浮動小数点で簡単に確認できます。32 ビットの浮動小数点数には 24 ビットの有意桁があります。「1.3333338」は、「1」に 1 ビット、中間の 6 桁に 24 ビット、および中間の 6 桁にもう 1 ビットあります。 ”8”.) そのため、結果は最も近い 32 ビット浮動小数点数 (2.400000095367431640625 (0x1.333334p+1)) に丸められます。

そこから 0.800000011920928955078125 を引くと 1.6000001430511474609375 (0x1.99999cp+0) となり、正確に表現できます。(「1」は 1 ビット、ファイブ ナインは 20 ビット、「c」には 2 つの有効ビットがあります。「c」の下位ビット 2 ビットは末尾のゼロであり、無視される可能性があります。したがって、23 の有効ビットがあります。 .)

そこから 0.800000011920928955078125 を引くと 0.800000131130218505859375 (0x1.99999ep-1) となり、これも正確に表現できます。

最後に、そこから 0.800000011920928955078125 を引くと、1.1920928955078125e-07 (0x1p-23) が得られます。

ここで学ぶべき教訓は、浮動小数点はすべての数値を表すわけではなく、表現できる最も近い数値を得るために結果を丸めることです。浮動小数点演算を使用するソフトウェアを作成するときは、これらの丸め操作を理解し、許可する必要があります。これを可能にする 1 つの方法は、表現できることがわかっている数値を使用することです。整数演算の使用を提案する人もいます。別のオプションは、浮動小数点数で正確に表現できることがわかっているほとんどの値を使用することです。これには、2 24までの整数が含まれます。. したがって、32 から始めて 8 を引くと、24、次に 16、次に 8、そして 0 が得られます。これらは、ループ制御に使用する中間値であり、エラーなしで計算を続行します。結果を出す準備ができたら、10 で割り、3.2、2.4、1.6、.8、および 0 (正確) に近い数値を生成できます。この方法では、反復ごとに丸め誤差を累積するのではなく、算術演算によって各結果に丸め誤差が 1 つだけ導入されます。

于 2012-07-23T15:56:21.767 に答える
2

You're looking at good old floating-point rounding error. Fortunately, in your case it should be simple to deal with. Just clamp:

if( val < increment ){
    val = 0.0;
}

Although, as Eric Postpischil explained below:

Clamping in this way is a bad idea, because sometimes rounding will cause the iteration variable to be slightly less than the increment instead of slightly more, and this clamping will effectively skip an iteration. For example, if the initial value were 3.6f (instead of 3.2f), and the step were .9f (instead of .8f), then the values in each iteration would be slightly below 3.6, 2.7, 1.8, and .9. At that point, clamping converts the value slightly below .9 to zero, and an iteration is skipped.

Therefore it might be necessary to subtract a small amount when doing the comparison.

A better option which you should consider is doing your calculations with integers rather than floats, then converting later.

int increment = 8;
int val = 32;

while( val > 0 ){
    val -= increment;

    float new_float_val = val / 10.0;
};
于 2012-07-23T04:31:15.560 に答える
2

これを行う別の方法は、引き算で得た数値に 10 を掛けて整数に変換し、その整数を 10.0 で割ることです。

これは、次のようにフロア関数 (floorf) を使用して簡単に行うことができます。

float newValue = floorf(oldVlaue*10)/10;

于 2012-07-23T04:14:21.780 に答える