Как вы округляете число с плавающей запятой в Perl?

169

Как я могу округлить десятичное число (с плавающей запятой) до ближайшего целого?

например.

1.2 = 1
1.7 = 2
Теги:
floating-point
rounding

12 ответов

193
Лучший ответ

Вывод perldoc -q round

Имеет ли Perl функцию round()? Как насчет ceil() и floor()? Триг функции?

Помните, что int() просто обрезается в сторону 0. Для округления до определенного количества цифр sprintf() или printf(), как правило, самый простой маршрут.

    printf("%.3f", 3.1415926535);       # prints 3.142

Модуль POSIX (часть стандартного дистрибутива Perl) реализует ceil(), floor(), а также ряд других математических и тригонометрических функции.

    use POSIX;
    $ceil   = ceil(3.5);                        # 4
    $floor  = floor(3.5);                       # 3

В 5.000 до 5.003 perls тригонометрия была выполнена в Math::Complex модуль. С 5.004 модуль Math::Trig (часть стандартного Perl распределение) реализует тригонометрические функции. Внутренне это использует модуль Math::Complex, и некоторые функции могут вырваться из вещественной оси в комплексную плоскость, например обратный синус 2.

Округление финансовых заявок может иметь серьезные последствия, и метод округления должен быть точно определен. В этих случаев, вероятно, стоит не доверять тому, что округление системы используется Perl, но вместо этого реализует функцию округления, которая вам нужна сам.

Чтобы понять, почему, обратите внимание на то, что у вас все еще будет проблема на полупункте чередование:

    for ($i = 0; $i < 1.01; $i += 0.05) { printf "%.1f ",$i}

    0.0 0.1 0.1 0.2 0.2 0.2 0.3 0.3 0.4 0.4 0.5 0.5 0.6 0.7 0.7
    0.8 0.8 0.9 0.9 1.0 1.0

Не обвиняйте Perl. Это то же самое, что и в C. IEEE, что мы должны делать это. Числа Perl, абсолютные значения которых являются целыми числами при 2**31 (on 32-разрядные машины) будут работать почти так же, как математические целые числа. Другие номера не гарантируются.

  • 17
    ^ Тариама, почему потолок считается устаревшим? Насколько я знаю, это не рекомендуется в POSIX или Perl. Нужна цитата!
  • 3
    @Beginners, не пытайтесь использовать printf если вы хотите получить результат в переменной, используйте sprintf ... надеюсь, это сэкономит вам время отладки :-P
Показать ещё 2 комментария
126

Не соглашаясь с комплексными ответами о методах полупунктов и т.д., для более распространенного (и, возможно, тривиального) варианта использования:

my $rounded = int($float + 0.5);

UPDATE

Если возможно, что ваш $float будет отрицательным, следующий вариант даст правильный результат:

my $rounded = int($float + $float/abs($float*2 || 1));

При этом вычисление -1.4 округляется до -1 и от -1,6 до -2, а нуль не будет взорваться.

  • 3
    ... но это не сработает на отрицательных числах: еще лучше sprintf
  • 2
    Ах нет, это не так. Округление отрицательного числа приводит вас ближе к нулю, а не дальше. Чему они учат в школах в эти дни?
Показать ещё 5 комментариев
70

Вы можете использовать модуль, например Math:: Round:

use Math::Round;
my $rounded = round( $float );

Или вы можете сделать это грубо:

my $rounded = sprintf "%.0f", $float;
45

Если вы решите использовать printf или sprintf, обратите внимание, что они используют метод Round half to even.

foreach my $i ( 0.5, 1.5, 2.5, 3.5 ) {
    printf "$i -> %.0f\n", $i;
}
__END__
0.5 -> 0
1.5 -> 2
2.5 -> 2
3.5 -> 4
  • 0
    Спасибо за указание на это. Точнее, название метода «Круглая половина до четной».
  • 0
    Все ответы, которые упоминают printf или sprintf, должны упомянуть об этом.
Показать ещё 2 комментария
8

См. perldoc/perlfaq:

Помните, что int() просто обрезается в направлении 0. Для округления до определенное количество цифр, sprintf() или printf(), как правило, самый простой маршрут.

 printf("%.3f",3.1415926535);
 # prints 3.142

Модуль POSIX (часть стандартного дистрибутива Perl) реализует ceil(), floor() и ряд других математических и тригонометрические функции.

use POSIX;
$ceil  = ceil(3.5); # 4
$floor = floor(3.5); # 3

В 5.000 до 5.003 perls тригонометрия была выполнена в модуле Math::Complex.

С 5.004 модуль Math::Trig (часть стандартного распределения Perl) > реализует тригонометрические функции.

Внутри он использует модуль Math::Complex, и некоторые функции могут прерываться из вещественной оси в комплексную плоскость, например обратный синус 2.

Округление в финансовых приложениях может иметь серьезные последствия, а округление метод должен быть точно определен. В этих случаях, вероятно, он не платит доверие зависит от того, какое окружение системы используется Perl, но вместо этого реализовать функция округления, в которой вы нуждаетесь.

Чтобы узнать, почему, обратите внимание, что у вас все еще будет проблема с чередованием на полпути:

for ($i = 0; $i < 1.01; $i += 0.05)
{
   printf "%.1f ",$i
}

0.0 0.1 0.1 0.2 0.2 0.2 0.3 0.3 0.4 0.4 0.5 0.5 0.6 0.7 0.7 0.8 0.8 0.9 0.9 1.0 1.0

Не обвиняйте Perl. Это то же самое, что и в C. IEEE, что мы должны делать это. Числа Perl, абсолютные значения которых равны целым числам 2 ** 31 (on 32-разрядные машины) будут работать почти так же, как математические целые числа. Другие номера не гарантируются.

3

Вам не нужен внешний модуль.

$x[0] = 1.2;
$x[1] = 1.7;

foreach (@x){
  print $_.' = '.( ( ($_-int($_))<0.5) ? int($_) : int($_)+1 );
  print "\n";
}

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

Для этого нужно пройти через каждое положительное число в элементе, напечатать число и закругленное целое число в указанном вами формате. Код объединяет соответствующее округленное положительное целое число только на основе десятичных знаков. int ($ _) в основном округляет число так ($ -int ($)) фиксирует десятичные числа. Если десятичные знаки (по определению) строго меньше 0,5, округлите число. Если нет, округлите, добавив 1.

  • 1
    Еще раз, зачем отвечать на древний вопрос сложным ответом, когда что-то вроде ответа RET работает одинаково хорошо.
  • 1
    Это на самом деле не очень сложно, и ответ RET включает в себя кучу математики, которые а) теоретически рискуют переполнением, б) занимают больше времени и в) без необходимости вводят больше неточности fp в ваше окончательное значение. Подожди, что опять сложно? ;)
1

Далее будут округлены положительные или отрицательные числа до заданной десятичной позиции:

sub round ()
{
    my ($x, $pow10) = @_;
    my $a = 10 ** $pow10;

    return (int($x / $a + (($x < 0) ? -0.5 : 0.5)) * $a);
}
1

Отрицательные числа могут добавить некоторые причуды, о которых люди должны знать.

printf -стильные подходы дают нам правильные цифры, но они могут привести к некоторым нечетным отображениям. Мы обнаружили, что этот метод (по-моему, глупо) ставит знак -, следует ли это делать или нет. Например, -0.01, округленное до одного десятичного знака, возвращает значение -0.0, а не только 0. Если вы собираетесь использовать стиль стиля printf, и вы знаете, что вы не хотите десятичного числа, используйте %d, а не %f (когда вам нужны десятичные знаки, это когда дисплей становится неуклюжим).

Хотя это правильно и для математики не имеет большого значения, для отображения это просто выглядит странно, показывая что-то вроде "-0.0".

Для метода int отрицательные числа могут изменить то, что вы хотите в результате (хотя есть некоторые аргументы, которые могут быть сделаны, они верны).

int + 0.5 вызывает реальные проблемы с неотрицательными числами, если вы не хотите, чтобы это работало таким образом, но я думаю, что большинство людей этого не делают. -0.9 должен, вероятно, округлять до -1, а не 0. Если вы знаете, что хотите, чтобы отрицательный был потолком, а не пол, тогда вы можете сделать это в одном слое, иначе вы можете использовать метод int с незначительным (это, очевидно, работает только для получения целых чисел:

my $var = -9.1;
my $tmpRounded = int( abs($var) + 0.5));
my $finalRounded = $var >= 0 ? 0 + $tmpRounded : 0 - $tmpRounded;
1

Ниже приведен пример из пяти различных способов суммирования значений. Первый - наивный способ выполнить суммирование (и терпит неудачу). Второй пытается использовать sprintf(), но он тоже терпит неудачу. Третий использует sprintf() успешно, в то время как последние два (4-й и 5-й) используют floor($value + 0.5).

 use strict;
 use warnings;
 use POSIX;

 my @values = (26.67,62.51,62.51,62.51,68.82,79.39,79.39);
 my $total1 = 0.00;
 my $total2 = 0;
 my $total3 = 0;
 my $total4 = 0.00;
 my $total5 = 0;
 my $value1;
 my $value2;
 my $value3;
 my $value4;
 my $value5;

 foreach $value1 (@values)
 {
      $value2 = $value1;
      $value3 = $value1;
      $value4 = $value1;
      $value5 = $value1;

      $total1 += $value1;

      $total2 += sprintf('%d', $value2 * 100);

      $value3 = sprintf('%1.2f', $value3);
      $value3 =~ s/\.//;
      $total3 += $value3;

      $total4 += $value4;

      $total5 += floor(($value5 * 100.0) + 0.5);
 }

 $total1 *= 100;
 $total4 = floor(($total4 * 100.0) + 0.5);

 print '$total1: '.sprintf('%011d', $total1)."\n";
 print '$total2: '.sprintf('%011d', $total2)."\n";
 print '$total3: '.sprintf('%011d', $total3)."\n";
 print '$total4: '.sprintf('%011d', $total4)."\n";
 print '$total5: '.sprintf('%011d', $total5)."\n";

 exit(0);

 #$total1: 00000044179
 #$total2: 00000044179
 #$total3: 00000044180
 #$total4: 00000044180
 #$total5: 00000044180

Обратите внимание, что floor($value + 0.5) можно заменить на int($value + 0.5), чтобы удалить зависимость от POSIX.

0

Если вам нужно только получить целочисленное значение из целого числа с плавающей запятой (т.е. 12347.9999 или 54321.0001), этот подход (заимствованный и измененный сверху) будет делать трюк:

my $rounded = floor($float + 0.1); 
0

Мое решение для sprintf

if ($value =~ m/\d\..*5$/){
    $format =~ /.*(\d)f$/;
    if (defined $1){
       my $coef = "0." . "0" x $1 . "05";    
            $value = $value + $coef;    
    }
}

$value = sprintf( "$format", $value );
-1
cat table |
  perl -ne '/\d+\s+(\d+)\s+(\S+)/ && print "".**int**(log($1)/log(2))."\t$2\n";' 

Ещё вопросы

Сообщество Overcoder
Наверх
Меню