Все началось с числа 4.4525. При округлении до 3-его знака ожидалось получить 4.453, однако Perl'овый sprintf настойчиво возвращал 4.452.
Далее прочитал следующие правила округления:
http://ibrain.kz/mod/book/view.php?id=211&chapterid=966
Ага, оно действительно подтверждается следующими выходами:
$ perl
foreach $i (0.5, 1.5, 2.5, 3.5, 4.5) {
printf "%f -> %.0f\n", $i, $i;
}
Output: 0.500000 -> 0
1.500000 -> 2
2.500000 -> 2
3.500000 -> 4
4.500000 -> 4
Резюме: допустимо, т.к. объяснимо))
тогда вот это как объяснить?!
$ perl
printf "%f -> %.3f\n", 4.45*5, 4.45*5;
Output: 4.450500 -> 4.450
4.451500 -> 4.452
4.452500 -> 4.452
4.453500 -> 4.454
4.454500 -> 4.455
4.455500 -> 4.455
4.456500 -> 4.457
4.457500 -> 4.457
4.458500 -> 4.458
4.459500 -> 4.460
Резюме: непонятно почему так
$ perl
printf "%f -> %.3f\n", 4.*525, 4.*525;
Output: 4.052500 -> 4.053
4.152500 -> 4.152
4.252500 -> 4.253
4.352500 -> 4.353
4.452500 -> 4.452
4.552500 -> 4.553
4.652500 -> 4.652
4.752500 -> 4.752
4.852500 -> 4.853
4.952500 -> 4.952
Резюме: непонятно почему так
========================================
Теперь добавим наименьшее слагаемое $delta = 1E-12
$ perl
foreach $i (0.5, 1.5, 2.5, 3.5, 4.5) {
printf "%f -> %.0f\n", $i+$delta, $i+$delta;
}
Output: 0.500000 -> 1
1.500000 -> 2
2.500000 -> 3
3.500000 -> 4
4.500000 -> 5
Резюме: окей
$ perl
printf "%f -> %.3f\n", 4.45*5+$delta, 4.45*5+$delta;
Output: 4.450500 -> 4.451
4.451500 -> 4.452
4.452500 -> 4.453
4.453500 -> 4.454
4.454500 -> 4.455
4.455500 -> 4.456
4.456500 -> 4.457
4.457500 -> 4.458
4.458500 -> 4.459
4.459500 -> 4.460
Резюме: окей
$ perl
printf "%f -> %.3f\n", 4.*525+$delta, 4.*525+$delta;
Output: 4.052500 -> 4.053
4.152500 -> 4.153
4.252500 -> 4.253
4.352500 -> 4.353
4.452500 -> 4.453
4.552500 -> 4.553
4.652500 -> 4.653
4.752500 -> 4.753
4.852500 -> 4.853
4.952500 -> 4.953
Резюме: окей
Таким образом, для sprintf такое добавление малого числа сказывается благотворно на результат округления.
========================================
Написал функцию для работы с положительными числами:
sub rounding ($$) {
my ($num, $accuracy) = @_;
$scale = eval "1e+$accuracy";
return int($num*$scale+0.5)/$scale;
}
print Dumper(rounding(4.4525, 3));
print Dumper(rounding(4.4525e-09, 12));
Output: 4.453
Резюме: окей
####################################################################################################
# "Nearest" routines (round to a multiple of any number)
# function from CPAN Math::Round
#
####################################################################################################
#
# nearest(10, 44) yields 40
# nearest(10, 46) 50
# nearest(10, 45) 50
# nearest(25, 328) 325
# nearest(.1, 4.567) 4.6
# nearest(10, -45) -50
sub nearest {
my $half = 0.50000000000008;
my $targ = abs(shift);
my @res = map {
if ($_ >= 0) { $targ * int(($_ + $half * $targ) / $targ); }
else { $targ * POSIX::ceil(($_ - $half * $targ) / $targ); }
} @_;
return (wantarray) ? @res : $res[0];
}
$ perl
print Dumper(nearest(.001, 4.4525));
Output: 4.452