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


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

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

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

4 54

4 ответа:

The слон в комнате заключается в том, что диапазон 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 типы. Многие люди считают --> и ++< как запутывающие, но в научном программировании они распространены, особенно -->.)

теперь на моей машине, двойной является IEEE754 64 бит с плавающей точкой. (Например, схема особенно хорошо представляет полномочия 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 - это тип с плавающей запятой. Если компилятору было разрешено рассматривать литерал как интегральный тип, то выходные данные двух циклов будут эквивалентны.

Он будет работать, предполагая, что ваш int по крайней мере 32 бита.

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

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

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

const int  MAX_INDEX = 10000000;

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

const int  MAX_INDEX = 10'000'000;

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

таким образом, на практике это безопасно на текущих настольных системах и больше.

но C++ позволяет int чтобы быть всего 16 бит, и, например, встроенная система с этим маленьким int, один будет иметь неопределенное поведение.

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

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

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

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