C# float бесконечный цикл

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

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

он достиг числа 16777216.0 и 16777216.0 + 1 оценивается в 16777216.0. Но в этот момент: i + 1 != i.

это какое-то безумие.

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

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

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

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

5 ответов


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

16777217.0

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

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

16777216.0

позвольте мне сказать так:

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

EDIT:

хорошо, это немного трудно объяснить, но попробуйте следующее:

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

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

EDIT2

по моим расчетам, по крайней мере 128 двоичных цифр без подписи будет необходимый.

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

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


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

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


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

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

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

у вас есть экспонента и мантисса. Вот так:

smmmmmmmmeeeeeee

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

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

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

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

01111111111111111

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

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


это не имеет ничего общего с переполнением или близостью к максимальному значению. Значение 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!!


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