0

我正在尝试创建一个值数组。这些值应该是“2.4,1.6,.8,0”。我每一步都减去 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(十六进制浮点数,0x1.99999ap+1)。最接近 0.8 的可表示数字是 0.800000011920928955078125 (0x1.99999ap-1)。

当从 3.2000000476837158203125 中减去 0.800000011920928955078125 时,精确的数学结果是 2.400000035762786865234375 (0x1.3333338p+1)。此结果也不能完全表示为 32 位浮点数。(您可以在十六进制浮点数中轻松看到这一点。一个 32 位浮点数有一个 24 位有效数。“1.3333338”在“1”中有一位,中间六位中有 24 位,在”8”。)所以结果被四舍五入到最接近的 32 位浮点数,即 2.400000095367431640625 (0x1.333334p+1)。

从中减去 0.800000011920928955078125 得到 1.6000001430511474609375 (0x1.99999cp+0),这是完全可表示的。(“1”是一位,五个9是20位,“c”有两个有效位。“c”中的低位两位是尾随零,可以忽略。所以有23个有效位.)

从中减去 0.800000011920928955078125 得到 0.800000131130218505859375 (0x1.99999ep-1),这也是完全可表示的。

最后,从中减去 0.800000011920928955078125 得到 1.1920928955078125e-07 (0x1p-23)。

The lesson to be learned here is the floating-point does not represent all numbers, and it rounds results to give you the closest numbers it can represent. When writing software to use floating-point arithmetic, you must understand and allow for these rounding operations. One way to allow for this is to use numbers that you know can be represented. Others have suggested using integer arithmetic. Another option is to use mostly values that you know can be represented exactly in floating-point, which includes integers up to 224. 所以你可以从 32 开始减去 8,得到 24,然后是 16,然后是 8,然后是 0。这些将是你用于循环控制和继续计算而没有错误的中间值。当您准备好交付结果时,您可以除以 10,产生接近 3.2、2.4、1.6、0.8 和 0 的数字(精确)。这样,您的算术只会在每个结果中引入一个舍入误差,而不是在一次迭代中累积舍入误差。

于 2012-07-23T15:56:21.767 回答
2

另一种方法是将减法得到的数字乘以 10,然后转换为整数,然后将该整数除以 10.0。

您可以使用 floor 函数 (floorf) 轻松完成此操作,如下所示:

浮动 newValue = floorf(oldVlaue*10)/10;

于 2012-07-23T04:14:21.780 回答
2

您正在查看旧的浮点舍入错误。幸运的是,在您的情况下,它应该很容易处理。只需夹紧:

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

虽然,正如Eric Postpischil 解释如下

以这种方式钳制是一个坏主意,因为有时舍入会导致迭代变量略小于增量而不是略多,并且这种钳制将有效地跳过迭代。例如,如果初始值为 3.6f(而不是 3.2f),步长是 0.9f(而不是 0.8f),那么每次迭代中的值将略低于 3.6、2.7、1.8 和 0.9。此时,钳位将略低于 0.9 的值转换为零,并跳过迭代。

因此,在进行比较时可能需要减去少量。

您应该考虑的一个更好的选择是使用整数而不是浮点数进行计算,然后再进行转换。

int increment = 8;
int val = 32;

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

    float new_float_val = val / 10.0;
};
于 2012-07-23T04:31:15.560 回答