Понимание Гуру Недели № 67: Двойной или Ничего

Недавно я читал пост: Двойной или ничего от 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 неопределенное поведение?

Спасибо!

8

Решение

Ключевое слово «точно».

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,

9

Другие решения

Попробуйте эту простую модификацию, которая рассчитывает значение последовательных значений x.

#include <iostream>
using namespace std;

int main()
{
float x = 1e8;
while( x > 0 )
{
cout << x << endl;
--x;
}
}

В некоторых реализациях float вы увидите, что значения float находятся в 1e8 или в этом регионе. Это из-за способа хранения чисел с плавающей точкой. Число с плавающей запятой не может (и не может представлять какое-либо битовое представление) представлять все возможные десятичные значения, поэтому, когда вы имеете дело с очень большими значениями с плавающей запятой, вы, по сути, имеете десятичное число, увеличенное до некоторой степени. Хорошо, если это десятичное значение заканчивается значением, где последний бит выпадает, значит, оно округляется в большую сторону. В результате вы получаете значение, которое уменьшается (затем обратно) до самого себя.

0

По вопросам рекламы [email protected]