Як округлити число з плаваючою комою в Perl?


174

Як я можу округлити десятковий номер (плаваючу крапку) до найближчого цілого числа?

напр

1.2 = 1
1.7 = 2

Відповіді:


196

Вихід perldoc -q round

Чи Perl має круглу () функцію? А що з стелею () та підлогою ()? Триггерні функції?

Пам’ятайте, що 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 перлів у 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

Не звинувачуй Перл. Це те саме, що в C. IEEE каже, що ми повинні це зробити. Числа Perl, абсолютні значення яких є цілими числами 2**31(на 32-бітових машинах), будуть працювати приблизно так само, як математичні цілі числа. Інші номери не гарантуються.


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

3
@Beginners, не намагайтеся використовувати, printfякщо ви хочете, щоб результат був змінним, використовуйте sprintf... сподіваюся, це заощадить вам час налагодження :-P
Борис Даппен

Чи можу я використовувати int()на PDL?
CinCout

1
використовувати POSIX; <br/> $ x = ($ x - підлога ($ x)> = .5)? стеля ($ х): підлога ($ х);
Йосиф Аргеніо

136

Хоча не погоджуючись зі складними відповідями про півдорогі позначки тощо, для більш поширеного (і, можливо, тривіального) випадку використання:

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

ОНОВЛЕННЯ

Якщо можливо, що ваш $floatнегативний показник, наступний варіант дасть правильний результат:

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

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


4
... але це не вдається на від'ємних цифрах: все ж краще спринт
алессандро

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

6
@ RET Так, це не вдається з від'ємними числами. $ float = -1.4 за допомогою цього методу призводить до 0. Це не те, що вони навчали в моїй школі. Пам'ятайте, що int () скорочується до нуля.
fishinear

4
@fishinear Ви праві, і я належним чином покараний. Але я сказав "для тривіального використання". Моя відповідь виправлена.
RET

1
Зауважте, що це $ float = 0, це не вдасться :-)
мат

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 перлів у 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

Не звинувачуй Перл. Це те саме, що в 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

Моє рішення для спринту

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]);
Jarett Lloyd

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.