适当减去浮点值

时间:2012-07-23 03:56:15

标签: objective-c ios c floating-point

我正在尝试创建一个值数组。这些值应为“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
)

我不希望在.8和0之间有一个非常小的数字。是否有标准方法来截断这些值?

3 个答案:

答案 0 :(得分:3)

3.2和.8都不能完全表示为32位浮点数。最接近3.2的可表示数字是3.2000000476837158203125(十六进制浮点数,0x1.99999ap + 1)。最接近.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)。

这里要学习的教训是浮点并不代表所有数字,它会对结果进行舍入,以便为您提供最接近的数字。编写软件以使用浮点运算时,必须了解并允许这些舍入运算。允许这种情况的一种方法是使用您知道可以表示的数字。其他人建议使用整数运算。另一种选择是主要使用您知道可以在浮点中精确表示的值,其中包括最多2 24 的整数。所以你可以从32开始减去8,然后产生24,然后是16,然后是8,然后是0.这些将是你用于循环控制和继续计算而没有错误的中间值。当你准备好提供结果时,你可以除以10,产生接近3.2,2.4,1.6,.8和0(确切)的数字。这样,你的算法只会在每个结果中引入一个舍入误差,而不是累积从迭代到迭代的舍入误差。

答案 1 :(得分:2)

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

您可以使用楼层功能(floorf)轻松完成此操作:

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

答案 2 :(得分:2)

你正在寻找旧的浮点舍入错误。幸运的是,在你的情况下应该很容易处理。只是钳:

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

虽然,Eric Postpischil explained below

  

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

因此,在进行比较时可能需要减去一小部分。

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

int increment = 8;
int val = 32;

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

    float new_float_val = val / 10.0;
};