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


174

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

например

1.2 = 1
1.7 = 2

Ответы:


196

Выход из 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(на 32-битных машинах), будут работать почти как математические целые числа. Другие номера не гарантируются.


17
^ Тариама, почему потолок считается устаревшим? Насколько я знаю, это не рекомендуется в POSIX или Perl. Нужна цитата!
Сэм Уоткинс

3
@ Начинающие, не пытайтесь использовать, printfесли вы хотите получить результат в переменной, используйте sprintf... надеюсь, это сэкономит вам некоторое время отладки :-P
Борис Даппен

Могу ли я использовать int()на PDL?
CinCout

1
использовать POSIX; <br/> $ x = ($ x - этаж ($ x)> = .5)? ceil ($ x): пол ($ x);
Джозеф Ардженио

136

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

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

ОБНОВИТЬ

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

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

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


4
... но это не помогает на отрицательных числах: еще лучше sprintf
Алессандро

2
Ах нет, это не так. Округление отрицательного числа приводит вас ближе к нулю, а не дальше. Чему они учат в школах в эти дни?
RET

6
@RET Да, это не с отрицательными числами. $ float = -1.4 приводит к 0 с этим методом. Это не то, что они учили в моей школе. Помните, что int () усекается до нуля.
fishinear

4
@fishinear Вы правы, и я должным образом наказан. Но я сказал «для тривиального варианта использования». Мой ответ был исправлен.
RET

1
Обратите внимание, что это $ float = 0, это не удастся :-)
mat

74

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

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

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

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

46

Если вы решили использовать printf или sprintf, обратите внимание, что они используют метод округления от половины до четности.

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

Спасибо за указание на это. Точнее, название метода - «От круглой половины до четной».
Жан Винсент

Все ответы, которые упоминают printf или sprintf, должны упомянуть об этом.
безумный

Это чрезвычайно важная информация. У меня были ошибки времени в программном обеспечении, потому что я предполагал, что 5 всегда будет округлено. Я наконец нашел, почему Perl никогда не делал то, что хотел. Спасибо за указание на это.
Борис Деппен

На самом деле, это зависит от ОС! В Windows это будет круглая половина от нуля и Unix-подобных завершат половину даже: exploringbinary.com/...
Отк

9

Смотрите 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 (на 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 в ваше окончательное значение. Подожди, что опять сложно? ;)
cptstubing06

2

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

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

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

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.


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;

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 );

0

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

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

0

читая документацию о том, как округлять числа, многие эксперты предлагают написать свои собственные процедуры округления, поскольку «консервированная» версия, поставляемая с вашим языком, может быть недостаточно точной или содержать ошибки. Я думаю, однако, они говорят много десятичных знаков, а не только один, два или три. Имея это в виду, вот мое решение (хотя и НЕ ТОЧНО, как просили, поскольку мои потребности в отображении долларов - процесс не сильно отличается).

sub asDollars($) {
  my ($cost) = @_;
  my $rv = 0;

  my $negative = 0;
  if ($cost =~ /^-/) {
    $negative = 1;
    $cost =~ s/^-//;
  }

  my @cost = split(/\./, $cost);

  # let's get the first 3 digits of $cost[1]
  my ($digit1, $digit2, $digit3) = split("", $cost[1]);
  # now, is $digit3 >= 5?
  # if yes, plus one to $digit2.
  # is $digit2 > 9 now?
  # if yes, $digit2 = 0, $digit1++
  # is $digit1 > 9 now??
  # if yes, $digit1 = 0, $cost[0]++
  if ($digit3 >= 5) {
    $digit3 = 0;
    $digit2++;
    if ($digit2 > 9) {
      $digit2 = 0;
      $digit1++;
      if ($digit1 > 9) {
        $digit1 = 0;
        $cost[0]++;
      }
    }
  }
  $cost[1] = $digit1 . $digit2;
  if ($digit1 ne "0" and $cost[1] < 10) { $cost[1] .= "0"; }

  # and pretty up the left of decimal
  if ($cost[0] > 999) { $cost[0] = commafied($cost[0]); }

  $rv = join(".", @cost);

  if ($negative) { $rv = "-" . $rv; }

  return $rv;
}

sub commafied($) {
  #*
  # to insert commas before every 3rd number (from the right)
  # positive or negative numbers
  #*
  my ($num) = @_; # the number to insert commas into!

  my $negative = 0;
  if ($num =~ /^-/) {
    $negative = 1;
    $num =~ s/^-//;
  }
  $num =~ s/^(0)*//; # strip LEADING zeros from given number!
  $num =~ s/0/-/g; # convert zeros to dashes because ... computers!

  if ($num) {
    my @digits = reverse split("", $num);
    $num = "";

    for (my $i = 0; $i < @digits; $i += 3) {
      $num .= $digits[$i];
      if ($digits[$i+1]) { $num .= $digits[$i+1]; }
      if ($digits[$i+2]) { $num .= $digits[$i+2]; }
      if ($i < (@digits - 3)) { $num .= ","; }
      if ($i >= @digits) { last; }
    }

    #$num =~ s/,$//;
    $num = join("", reverse split("", $num));
    $num =~ s/-/0/g;
  }

  if ($negative) { $num = "-" . $num; }

  return $num; # a number with commas added
  #usage: my $prettyNum = commafied(1234567890);
}

чтобы подпрограмма соответствовала вашим спецификациям, просто измените следующее: if ($digit3 >= 5) { $digit3 = 0; $digit2++; if ($digit2 > 9) { $digit2 = 0; $digit1++; if ($digit1 > 9) { $digit1 = 0; $cost[0]++; } } } так оно и есть: if ($digit1 >= 5) { $digit1 = 0; $cost[0]++; } тогда простоreturn commafied($cost[0]);
Джарет Ллойд

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.