Как устранить ошибки округления Perl - PullRequest
3 голосов
/ 13 июля 2010

Рассмотрим следующую программу:

$x=12345678901.234567000;
$y=($x-int($x))*1000000000;
printf("%f:%f\n",$x,$y);

Вот что такое отпечатки:

12345678901.234568:234567642.211914

Я ожидал:

12345678901.234567:234567000

Это похоже на проблему округления в Perl.
Как я могу изменить ее, чтобы вместо нее получить 234567000?
Я сделал что-то не так?

Ответы [ 6 ]

6 голосов
/ 13 июля 2010

Это часто задаваемый вопрос.

Почему я получаю длинные десятичные знаки (например, 19,9499999999999) вместо цифр, которые я должен получать (например, 19,95)?

Внутренне ваш компьютер представляет двоичные числа с плавающей запятой. Цифровые (как в полномочиях двух) компьютеры не могут хранить все числа точно. Некоторые действительные числа теряют точность в процессе. Эта проблема связана с тем, как компьютеры хранят числа и влияют на все языки компьютеров, а не только на Perl.

perlnumber показывает кровавые детали представлений и преобразований чисел. Чтобы ограничить количество десятичных разрядов в своих числах, вы можете использовать функцию printf или sprintf. См. Арифметика с плавающей точкой для получения более подробной информации.

printf "%.2f", 10/3;
my $number = sprintf "%.2f", 10/3;
5 голосов
/ 13 июля 2010

Сделайте "use bignum ;"первая строка вашей программы.

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

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

3 голосов
/ 13 июля 2010

Ответ на весь вопрос о точности с плавающей запятой уже получен, но вы все еще видите проблему, несмотря на bignum.Зачем?Виновником является printf.bignum это мелкая прагма.Это влияет только на то, как числа представлены в переменных и математических операциях.* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}}}} 1006 * берет ваше точное число и превращает его обратно в неточное число с плавающей точкой.просто print и у них все должно получиться.Вам придется форматировать их вручную.

Другая вещь, которую вы можете сделать, это перекомпилировать Perl с -Duse64bitint -Duselongdouble, что заставит Perl внутренне использовать 64-битные целые числа и long double числа с плавающей запятой.Это даст вам гораздо больше точности, более согласованно и почти без затрат на производительность (bignum немного повышает производительность для математически интенсивного кода).Он не на 100% точен, как bignum, но влияет на такие вещи, как printf.Однако такая перекомпиляция Perl делает его двоичным несовместимым, поэтому вам придется перекомпилировать все ваши расширения.Если вы сделаете это, я предлагаю установить новый Perl в другом месте (/usr/local/perl/64bit или где-то еще), а не пытаться управлять параллельными установками Perl, использующими одну и ту же библиотеку.

2 голосов
/ 13 июля 2010

Домашнее задание (Googlework?) Для вас: Как числа с плавающей запятой представлены компьютерами?

У вас может быть только ограниченное количество точных цифр, все, кроме шума преобразования базы (двоичное вдесятичный).Вот почему последняя цифра вашего $x выглядит так: 8.

$x - (int($x) - это 0.23456linenoise, что также является числом с плавающей запятой.Умноженный на 1000000000, он дает другое число с плавающей запятой, с большим количеством случайных цифр, извлеченных из несоизмеримости оснований.

1 голос
/ 13 июля 2010

Perl не выполняет арифметику произвольной точности для своих встроенных типов с плавающей точкой. Итак, ваша начальная переменная $x является приблизительной. Вы можете увидеть это, выполнив:

$ perl -e 'printf "%.10f", 12345678901.234567000'
12345678901.2345676422
0 голосов
/ 08 мая 2018

Этот ответ работает на моей платформе x64, с учетом шкала ошибок

sub safe_eq {
  my($var1,$var2)=@_;
  return 1 if($var1==$var2);
  my $dust;
  if($var2==0) { $dust=abs($var1); }
  else { $dust= abs(($var1/$var2)-1); }
  return 0 if($dust>5.32907051820076e-15 ); # 5.32907051820075e-15
  return 1;
}

Вы можете воспользоваться вышеизложенным, чтобы решить большинство ваших проблем.

Избегайте bignum, если можете - это громадно медленно - плюс это не решит никаких проблем, если вам нужно хранить свои числа где-нибудь, например, в БД или в JSON и т. Д.

...