Мне было интересно, можно ли при определенных условиях удалить ошибки с плавающей запятой, не обращаясь к типам данных произвольной точности.
Проблема обычна. Язык 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 gems для создания дерева синтаксического анализа, кастовать тип данных в Bignum, Rational, что-то из библиотеки Flt, десятичных представлений с плавающей запятой или того, что есть, но это сложно, поскольку фактические строки могут стать несколько сложнее, поэтому я предпочитаю не идти этим путем. Я поеду туда, если ничего не будет возможно, но этот вопрос специально здесь, чтобы увидеть, могу ли я избежать этого пути. Таким образом, тип данных 12.0 строго Float и результат строго Float, и единственное, что я могу сделать, это интерпретировать окончательный ответ фрагмента и пытаться "исправить" его, если он округляет "неправильный" способ.
Единственные расчеты, которые пользователи используют, включают числа с точностью до двух десятичных цифр (и не более 8 цифр). С "простым" я имею в виду, что ошибки с плавающей запятой не имеют возможности накапливаться: я могу добавить два из этих чисел и разделить их на целое число, но затем вычисление выполняется, результат округляется и сохраняется, а любой последующий расчет основано на этом округленном числе. Обычно будет задействована только одна ошибка с плавающей запятой, но я думаю, что проблема не будет существенно изменяться, если два могут накапливаться, хотя остаточная частота ошибок может быть больше по определению.
То, что может сначала прийти на ум, сначала округляется до трех десятичных цифр, а затем до 2. Однако это не работает. Это приведет к
152.48499999999999 => 152.485 => 152.49
но также
152.4846 => 152.485 => 152.49
который вам не нужен.
В следующий раз я подумал о добавлении наименьшего возможного приращения (которое, как указывали люди, зависит от рассматриваемого значения с плавающей запятой) с поплавком, если это подталкивает его к границе .5. Мне в основном интересно, как часто это может привести к "ложному положительному": числу, к которому добавляется наименьший приращение, хотя тот факт, что он был чуть ниже границы .5, был связан не с ошибкой с плавающей запятой, а с потому что это был просто результат расчета?
Второй вариант: всегда добавляйте наименьший приращение к числам, так как область .5 - это единственная, где это имеет значение.
Изменить: Я просто переписал вопрос, чтобы включить часть моих ответов в комментарии, как предложил cdiggins. Я наградил Ирак Бакстер щедростью за активное участие в обсуждении, хотя я еще не уверен, что он прав: Марк Рэнсом и Эмилио М Бумачар, похоже, поддерживают мою идею о том, что возможна коррекция, которая на практике может быть в относительно большом большинстве случаев, получается "правильный" результат.
Мне все же нужно выполнить эксперимент, чтобы увидеть, как часто результат будет правильным, и я полностью намерен, но время, которое я могу потратить на это, несколько ограничено, поэтому я еще не обходил его. Эксперимент не является тривиальным.