Мне было интересно, можно ли при определенных условиях удалять ошибки с плавающей запятой, не прибегая к типам данных произвольной точности.
Проблема обычная. Язык Ruby, но он поддерживается на любом языке:
f = 1829.82
=> 1829.82
f / 12.0
=> 152.485
(f / 12.0).round(2)
=> 152.48
Почему не 152,49? Потому что из-за конечной точности с плавающей точкой:
format("%18.14f", f)
=> "1829.81999999999994"
format("%18.14f", f / 12.0)
=> "152.48499999999999"
Итак, округление правильное. Теперь мой вопрос: есть ли способ получить ответ, который я хочу, в любом случае, учитывая следующие обстоятельства: существуют строгие ограничения на (число) операций, выполняемых с использованием float, необходимая точность ограничена двумя десятичными разрядами (максимум 8 цифр) в целом) и небольшое количество оставшихся «неправильных» округленных ответов приемлемо?
Ситуация такова, что пользователи могут вводить допустимые строки Ruby, такие как:
"foo / 12.0"
где foo - это число, указанное в контексте, в котором выполняется строка, но где '12 .0 '- это то, что вводит пользователь. Представьте себе электронную таблицу с некоторыми свободными полями формул. Строки просто оцениваются как Ruby, поэтому 12.0 становится Float. Я мог бы использовать гемы ruby_parser + ruby2ruby для построения дерева разбора, переноса типа данных в Bignum, Rational, что-то из библиотеки Flt, десятичных представлений с плавающей запятой или что-то-у-вас, но это сложно, так как фактические строки могут стать несколько сложнее, поэтому я предпочитаю не идти по этому пути. Я пойду по этому пути, если больше ничего не возможно, но этот вопрос специально здесь, чтобы посмотреть, смогу ли я избежать этого пути. Таким образом, тип данных 12.0 - строго Float, а результат - строго Float, и единственное, что я могу сделать, это интерпретировать окончательный ответ фрагмента и попытаться «исправить» его, если он округляет «неправильный» путь.
Единственные вычисления, которые пользователи выполняют, включают числа с точностью до двух десятичных цифр (и всего не более 8 цифр). Под «простым» я подразумеваю, что ошибки с плавающей запятой не получают шансов накапливаться: я могу добавить два из этих чисел и разделить одно на целое, но затем вычисление будет выполнено, результат округлен и сохранен, и любой последующий расчет основан на этом округленном числе. Обычно будет иметь место только одна ошибка с плавающей запятой, но я думаю, что проблема существенно не изменится, если две могут накапливаться, хотя коэффициент остаточных ошибок может быть больше по определению.
Что может сначала прийти на ум, это сначала округлить до 3 десятичных цифр, а затем до 2. Однако это не работает. Это привело бы к
152.48499999999999 => 152.485 => 152.49
но также
152.4846 => 152.485 => 152.49
это не то, что вы хотите.
Что мне пришло в голову, это добавление наименьшего возможного приращения (которое, как указывали люди, зависит от рассматриваемого значения с плавающей запятой) к плавающему значению , если , которое смещает его на 0,5 границы. Мне в основном интересно, как часто это может приводить к «ложному положительному результату»: числу, к которому добавляется наименьшее приращение, даже если тот факт, что он был чуть ниже границы .5, был вызван не ошибкой с плавающей запятой, а потому что это был просто результат расчета?
Второй вариант: просто всегда добавьте наименьшее приращение к числам, поскольку регион .5 является единственным, где он в любом случае имеет значение.
Edit:
Я просто переписал вопрос, чтобы включить часть моих ответов в комментарии, как предложил cdiggins. Я наградил Иру Бакстера за его активное участие в обсуждении, хотя я еще не уверен, что он прав: Марк Рэнсом и Эмилио М. Бумачар, кажется, поддерживают мою идею о том, что исправление возможно, что на практике, возможно, в относительно большом большинстве случаев дают «правильный» результат.
Мне все еще нужно провести эксперимент, чтобы увидеть, как часто результат будет правильным, и я полностью намерен это сделать, но время, которое я могу потратить на это, несколько ограничено, поэтому я еще не дошел до этого. Эксперимент не тривиален.