Как на самом деле избежать ошибок с плавающей запятой, когда вам нужно использовать float?

Я пытаюсь повлиять на перевод 3D-модели с помощью некоторых кнопок пользовательского интерфейса, чтобы сместить положение на 0,1 или -0,1.

Моя позиция модели - это трехмерное поплавок, поэтому простое добавление 0,1f к одному из значений вызывает очевидные ошибки округления. Хотя я могу использовать что-то вроде BigDecimal для сохранения точности, мне все равно нужно преобразовать его из числа с плавающей запятой и обратно в число с плавающей запятой в конце, и это всегда приводит к глупым числам, из-за которых мой пользовательский интерфейс выглядит беспорядочно.

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

Итак, как мне на самом деле избежать этих ошибок, когда мне нужно использовать поплавок?


person Caustic    schedule 21.04.2013    source источник
comment
Сложение и вычитание .1f несколько раз не приведет к огромным ошибкам. Наиболее заметным было бы одно изменение на единицу, например, когда желаемый результат был четыре, но было получено что-то немного меньшее и усеченное до трех во время преобразования в целое число. Это можно исправить округлением перед преобразованием. Если вы получаете другие ошибки, возможно, что-то еще не так. Пожалуйста, покажите примеры данных и кода, иллюстрирующие проблемы, с которыми вы столкнулись.   -  person Eric Postpischil    schedule 22.04.2013
comment
Самый простой: увеличить / уменьшить на 0,125.   -  person Thorsten S.    schedule 22.04.2013


Ответы (4)


Я бы использовал класс Rational. Их много - этот похоже, что он должен работать.

Одна значительная стоимость будет связана с преобразованием Rational в float, а другая - с уменьшением знаменателя до gcd. Тот, который я опубликовал, сохраняет числитель и знаменатель в полностью сокращенном состоянии, что должно быть довольно эффективным, если вы всегда добавляете или вычитаете 1/10.

Эта реализация содержит нормализованные значения (т. е. с согласованным знаком), но невосстановленный.

Вам следует выбрать такую ​​реализацию, которая наилучшим образом соответствует вашему использованию.

person OldCurmudgeon    schedule 21.04.2013

суммирование Кахана и попарного суммирования помогают уменьшить количество ошибок с плавающей запятой. Вот код Java для алгоритма Кахана.

person Zim-Zam O'Pootertoot    schedule 21.04.2013
comment
Вопрос сообщается с использованием float. Переход на удвоение обеспечит большее сокращение ошибок, чем алгоритм суммирования Кахана, и будет более дешевым (на большинстве аппаратных средств). Однако ни один из методов не решит ошибки, возникающие при преобразовании очень незначительно неправильных результатов в целые числа путем усечения. - person Eric Postpischil; 22.04.2013

Простое решение - использовать фиксированную точность. то есть целое число в 10 или 100 раз больше, чем вы хотите.

float f = 10;
f += 0.1f;

становится

int i = 100;
i += 1;  // use an many times as you like
// use i / 10.0 as required.

Я бы ни в коем случае не стал использовать float, так как вы получаете больше ошибок округления, чем double, что практически бесполезно (если у вас нет миллионов значений с плавающей запятой) double дает вам еще 8 цифр точности, а при разумном округлении эти ошибки не будут видны. .

person Peter Lawrey    schedule 21.04.2013

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

раунд (2 ^ n * значение) * 1/2 ^ n.

n - количество бит, значение - число, которое нужно использовать (в вашем случае 0,1)

В вашем случае с повышением точности:

n = 4 => 0,125
n = 8 (байт) => 0,9765625
n = 16 (короткий) => 0,100006103516 ....

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

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

Если вы опасаетесь, что ваш дисплей будет скомпрометирован при использовании этого решения (потому что это нечетные числа с плавающей запятой), используйте и храните только целые числа (шаг увеличения -1/1). Конечное внутреннее значение -

x = значение * шаг.

При увеличении или уменьшении шага на 1 точность будет сохраняться.

person Thorsten S.    schedule 21.04.2013
comment
Каковы ваши намерения здесь? Хотя это устраняет ошибки во время сложения и вычитания, но в определенных пределах увеличивает общую ошибку, поскольку используемый размер шага (.100006…) отличается от желаемого размера шага. Это отличает рассчитанные результаты от желаемых. Как вы ожидаете получить желаемые результаты? - person Eric Postpischil; 22.04.2013
comment
Потому что обычно не имеет значения, является ли цель вычисления равной 0,1 или 0,100 ... В этом конкретном случае 3D-модель увеличивается или уменьшается на определенную величину. Я использую этот трюк, например, в интервалах интегрирования, потому что добавление происходит намного быстрее и точнее, если начальная позиция и интервал увеличения являются точным числом с плавающей точкой. На самом деле я бы посоветовал использовать 0,125, потому что это означает, что с 8 шагами у меня снова целочисленное значение, и вполне естественно разделить 1,0 на восемь шагов. - person Thorsten S.; 22.04.2013
comment
Этот ответ не объясняет сам себя. Очевидно, намерение состоит в том, чтобы изменить размер шага, а не исправлять ошибки, которые возникают с первоначально предложенным размером шага, но из постановки задачи не ясно, приемлем ли измененный размер шага. Очевидно, это меняет поведение программы: с измененным размером шага больше не существует ровно десяти шагов с 3 до 4. Возможно, это будет приемлемо для спрашивающего, а может, и нет. Но ответ, безусловно, должен вводить предложение и объяснять, что оно меняет наблюдаемое поведение, и устранять последствия этого. - person Eric Postpischil; 22.04.2013