вторник, 12 февраля 2013 г.

Чудеса округления sprintf

Все началось с числа  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


Комментариев нет:

Отправить комментарий