整型转双精度浮点数的问题

13

我是一名Objective-C开发者,对C/C++的经验很少(且没有培训),今天在使用硬编码数字值时遇到了一些奇怪的问题。

我相信这是一个简单/愚蠢的问题,但是有人可以解释一下为什么这个可以工作吗:

NSDate *start = [NSDate date];
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);

dispatch_after(popTime, dispatch_get_main_queue(), ^{
  NSLog(@"seconds: %f", [start timeIntervalSinceNow]);
});
// output: seconds: -1.0001

这个方法同样有效(注意秒数已更改):

NSDate *start = [NSDate date];
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);

dispatch_after(popTime, dispatch_get_main_queue(), ^{
  NSLog(@"seconds: %f", [start timeIntervalSinceNow]);
});
// output: seconds: -2.0001

但是这将立即执行:

NSDate *start = [NSDate date];
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 4 * NSEC_PER_SEC);

dispatch_after(popTime, dispatch_get_main_queue(), ^{
  NSLog(@"seconds: %f", [start timeIntervalSinceNow]);
});
// output: seconds: -0.0001

然而,使用4.0代替4可以解决这个问题:

NSDate *start = [NSDate date];
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 4.0 * NSEC_PER_SEC);

dispatch_after(popTime, dispatch_get_main_queue(), ^{
  NSLog(@"seconds: %f", [start timeIntervalSinceNow]);
});
// output: seconds: -4.0001
为什么数字1和2正确转换为相应的double值,但更大的数字(我测试了3和4)似乎被表示为0
我正在使用配置为使用LLVM 3.0的Xcode 4.2进行编译。
编辑:
dispatch_time_t被定义为:
typedef uint64_t dispatch_time_t;

而 dispatch_time 是:

dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);

而 NSEC_PER_SEC 是:

#define NSEC_PER_SEC    1000000000  /* nanoseconds per second */

dispatch_time的参数是什么? - Pubby
看起来很奇怪,1,2可以工作而3,4不行...我会再检查一遍,但你确实会看到整数会被评估为0f的情况。 - Grady Player
3个回答

25

一秒钟有10亿纳秒,因此我假设NSEC_PER_SEC被定义为1000000000

  • 4的类型是int
  • 4.0的类型是double

现在假设一个int包含32位,那么int的范围将是[-2,147,483,648到2,147,483,647]

4000000000 > 2147483647,因此会导致int溢出,使得值被设置为0。

编辑:我可能可以更好地表述上面的声明。溢出可能导致int(假设它大小为32位,如上所述)等于值-294967296,并且dispatch_time将把任何值<= 0视为0秒。这就是上面的“0”来自哪里。

double变量可以容纳比int大的值,并能够存储4000000000的近似值。


1
当计算溢出时,您将得到环绕而不是零。这里的值可能会小于0,并且调度程序可能将其视为“立即执行”。 - Jon Hess
@JonHess:我的意思是溢出导致计时器被设置为0,我可能应该重新措辞。 - AusCBloke
@JonHess:我认为“溢出”比“环绕”更好,因为负值在无符号值方面比正值具有更大的无符号值(就位序列而言,4000000000适合于无符号32位整数)。 “环绕”会暗示位被截断/超过0000 ... - AusCBloke
在C标准中,有符号溢出是未定义行为。实际上,编译器的作者可能会确定一种可预测的行为,因此将其称为不可移植更有用。无论如何,在这种情况下使用double是不合适的,因为dispatch_time期望并且需要一个int64_t作为它的第二个参数。dispatch_time的手册建议如下:“如果不确定,请使用ull作为后缀。” - Alexei Sholik

7

前两种方法可行是因为1 * 10^9和2 * 10^9可以适应有符号32位整数。但是,4*10^9无法适应有符号32位整数。

由于浮点数可以表示该值,因此4.0 * 10^9可行。

我预计这种方法也可行:

dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, ((int64_t)4) * NSEC_PER_SEC);

2
我对Objective C一无所知,但我的猜测是4 * NSEC_PER_SEC对于32位整数来说太大了。通过使用4.0,您可以强制进行浮点运算并解决问题。
更新
可能是64位代码,但在某些语言中(我知道C#是这样的),数字文字默认为32位有符号整数,除非您明确定义它否则可能会出现此情况。这可能是这里发生的事情。

谢谢,"NSEC" 表示纳秒,所以它是一个很大的数字。我已经更新了我的问题并附上了它的定义。顺便说一下,这都是 64 位代码,虽然我不知道这是否有影响。 - Abhi Beckert
жҳҜзҡ„пјҢеңЁ32дҪҚUNIXжҲ–64дҪҚUNIXдёҠпјҢintжҳҜ32дҪҚгҖӮ - Anthony Blake
我不确定字面值,但在当前版本的Objective-C中,int至少是32位。根据@AusCBloke的答案,2秒钟只能刚好适合32位int,所以这就是发生的事情。苹果最近开始推动每个人使用新的NSInteger类型,当编译为64位CPU时,它是64位的(否则为32位)。我想他们有一天会将字面值切换到该类型。 - Abhi Beckert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接