Циклический бесконечный цикл С#
Следующий код в С# (.Net 3.5 SP1) - это бесконечный цикл на моей машине:
for (float i = 0; i < float.MaxValue; i++) ;
Он достиг номера 16777216.0 и 16777216.0 + 1 оценивается до 16777216.0. Однако на данный момент: я + 1!= I.
Это безумие.
Я понимаю, что есть некоторая неточность в том, как хранятся числа с плавающей запятой. И я читал, что целые числа больше 2 ^ 24, чем не могут быть правильно сохранены как плавающие.
Тем не менее, код выше, должен быть действительным на С#, даже если номер не может быть правильно представлен.
Почему это не работает?
Вы можете получить то же самое, что и в случае двойного, но это занимает очень много времени. 9007199254740992.0 - это предел для двойного.
Ответы
Ответ 1
Правильно, проблема заключается в том, что для добавления одного к float он должен стать
16777217.0
Так получилось, что это на границе радиуса и не может быть представлено точно как float. (Следующим самым высоким значением является 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-битных номера. Однако для этого потребуется не менее десятилетия.
Ответ 2
Предположим, например, что число с плавающей запятой представлено до двух значащих десятичных цифр плюс показатель степени: в этом случае вы можете рассчитывать от 0 до 99 точно. Следующий будет 100, но потому, что у вас может быть только 2 значащих цифры, которые будут храниться как "1.0 раза 10 до 2". Добавление одного к этому было бы... что?
В лучшем случае это будет 101 как промежуточный результат, который фактически будет сохранен (через ошибку округления, которая отбрасывает незначительную третью цифру) как "1,0 раз 10 до мощности 2".
Ответ 3
Чтобы понять, что пойдет не так, вам нужно будет прочитать стандарт IEEE на плавающей запятой
Рассмотрим структуру число с плавающей запятой за секунду:
Число с плавающей запятой разбивается на две части (ok 3, но игнорировать бит знака на секунду).
У вас есть экспонента и мантисса. Например:
smmmmmmmmeeeeeee
Примечание: это не соответствует количеству бит, но дает общее представление о том, что происходит.
Чтобы выяснить, какой у вас номер, мы делаем следующий расчет:
mmmmmm * 2^(eeeeee) * (-1)^s
Итак, что такое float.MaxValue? Ну, у вас будет самая большая возможная мантисса и самый большой возможный показатель. Представьте, что это выглядит примерно так:
01111111111111111
В действительности мы определяем NAN и + -INF и пару других соглашений, но игнорируем их на секунду, потому что они не имеют отношения к вашему вопросу.
Итак, что происходит, когда у вас есть 9.9999*2^99 + 1
? Ну, у вас недостаточно значительных цифр, чтобы добавить 1. В результате он округляется до одного и того же номера. В случае единственной точности с плавающей запятой точка, в которой +1
начинает округляться, оказывается 16777216.0
Ответ 4
Он не имеет ничего общего с переполнением или близок к максимальному значению. Значение float для 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!!
Ответ 5
Итерация, когда я приближаюсь к float.MaxValue, имеет значение чуть ниже этого значения. Следующая итерация добавляет i, но не может содержать число, большее, чем float.MaxValue. Таким образом, он сохраняет значение намного меньше и снова начинает цикл.