Почему Perl не согласуется с округлением sprintf?
Вот скрипт Perl:
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
my @numbers = qw(
0.254
0.255
0.256
);
foreach my $number (@numbers) {
my $rounded = sprintf '%.2f', $number;
say "$number => $rounded";
}
foreach my $number (@numbers) {
$number += 100;
my $rounded = sprintf '%.2f', $number;
say "$number => $rounded";
}
Это выводит:
0.254 => 0.25
0.255 => 0.26
0.256 => 0.26
100.254 => 100.25
100.255 => 100.25
100.256 => 100.26
Для меня очень странно, что Perl несовместим с округлением. Я ожидаю, что оба числа, заканчивающиеся на .255, будут округлены до .26 Это верно для 0.255, но это неверно для числа 100.255.
Вот цитата из Perl Cookbook, http://docstore.mik.ua/orelly/perl/cookbook/ch02_04.htm,
спринтф. Формат f позволяет вам указать определенное количество десятичных знаков для округления его аргумента. Perl смотрит на следующую цифру, округляет ее, если она больше 5, и округляет в противном случае.
Но я не вижу никаких доказательств того, что это правильно в http://perldoc.perl.org/functions/sprintf.html
Это ошибка в sprintf или Perl Cookbook не так? Если это желаемое поведение, почему это работает таким образом?
Ответы
Ответ 1
Perl использует базовую библиотеку C для форматирования. То, что эта библиотека может варьироваться от платформы к платформе. Даже POSIX говорит, что "младшая цифра должна быть округлена в соответствии с реализацией".
В glibc, который, возможно, используется большинством бинарных файлов perl, поведение, о котором вы видите, будет зависеть от нескольких вещей:
Во-первых, как указано в другом ответе, значение, которое вы считаете округленным, может быть не точно представлено в плавающей запятой, и каким образом округление будет определено, если оно является следующим более высоким или меньшим представимым числом.
Во-вторых, даже если значение точно представлено как на полпути между двумя возможными округлениями, glibc будет использовать округление банкира. То есть, он округляется до четной цифры. Таким образом, sprintf '%.1g', .25
будет производить .2
, но sprintf '%.1g', .75
будет производить .8
.
Цитата из Cookbook Perl просто неверна.
Ответ 2
Если вы добавите эту строку:
$number = sprintf '%.15f', $number;
перед печатью вы получите:
0.254000000000000 => 0.25
0.255000000000000 => 0.26
0.256000000000000 => 0.26
100.254000000000005 => 100.25
100.254999999999995 => 100.25
100.256000000000000 => 100.26
как вы можете видеть, 100.255
не точно 100.255
, это связано с представлением чисел с плавающей запятой.