Вопрос

Следующий код на 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 — это предел для двойного значения.

Это было полезно?

Решение

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

16777217.0

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

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

16777216.0

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

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

РЕДАКТИРОВАТЬ:

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

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

Это будет работать нормально, потому что при этом значении, чтобы представить разницу в 1,0f, вам потребуется более 128 бит точности.Число с плавающей запятой имеет только 32 бита.

РЕДАКТИРОВАТЬ2

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

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

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

Другие советы

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

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

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

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

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

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

smmmmmmmmeeeeeee

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

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

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

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

01111111111111111

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

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

Это не имеет ничего общего с переполнением или близостью к максимальному значению.Число с плавающей запятой для 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, но она не может содержать число больше, чем float.MaxValue.Таким образом, он сохраняет значение, намного меньшее, и цикл начинается снова.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top