Подтвердить что ты не робот

Использование научной нотации для циклов

Недавно я столкнулся с некоторым кодом, который имеет петлю формы

for (int i = 0; i < 1e7; i++){
}

Я сомневаюсь в мудрости этого, так как 1е7 является типом с плавающей запятой и приведет к повышению i при оценке условия остановки. Должно ли это быть причиной для беспокойства?

4b9b3361

Ответ 1

слон в комнате здесь состоит в том, что диапазон int может быть от -32767 до +32767, а поведение при назначении большего значения, чем это, для такого int undefined.

Но, что касается вашего основного момента, действительно, это должно касаться вас, поскольку это очень плохая привычка. Вещи могут пойти не так, как да, 1e7 - двойной тип с плавающей точкой.

Тот факт, что i будет преобразован в плавающую точку из-за правил продвижения по типу, является несколько спорным: реальный урон выполняется, если есть неожиданное усечение видимого интегрального литерала. Кстати, "доказательство на примере", сначала рассмотрим цикл

for (std::uint64_t i = std::numeric_limits<std::uint64_t>::max() - 1024; i ++< 18446744073709551615ULL; ){
    std::cout << i << "\n";
}

Это выводит каждое последовательное значение i в диапазоне, как и следовало ожидать. Обратите внимание, что std::numeric_limits<std::uint64_t>::max() равно 18446744073709551615ULL, что на 1 меньше, чем 64-я степень 2. (Здесь я использую слайд-подобный "оператор" ++<, который полезен при работе с типами unsigned. рассмотрите --> и ++< как запутывание, но в научном программировании они являются общими, особенно -->.)

Теперь на моей машине двойной - 64-разрядная плавающая точка IEEE754. (Например, схема особенно хороша при представлении степеней 2 точно - IEEE754 может точно представлять степени от 2 до 1022.) Таким образом, 18,446,744,073,709,551,616 (64-я степень 2) может быть представлена ​​точно как двойная. Ближайшее представимое число до этого 18,446,744,073,709,550,592 (которое меньше 1024).

Итак, теперь напишите цикл как

for (std::uint64_t i = std::numeric_limits<std::uint64_t>::max() - 1024; i ++< 1.8446744073709551615e19; ){
    std::cout << i << "\n";         
}

На моей машине будет выводиться только одно значение i: 18,446,744,073,709,550,592 (номер, который мы уже видели). Это доказывает, что 1.8446744073709551615e19 - тип с плавающей точкой. Если компилятору было разрешено обрабатывать литерал как интегральный тип, то выход двух циклов был бы эквивалентен.

Ответ 2

Он будет работать, считая, что ваш int не менее 32 бит.

Однако, если вы действительно хотите использовать экспоненциальную нотацию, вам лучше определить целочисленную константу вне цикла и использовать правильное кастинг, например:

const int MAX_INDEX = static_cast<int>(1.0e7);
...
for (int i = 0; i < MAX_INDEX; i++) {
    ...
}

Учитывая это, я бы сказал, что гораздо лучше писать

const int  MAX_INDEX = 10000000;

или если вы можете использовать С++ 14

const int  MAX_INDEX = 10'000'000;

Ответ 3

1e7 является литералом типа double, и обычно double - это 64-разрядный формат IEEE 754 с 52-битной мантиссой. Примерно каждая десятая степень 2 соответствует третьей степени 10, поэтому double должна иметь возможность представлять целые числа не менее 10 5 * 3= 10 15 в точку. И если int - 32-бит, то int имеет примерно 10 3 * 3= 10 9 как максимальное значение (при запросе в Google он говорит, что "2 ** 31 - 1" = 2 147 483 647, т.е. Дважды грубая оценка).

Таким образом, на практике он безопасен для существующих настольных систем и больше.

Но С++ позволяет int быть только 16 битами, а напр. встроенная система с этим небольшим int, у вас будет Undefined Behavior.

Ответ 4

Если намерение петли для точного целого числа итераций, например, если итерация по точно всем элементам в массиве, то сравнение с значением с плавающей запятой, возможно, не такая хорошая идея, исключительно по соображениям точности; так как неявный листинг целого числа для float будет усекать целые числа в нуль, там нет реальной опасности доступа за пределы границ, он просто прервет короткий цикл.

Теперь возникает вопрос: когда эти эффекты на самом деле вступают? Будет ли ваша программа работать с ними? Представление с плавающей запятой, обычно используемое в наши дни, это IEEE 754. Пока показатель степени равен 0, значение с плавающей запятой по существу является целым числом. С двойной точностью плавает 52 бит для мантиссы, что дает вам целую точность до значения до 2 ^ 52, которая составляет порядка 1е15. Не указывая с суффиксом f, что вы хотите, чтобы литерал с плавающей точкой интерпретировался как единая точность, литерал будет двойной точностью, и неявное преобразование также будет нацелено на это. До тех пор, пока ваше конечное условие цикла меньше 2 ^ 52, оно будет работать надежно!

Теперь один вопрос, о котором вы должны думать о архитектуре x86, - это эффективность. Самые первые 80x87 FPU появились в другом пакете, а позже другой чип и как получение значений в регистрах FPU немного неудобно на уровне сборки x86. В зависимости от ваших намерений это может повлиять на время выполнения приложения в реальном времени; но это преждевременная оптимизация.

TL; DR: Безопасно ли это? Конечно, да. Это вызовет проблемы? Это может вызвать численные проблемы. Может ли он вызвать поведение undefined? Зависит от того, как вы используете условие конца цикла, но если i используется для индексации массива, и по какой-то причине длина массива оказалась в переменной с плавающей запятой, всегда усекающейся к нулю, это не приведет к возникновению логической проблемы. Это разумная вещь? Зависит от приложения.