Как я могу обойти ошибку округления, которая вызывает бесконечный цикл в Perl Statistics :: Descriptive?

Я использую библиотеку Statistics :: Descriptive в Perl для расчета частотного распределения и против проблемы с ошибкой округления с плавающей запятой.

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

Пройдя через отладчик, я вижу, что он делает:

my $interval = $self->{sample_range}/$partitions;

my $iter = $self->{min};

while (($iter += $interval) <  $self->{max}) {

  $bins{$iter} = 0;

  push @k, $iter;  ##Keep the "keys" unstringified

}

$ self-> sample_range (диапазон max-min) возвращает 2,77555756156289e-17, а не 0, как я ожидал. Это означает, что цикл ((min + = range) ‹max)) входит в (для всех намерений и целей) бесконечный цикл.

DB ‹8> print $ self -> {max};
0.205
DB ‹9> print $ self -> {min};
0.205
DB ‹10> print $ self -> { max} - $ self -> {min};
2.77555756156289e-17

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

Привет, Нил


person NeilInglis    schedule 03.06.2009    source источник


Ответы (3)


Я отвечаю за статистику :: Описательный сопровождающий. Из-за своей числовой природы сообщалось о многих проблемах с округлением. Я считаю, что эта конкретная версия была исправлена ​​в более поздней версии той, которую вы использовали, которую я недавно выпустил, с использованием умножения для делений вместо + =.

Используйте самую последнюю версию из CPAN, и она должна быть лучше.

person Shlomi Fish    schedule 03.06.2009
comment
Привет, Шломи! Рад, что вы обратили внимание на этот вопрос; вы избавили меня от необходимости отправлять вам ссылку на него по электронной почте. Я вижу, что в новой версии в качестве хэш-ключей все еще используются числа, например $ bins {$ self- ›max ()} = 0; чтобы избежать этого раунда значений, вы можете использовать pack F (требуется 5.8.0+) и распаковывать каждый раз, когда вы используете ключ. - person ysth; 04.06.2009
comment
Отлично, спасибо! Надо было проверить наличие новой версии, моя вина. Очень впечатлен этим ответом на мой первый вопрос о переполнении стека. Еще раз спасибо всем, кто откликнулся. - person NeilInglis; 04.06.2009

Не совсем проблема округления; вы можете увидеть более точные значения, например,

printf("%.18g %.18g", $self->{max}, $self->{min});

Мне кажется, что в модуле есть недостаток, в котором предполагается, что диапазон выборки может быть разделен на части $ partitions; поскольку точность с плавающей запятой не бесконечна, это не всегда возможно. В вашем случае минимальные и максимальные значения являются точно смежными представляемыми значениями, поэтому не может быть более одного раздела. Я не знаю, для чего именно модуль использует разделы, поэтому я не уверен, как это может повлиять. Другая возможная проблема в модуле заключается в том, что он использует числа в качестве хэш-ключей, что неявно их объединяет в строку, что немного округляет значение.

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

$data = 0+"$data";

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

person ysth    schedule 03.06.2009
comment
Ага, спасибо. На самом деле Max составляет 0,20500000000000002, а min - 0,20499999999999999, так что это объясняет, почему все идет не так. Я попробую обходные пути. - person NeilInglis; 03.06.2009

Это не должно вызывать бесконечный цикл. Если бы $self->{sample_range}/$partitions был равен 0, этот цикл стал бы бесконечным.

person Chas. Owens    schedule 03.06.2009
comment
Да, я тоже так не думал, но DB ‹12› p $ iter; 0,205 ДБ ‹13› p $ интервал; 3.46944695195361e-18 DB ‹14› p $ iter + = $ interval 0.205 DB ‹15› p $ self - ›{max} 0.205 DB ‹16› p ($ iter + = $ interval)‹ $ self - ›{max} 1 поэтому ((0.205 + 3.46944695195361e-18) ‹0.205) оценивается как истинное. Конечно, это был долгий день, так что я мог быть не в курсе ... - person NeilInglis; 03.06.2009
comment
Неа; возьмем, например, числа 1 и 1 + 2 ** - 52. Они различаются на 2 ** - 52. Предполагая, что вам нужно 4 раздела, что дает интервал от 2 ** до 54 (который явно не равен нулю), но если вы попытаетесь добавить это к 1, вы оставите 1 без изменений (на большинстве платформ), поскольку ближайший представимый значение 1 + 2 ** - 54 равно 1. Цикл предполагает, что если вы увеличиваете число на ненулевое значение, это приведет к увеличению числа, а в данном случае это неверно, что приведет к бесконечному циклу. - person ysth; 03.06.2009