在C和Objective-C中,将浮点或双精度截断为整数的正确方法是什么?

在C和Objective-C中,将浮点或双精度截断为整数的正确方法是什么?,objective-c,c,floating-point,floating-accuracy,Objective C,C,Floating Point,Floating Accuracy,我以前主要处理整数,在需要将浮点或双精度截断为整数的情况下,我会使用以下方法: (int) someValue 除非我发现以下情况: NSLog(@"%i", (int) ((1.2 - 1) * 10)); // prints 1 NSLog(@"%i", (int) ((1.2f - 1) * 10)); // prints 2 (解释请参见) 简短的问题是:我们应该如何正确地将浮点或双精度截断为整数?(在这种情况下需要截断,而不是“舍入”)。或者,我们可以说,因为一个数字

我以前主要处理整数,在需要将浮点或双精度截断为整数的情况下,我会使用以下方法:

(int) someValue
除非我发现以下情况:

NSLog(@"%i", (int) ((1.2 - 1) * 10));     // prints 1
NSLog(@"%i", (int) ((1.2f - 1) * 10));    // prints 2
(解释请参见)

简短的问题是:我们应该如何正确地将浮点或双精度截断为整数?(在这种情况下需要截断,而不是“舍入”)。或者,我们可以说,因为一个数字是1.9999999999,另一个是2.00000000000001(粗略地说),所以截断实际上是正确的。所以问题是,我们应该如何转换一个浮点数或double,从而使结果是一个“截断”的数字,这是常见的用法

(目的不是使用
舍入
,因为在这种情况下,对于
1.8
,我们确实希望得到
1
的结果,而不是
2


更长的问题:

我曾经

int truncateToInteger(double a) {
    return (int) (a + 0.000000000001);
}

-(void) someTest {
    NSLog(@"%i", truncateToInteger((1.2 - 1) * 10));
    NSLog(@"%i", truncateToInteger((1.2f - 1) * 10));
}
两个都打印成
2
,但这似乎是一个太多的黑客行为,我们应该用什么小数字来“消除不准确”?有没有一种更标准或更仔细的方法,而不是这种武断的黑客行为


(请注意,在某些用法中,我们需要截断,而不是四舍五入,例如,如果秒数为90或118,当我们显示已过多少分钟和多少秒时,分钟应显示为
1
,但不应四舍五入到
2

您必须计算预期的错误,然后可以安全地将其添加到截断中。例如,您说过1.8应该映射到1。1.9呢?1.99怎么样?
如果您知道在您的问题域中无法获得大于1.8的任何值,则可以安全地添加0.001以使截断生效。

使用“hack”是正确的方法。浮动的工作原理很简单,如果您想要更合理的十进制行为
NSDecimal(Number)
可能就是您想要的。

当然,截断是正确执行的,但中间值不准确

一般来说,无法知道您的
1.999999
结果是稍微不准确的
2
(因此截断后的精确数学结果是
2
),还是稍微不准确的
1.999998
(因此截断后的精确数学结果是
1

因此,对于某些计算,您可能会得到稍微不准确的
2.000001
。不管你做什么,你都会弄错的。截断是一个非连续函数,所以无论你怎么做,它都会使你的整体计算在数值上不稳定

您可以添加任意公差:
(int)(x>0?x+epsilon:x-epsilon)
。它可能有帮助,也可能没有帮助,这取决于你在做什么,这就是为什么它是一个“黑客”
epsilon
可以是一个常数,也可以根据
x
的大小进行缩放

第二个问题最常见的解决方案不是“消除不准确”,而是接受不准确的结果,就好像它是准确的一样。所以,如果浮点单位表示
(1.2-1)*10
是1.999999,那么它是1.999999。如果该值表示分钟数,则它将截断为1分59秒。最终显示的结果将偏离真实值1s。如果您需要更精确的最终显示结果,那么您不应该使用浮点运算来计算它,或者您应该在截断为分钟之前四舍五入到最接近的秒

任何试图从浮点数中“消除”不精确性的尝试实际上只是将不精确性四处移动——一些输入将给出更精确的结果,而另一些则不太精确。如果你足够幸运,在这种情况下,误差会转移到你不关心的输入,或者在进行计算之前可以过滤掉,那么你赢了。但一般来说,如果你不得不接受任何意见,那么你就会失去一些东西。你需要看看如何使你的计算更准确,而不是试图在最后的截断步骤中消除不准确


对于您的示例计算,有一个简单的修正——使用小数点后10位为基数的定点算法。我们知道格式可以准确地表示1.2。因此,与其写
(1.2-1)*10
,不如重新调整计算比例,使用十分之一(写
(12-10)*10
),然后将最终结果除以10,将其缩放回单位。

正确的方法是:识别您执行的每个浮点操作。这包括将十进制数字转换为浮点(例如源文本中的“1.2”产生浮点值0x1.3333p0或“1.2f”产生0x1.33333 4p0)。确定每个操作可能产生的错误的限制。(对于IEEE 754定义的基本运算,如简单算术,该限制为1/2 ULP[最小精度单位]实际输入的精确数学结果。对于从十进制数字到二进制浮点的转换,语言规范可能允许1 ULP,但好的编译器将其限制为1/2 ULP。对于提供正弦或对数等复杂函数的库例程,商业库通常允许多个ULP的错误,尽管虽然它们通常在基本间隔内更好。您需要从库供应商处获得规范。)通过数学证明确定最终错误的界限。如果你能证明,对于某些误差界e,当精确的数学结果是某个整数i时,实际计算的结果在半开区间[i-e,i+1-e]内,那么你可以通过将e添加到计算结果并将该计算结果截断为整数来产生精确的数学结果。(为了简洁起见,我省略了某些复杂问题。其中一个问题是,添加e可能会导致I+1的四舍五入。另一个问题是
float a = (1.2f - 1) * 10;
int b;

// multiply by 10 to "round to one decimal place"
a = round( a * 10. );

// now cast to integer first to avoid further decimal errors
b = (int) a;

// get rid of the factor 10 again by integer division
b = b / 10;

// now 'b' should hold the result you're expecting;
NSLog(@"%i", [[NSNumber numberWithFloat:((1.2 - 1) * 10)] intValue]); //2
NSLog(@"%i", [[NSNumber numberWithFloat:(((1.2f - 1) * 10))] intValue]); //2 
NSLog(@"%i", [[NSNumber numberWithFloat:1.8] intValue]); //1
NSLog(@"%i", [[NSNumber numberWithFloat:1.8f] intValue]); //1
NSLog(@"%i", [[NSNumber numberWithDouble:2.0000000000001 ] intValue]);//2