Увеличение значения в цикле дает другое (неправильное) значение?

Я потратил почти весь день, пытаясь выяснить, почему это происходит, у меня был код, который увеличивал число с плавающей запятой, если оно не было целым числом, пока оно не стало целым числом, я запускал его, увеличивая значение на 0,01 и цикл продолжает увеличивать его на 0,01, пока оно не станет целым числом, но никогда не останавливается, отлаживая его, я заметил, что число становится все более неточным по мере продвижения цикла, что приводит к тому, что к концу оно составляет 0,0000066.
Я подумал, может быть, переменная могла иметь к нему каким-то образом обращались в другом месте, поэтому я создал его просто в пустой "основной" программе:

float value = 0;
for (int i=0; i<100; i++) {
    value += 0.01f;
}
System.out.println(value); // Displays 0.99999934
System.out.println(value == 1); // Just in case it's displaying wrong in println (displays false)

Почему до конца не 1? Я не думаю, что это может иметь какое-либо отношение к типам данных, поскольку я добавляю float к float.


person Lee Fogg    schedule 25.07.2013    source источник
comment
@MadProgrammer 0-99 по-прежнему составляет 100 итераций. 100 * 0,01 = 1   -  person DannyMo    schedule 26.07.2013
comment
@MadProgrammer Нет, 100 раз как положено. Выполнение <= 100 приведет к 101 итерации.   -  person Dennis Meng    schedule 26.07.2013
comment
Короче говоря: не проверяйте равенство чисел с плавающей запятой или удвоением. Как вы только что обнаружили, это (обычно) не работает. Вот как работают эти типы данных. Чтобы узнать подробности, проверьте ссылку, предоставленную @RohitJain.   -  person Jan Dörrenhaus    schedule 26.07.2013
comment
@JanDoerrenhaus Короткая версия истории - неудачный ярлык. Равенство - единственная работающая часть программы вопроса. 0.01f не «работает», и + не «работает», но == работает отлично. «Не используйте 0.01f и +, если вы не понимаете, как они себя ведут» было бы намного лучше, чем «Не используйте ==».   -  person Pascal Cuoq    schedule 29.07.2013
comment
@JanDoerrenhaus Например, изменение value += 0.01f; на value = i * 0.01f; делает условие value == 1 истинным после цикла. == никогда не ошибался в этом состоянии, только использовались 0.01f и +. Замена этого использования опасных конструкций на что-то более продуманное исправляет программу.   -  person Pascal Cuoq    schedule 29.07.2013


Ответы (2)


Числа с плавающей запятой в Java соответствуют стандартам с плавающей запятой IEEE, и это означает, что такое значение, как 0,01, не может быть представлены идеально (это повторяющееся десятичное число в двоичном формате). Есть небольшая ошибка, которая не видна при отображении только 0,01 (она округляется при преобразовании в String), но ошибки накапливаются при каждом добавлении (или любой другой математической операции); вы проявили видимый эффект.

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

person rgettman    schedule 25.07.2013
comment
Я думаю, что буду использовать целые числа, а затем делю. Я должен помнить об этом, я бы определенно хотел, чтобы этого не произошло ... - person Lee Fogg; 26.07.2013

Да, это проблема с спецификацией IEEE для чисел с плавающей запятой. Если в вашем приложении важна точность, вы должны вместо этого использовать BigDecimal

person Michael Lang    schedule 25.07.2013