C # плавать бесконечный цикл - PullRequest
15 голосов
/ 08 февраля 2010

Следующий код в C # (.Net 3.5 SP1) представляет собой бесконечный цикл на моей машине:

for (float i = 0; i < float.MaxValue; i++) ;

Достигнуто число 16777216.0, а 16777216.0 + 1 соответствует 16777216.0. И все же на этом этапе: я + 1! = Я.

Это какое-то сумасшествие.

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

Тем не менее приведенный выше код должен быть действительным в C #, даже если число не может быть правильно представлено.

Почему это не работает?

Вы можете получить то же самое, что и для двойника, но это займет очень много времени. 9007199254740992.0 - предел для двойного.

Ответы [ 5 ]

21 голосов
/ 08 февраля 2010

Правильно, поэтому проблема в том, что для добавления одного в число поплавков он должен был бы стать

16777217.0

Просто так получилось, что он находится на границе радиуса и не может быть представлен точно как поплавок. (Следующее наибольшее доступное значение - 16777218.0)

Итак, он округляется до ближайшего представимого числа с плавающей точкой

16777216.0

Позвольте мне выразиться так:

Поскольку у вас есть плавающая величина точности, вы должны увеличиваться на большее и большее число.

EDIT:

Хорошо, это немного сложно объяснить, но попробуйте это:

float f = float.MaxValue;
f -= 1.0f;
Debug.Assert(f == float.MaxValue);

Это будет работать просто отлично, потому что при этом значении, чтобы представить разницу в 1.0f, вам потребуется более 128 бит точности. У поплавка всего 32 бита.

EDIT2

По моим расчетам понадобилось бы не менее 128 двоичных цифр без знака .

log(3.40282347E+38) * log(10) / log(2) = 128

В качестве решения вашей проблемы вы можете перебрать два 128-битных числа. Однако на это уйдет не менее десяти лет.

8 голосов
/ 08 февраля 2010

Представьте, например, что число с плавающей запятой представлено максимум двумя значащими десятичными цифрами плюс показатель степени: в этом случае вы можете точно посчитать от 0 до 99. Следующим будет 100, но, поскольку вы можете иметь только 2 значащие цифры, которые будут сохранены как «1,0 от 10 до степени 2». Добавление одного к этому было бы ... что?

В лучшем случае это будет 101 в качестве промежуточного результата, который фактически будет сохранен (из-за ошибки округления, которая отбрасывает незначительную 3-ю цифру) как «1,0 от 10 до степени 2» снова.

6 голосов
/ 08 февраля 2010

Чтобы понять, что происходит не так, вам нужно прочитать стандарт IEEE по с плавающей запятой

Давайте рассмотрим структуру числа с плавающей запятой на секунду:

Число с плавающей запятой разбито на две части (хорошо 3, но игнорируйте бит знака в течение секунды).

У вас есть показатель и мантисса. Вот так:

smmmmmmmmeeeeeee

Примечание: это не точное количество битов, но дает общее представление о том, что происходит.

Чтобы выяснить, какое у вас число, мы сделаем следующий расчет:

mmmmmm * 2^(eeeeee) * (-1)^s

Так что же такое float.MaxValue? Ну, у вас будет максимально возможная мантисса и максимально возможный показатель. Давайте представим, что это выглядит примерно так:

01111111111111111

на самом деле мы определяем NAN и + -INF и пару других соглашений, но игнорируем их на секунду, потому что они не имеют отношения к вашему вопросу.

Итак, что происходит, когда у вас есть 9.9999*2^99 + 1? Ну, вам не хватает значащих цифр для добавления 1. В результате оно округляется до того же числа. В случае единой точности с плавающей запятой точка, в которой +1 начинает округляться, оказывается 16777216.0

2 голосов
/ 09 февраля 2010

Это не имеет ничего общего с переполнением или близостью к максимальному значению. Значение с плавающей запятой для 16777216.0 имеет двоичное представление 16777216. Затем вы увеличиваете его на 1, поэтому оно должно быть 16777217.0, за исключением того, что двоичное представление 16777217.0 равно 16777216 !!! Таким образом, он на самом деле не увеличивается или, по крайней мере, увеличение не делает то, что вы ожидаете.

Вот класс, написанный Джоном Скитом, который иллюстрирует это:

DoubleConverter.cs

Попробуйте этот код с ним:

double d1 = 16777217.0;
Console.WriteLine(DoubleConverter.ToExactString(d1));

float f1 = 16777216.0f;
Console.WriteLine(DoubleConverter.ToExactString(f1));

float f2 = 16777217.0f;
Console.WriteLine(DoubleConverter.ToExactString(f2));

Обратите внимание, что внутреннее представление 16777216.0 такое же, как 16777217.0 !!

0 голосов
/ 08 февраля 2010

Итерация при приближении к float. MaxValue имеет значение чуть ниже этого значения. Следующая итерация добавляет к i, но она не может содержать число больше, чем float.MaxValue. Таким образом, он имеет гораздо меньшее значение и снова начинает цикл.

...