Недавно я читал пост: Двойной или ничего от GOTW Херб Саттер
Я немного запутался с объяснением следующей программы:
int main()
{
double x = 1e8;
while( x > 0 )
{
--x;
}
}
Предположим, что этот код выполняется 1 секунда на некотором компьютере. Я согласен с тем, что такой код глупо.
Тем не менее, в соответствии с объяснением о проблеме, если мы изменим x
от float
в double
затем на некоторых компиляторах компьютер будет работать вечно. Объяснение основано на следующей цитате из стандарта.
Цитирование из раздела 3.9.1 / 8 стандарта C ++:
Существует три типа с плавающей точкой: float, double и long double. Тип double обеспечивает, по крайней мере, такую же точность, как и float, а тип long double обеспечивает, по крайней мере, такую же точность, что и double. Набор значений типа float является подмножеством набора значений типа double; набор значений типа double является подмножеством набора значений типа long double.
Вопрос для кода:
Сколько времени вы ожидаете, если вы измените «double» на «float»? Зачем?
Вот объяснение:
Вероятно, это займет около 1 секунды (в конкретной реализации операции с плавающей запятой могут быть несколько быстрее, быстрее или медленнее, чем удваивается), или навсегда, в зависимости от того, может ли функция с плавающей запятой точно представлять все целочисленные значения от 0 до 1e8 включительно.
Приведенная выше цитата из стандарта означает, что могут быть значения, которые могут быть представлены двойным, но которые не могут быть представлены с плавающей точкой. В частности, на некоторых популярных платформах и компиляторах double может точно представлять все целочисленные значения в [0,1e8], но float не может.
Что если float не может точно представлять все целочисленные значения от 0 до 1e8? Затем измененная программа начнет обратный отсчет, но в конечном итоге достигнет значения N, которое невозможно представить и для которого N-1 == N (из-за недостаточной точности с плавающей запятой) … и
Мой вопрос:
Если поплавок даже не может представлять 1e8
, тогда мы должны иметь переполнение уже при инициализации float x = 1e8
; тогда почему мы заставим компьютер работать вечно?
Я попробовал простой пример здесь (хотя не double
но int
)
#include <iostream>
int main()
{
int a = 4444444444444444444;
std::cout << "a " << a << std::endl;
return 0;
}
It outputs: a -1357789412
Это означает, что если компилятор не может представить данное число с int
типа, это приведет к переполнению.
Так я неправильно прочитал? Какой момент я упустил? Меняется x
от double
в float
неопределенное поведение?
Спасибо!
Ключевое слово «точно».
float
может представлять 1e8
даже точно, если у вас нет урода float
тип. Но это не значит, что он может точно представлять все меньшие значения, например, обычно 2^25+1 = 33554433
, который нуждается в 26 битах точности, не может быть точно представлен в float
(обычно это имеет 23 + 1 бит точности), и не может 2^25-1 = 33554431
, который нуждается в 25 битах точности.
Оба эти числа затем представлены в виде 2^25 = 33554432
, а потом
33554432.0f - 1 == 33554432.0f
будет цикл (Вы попадете в цикл раньше, но у этого есть хорошее десятичное представление;)
В целочисленной арифметике у вас есть x - 1 != x
для всех x
, но не в арифметике с плавающей точкой.
Обратите внимание, что цикл также может закончиться, даже если float
имеет только обычные 23 + 1 бит точности, так как стандарт позволяет выполнять вычисления с плавающей запятой с большей точностью, чем тип, и если вычисления выполняются с достаточно большей точностью (например, обычный double
с 52 + 1 бит), каждое вычитание изменится x
,
Попробуйте эту простую модификацию, которая рассчитывает значение последовательных значений x.
#include <iostream>
using namespace std;
int main()
{
float x = 1e8;
while( x > 0 )
{
cout << x << endl;
--x;
}
}
В некоторых реализациях float вы увидите, что значения float находятся в 1e8 или в этом регионе. Это из-за способа хранения чисел с плавающей точкой. Число с плавающей запятой не может (и не может представлять какое-либо битовое представление) представлять все возможные десятичные значения, поэтому, когда вы имеете дело с очень большими значениями с плавающей запятой, вы, по сути, имеете десятичное число, увеличенное до некоторой степени. Хорошо, если это десятичное значение заканчивается значением, где последний бит выпадает, значит, оно округляется в большую сторону. В результате вы получаете значение, которое уменьшается (затем обратно) до самого себя.