Проблема с двойным кастингом

Я разработчик 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 правильно преобразуются в соответствующее двойное значение, но большие числа (я тестировал 3 и 4) представляются как 0?

Я компилирую с помощью Xcode 4.2, настроенного на использование LLVM 3.0.

РЕДАКТИРОВАТЬ:

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 */

person Abhi Beckert    schedule 07.12.2011    source источник
comment
Что такое параметр dispatch_time?   -  person Pubby    schedule 08.12.2011
comment
Спасибо @Pubby, я обновил вопрос.   -  person Abhi Beckert    schedule 08.12.2011
comment
кажется очень странным, что 1,2 работают, а 3,4 нет... Я бы еще раз проверил это, но вы, безусловно, видите случаи, когда целые числа оцениваются как 0f.   -  person Grady Player    schedule 08.12.2011


Ответы (3)


В секунде 1 000 000 000 наносекунд, поэтому я предполагаю, что NSEC_PER_SEC определяется как 1000000000.

  • 4 относится к типу int
  • 4.0 относится к типу double

Теперь предположим, что int содержит 32 бита, диапазон int будет [-2,147,483,648 to 2,147,483,647]

4000000000 > 2147483647, поэтому вы вызовете переполнение int, в результате чего значение будет установлено на 0.

EDIT: я, вероятно, мог бы лучше сформулировать приведенное выше утверждение. Переполнение может привести к тому, что int (при условии, что оно имеет размер 32 бита, как указано выше) будет равно значению -294967296, а dispatch_time будет рассматривать любое значение <= 0 как 0 секунд. Вот откуда взялся «0» выше.

Переменная double может содержать большие значения, чем int, и может хранить приблизительное значение 4000000000.

person AusCBloke    schedule 07.12.2011
comment
Когда расчет переполнится, вы получите цикл, а не ноль. Значение здесь, вероятно, будет ‹ 0, и диспетчер, вероятно, рассматривает это как «сделать это немедленно». - person Jon Hess; 08.12.2011
comment
@JonHess: я имел в виду, что переполнение приводит к установке таймера на 0, вероятно, мне следует перефразировать его. - person AusCBloke; 08.12.2011
comment
@JonHess: И я думаю, что переполнение - лучшее выражение, чем перенос, поскольку отрицательные значения имеют большее беззнаковое значение, чем положительные (с точки зрения последовательности битов, а 4000000000 вписывается в 32-битное целое число без знака). обертывание означает, что биты обрезаются/обертываются после 0000... - person AusCBloke; 08.12.2011
comment
Знаковое переполнение — это неопределенное поведение в стандарте C. На практике составители компиляторов могут остановиться на предсказуемом поведении, поэтому полезнее называть его непереносимым. Использование double в этом случае в любом случае неуместно, так как dispatch_time ожидает и int64_t в качестве второго аргумента. На справочных страницах для dispatch_time есть следующее предложение: Если вы сомневаетесь, используйте ull в качестве суффикса. - person Alexei Sholik; 17.04.2015

Первые два работают, потому что 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);
person Anthony Blake    schedule 07.12.2011

Я ничего не знаю о Objective C, но я предполагаю, что 4 * NSEC_PER_SEC слишком велико для 32-битного целого числа. Используя 4.0, вы заставляете умножение выполнять арифметику с плавающей запятой и решаете проблему.

Обновить

Это может быть 64-битный код, но в некоторых языках (и я знаю, что C# является одним из них) числовой литерал по умолчанию равен 32-битному целому числу со знаком, если вы явно не определите иное. Может быть, это то, что здесь происходит.

person Andrew Cooper    schedule 07.12.2011
comment
Спасибо, NSEC — это наносекунды, так что это большое число. Я обновил свой вопрос с его определением. Кстати, это весь 64-битный код, хотя я не знаю, имеет ли это значение. - person Abhi Beckert; 08.12.2011
comment
Да, в 32-разрядной UNIX или 64-разрядной версии UNIX int является 32-разрядным. - person Anthony Blake; 08.12.2011
comment
Я не уверен насчет литералов, но int определенно 32-битный, по крайней мере, в текущей версии Objective-C. Основываясь на ответе @AusCBloke, 2 секунды уместились бы только в 32-битном int, так что, должно быть, это то, что происходит. Apple недавно начала подталкивать всех к использованию нового типа NSInteger, который является 64-битным при компиляции для 64-битного процессора (в противном случае он будет 32-битным). Я полагаю, что однажды они переведут литералы на этот тип. - person Abhi Beckert; 08.12.2011