Должны ли сценарии учитывать ошибку округления? - PullRequest
5 голосов
/ 31 августа 2009

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

Ответы [ 9 ]

10 голосов
/ 31 августа 2009

Это зависит. double 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

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

7 голосов
/ 31 августа 2009

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

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

Эту проблему можно обойти, используя высокоточные математические библиотеки, которые представляют числа либо в виде двух чисел дроби (то есть «цифра = 1, знаменатель = 10»), либо в виде строки вместо использования собственного двоичного файла представление. Однако из-за дополнительной работы, связанной с выполнением любых вычислений чисел, которые хранятся как-то еще, эти библиотеки обязательно замедляют любую математику, которая должна пройти через них.

6 голосов
/ 31 августа 2009

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

x = 1 / 2

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

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

from fractions import Fraction

x = Fraction(1, 2)

, которая имеет точную арифметику с рациональными числами.

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

from decimal import Decimal

x = Decimal('0.5')

Вы сможете установить его точность, скажем, до 100 цифр, если хотите. Или установите значение 2 для банковских приложений.

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

Кроме того, это большое недоразумение, что точная арифметика не приводит к проблемам с округлением. Каждый раз, когда вы округляете точное значение, чтобы сделать что-то полезное для пользователя - например, распечатайте его пользователю или добавьте столько долларов на банковский счет пользователя - вы столкнетесь со «странным поведением» округления. Это присуще нецелочисленной арифметике.

4 голосов
/ 31 августа 2009

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

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

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

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

Обновление:

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

2 голосов
/ 31 августа 2009

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

Для Python есть SciPY и NumPy для научных расчетов.

1 голос
/ 31 августа 2009

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

0 голосов
/ 31 августа 2009

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

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

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

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

0 голосов
/ 31 августа 2009

Конечно, они делают!

Пример из Python 2.6:

>>> 1440.0 / 900.0
1.6000000000000001

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

0 голосов
/ 31 августа 2009

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

irb(main):033:0> (2.01 * 1000).to_i
=> 2009
irb(main):034:0> ((2.01 * 1000.0) + 0.5).floor
=> 2010
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...