Должны ли сценаристы учитывать ошибку округления?


Я изучаю C, и возникла идея охранных цифр и ошибок округления. Нужно ли практикам скриптовых языков (я имею в виду Python и Perl) беспокоиться об этом? Что, если они занимаются научным программированием?

9 5

9 ответов:

Я бы не согласился с лутцем... Хотя ошибки округления, о которых вы упомянули, действительно существуют в Python/Perl/Ruby, они не имеют абсолютно никакого отношения к языкам, реализуемым в C. проблема глубже.

Числа с плавающей запятой, как и все данные, представлены в двоичном виде на современных компьютерах. Так же, как существуют числа с периодическими десятичными представлениями (например, 1/3 = 0,333333...), существуют также числа с периодическими двоичными представлениями (напр., 1/10 = 0.0001100110011...). Поскольку эти числа не могут быть точно представлены в (конечном объеме) компьютерной памяти, любые вычисления, связанные с ними, приведут к ошибке. Это можно обойти с помощью высокоточных математических библиотек, которые представляют числа либо в виде двух чисел дроби (например, "числитель = 1, знаменатель = 10"), либо в виде строки вместо использования собственного двоичного представления. Однако из-за дополнительной работы, связанной с выполнением каких-либо расчетов на числа, которые хранятся как нечто другое, эти библиотеки обязательно замедляют любую математику, которая должна пройти через них.

Это зависит. S ведут себя одинаково везде, так что если вы делаете математику с двойниками, у вас будет та же проблема с любым языком. Если вы используете собственный тип произвольной точности, то нет, это не проблема. Рассмотрим:

use Math::BigFloat;
my $big   = Math::BigFloat->new("1_000_000_000_000_000_000_000");
my $small = Math::BigFloat->new("0.000000000000000000000000001"); 
print $big + $small;

(или, если вы действительно хотите скрыть, что происходит:

use bignum;
print 1_000_000_000_000_000_000_000 + 0.000000000000000000000000001

)

Как и ожидалось, это дает:

1000000000000000000000.000000000000000000000000001

Также, как и ожидалось, это не делается в одной инструкции процессора.

В Python существует несколько типов нецелых чисел:

x = 1 / 2

Даст вам стандартный float. Его тип - float, он по существу такой же, как и в C, он обрабатывается аппаратным обеспечением, и у него те же проблемы, что и у любого другого float в мире.

Однако существует также дробный тип :

from fractions import Fraction

x = Fraction(1, 2)
Который имеет точную арифметику с рациональными числами.

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

from decimal import Decimal

x = Decimal('0.5')
Вы сможете установить его точность, скажем, до 100 цифр, если захотите. Или установите его на 2 для банковских приложений. Пока компьютеры тупы, нам, вероятно, понадобится так много разных типов. По крайней мере, в соответствии с принципами Python, Python требует, чтобы вы сделали явный выбор о том, что вы хотите от вашего числа. Более того, это большое недоразумение, что точная арифметика не приводит к проблемам с округлением. Каждый раз, когда вы округляете точное значение, чтобы сделать что-то полезное для пользователя-например, распечатать его пользователю или добавить столько долларов на банковский счет пользователя - вы сталкиваетесь с "странным поведением" округления. Это присуще нецелочисленной арифметике.

Это зависит от того, как вы представляете свои числа, а не от языка, который вы используете.

Например, если я пишу весь свой код в 8051 assember, но реализовал гладкую библиотеку рациональных чисел, то округление не является проблемой. 1/3-это всего лишь 1/3.

Однако, если я использую последний шикарный динамический язык, и он использует iee754 floats, то применяются все ограничения IEEE754.

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

Обновление:

PDL - это популярная библиотека для выполнения научных вычислений на Perl.

Поскольку базовый интепретер CPython и Perl реализованы на языке C, они ведут себя как программа на языке C.

Для Python существует SciPY и NumPy для научных вычислений.

Вы можете выполнить несколько точных вычислений с помощью Python, с внешними модулями. В разделе "Многоточная математика" на официальном веб-сайте перечислены многие из них.

Ну, вы не застрахованы от ошибок с плавающей запятой в Ruby. Например:

irb(main):033:0> (2.01 * 1000).to_i
=> 2009
irb(main):034:0> ((2.01 * 1000.0) + 0.5).floor
=> 2010

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

Доказательство: Предположим, вы хотите отследить движение молекулы вблизи границы Вселенной. Размер Вселенной составляет около 93 миллиардов световых лет (насколько нам известно). Молекула довольно крошечная, поэтому вам понадобится по крайней мере нанометровая точность (10^-6). Это на 50 порядков больше.

По какой-то причине вам нужно повернуть эта молекула. Это включает в себя операции sin() и cos() и умножение. Умножение не является проблемой, так как число допустимых цифр является просто суммой длины обоих операндов. Но как насчет sin()?

Вы должны создать уравнение ошибки, чтобы быть уверенным, что вы сохраняете достаточное количество цифр, так что конечный результат будет иметь знать максимальную ошибку. Я не знаю ни одной" простой " числовой библиотеки, которая могла бы выполнять эту операцию автоматически (скажем, как часть вызова sin()). Это то, что вам нужно Matlab или что-то подобное.

Конечно, есть!

Пример из Python 2.6:

>>> 1440.0 / 900.0
1.6000000000000001
Как говорит лутц, поскольку скриптовые языки часто реализуются на языке Си, они наследуют эти "особенности". Компенсация их в языке, несомненно, означала бы какой-то компромисс в производительности или переносимости.