Как округлить число с плавающей запятой в Perl?
-
05-07-2019 - |
Вопрос
Как округлить десятичное число (с плавающей запятой) до ближайшего целого числа?
например
1.2 = 1
1.7 = 2
Решение
Выход 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 перлов тригонометрия выполнялась в
Math::Complex
модуль.С 5.004,Math::Trig
Модуль (часть стандартного распределения Perl) реализует тригонометрические функции.Внутренне он используетMath::Complex
Модуль и некоторые функции могут вырваться от реальной оси в сложную плоскость, например, обратную синус.Округление в финансовых приложениях может иметь серьезные последствия, и используемый метод округления должен быть точно указан.В этих случаях, вероятно, стоит не доверять какой -либо системе, округлению, используемой 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 -битных машинах) будет работать в значительной степени как математические целые числа.Другие номера не гарантируются.
Другие советы
Хотя я не согласен со сложными ответами о промежуточных отметках и т. д., для более распространенного (и, возможно, тривиального) варианта использования:
my $rounded = int($float + 0.5);
ОБНОВЛЯТЬ
Если это возможно для вашего $float
чтобы быть отрицательным, следующий вариант даст правильный результат:
my $rounded = int($float + $float/abs($float*2 || 1));
При таком расчете -1,4 округляется до -1, а -1,6 до -2, и ноль не взрывается.
Вы можете использовать модуль типа Математика::Раунд:
use Math::Round;
my $rounded = round( $float );
Или вы можете сделать это грубым способом:
my $rounded = sprintf "%.0f", $float;
Если вы решите использовать 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
Видеть 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
Модуль и некоторые функции могут вырваться от реальной оси в сложную плоскость, например, обратную синус.Округление в финансовых приложениях может иметь серьезные последствия, и используемый метод округления должен быть точно указан.В этих случаях, вероятно, стоит не доверять какой -либо системе, округлению, используемой 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 -битных машинах), будут работать в значительной степени как математические целые числа.Другие номера не гарантируются.
Вам не нужен внешний модуль.
$x[0] = 1.2;
$x[1] = 1.7;
foreach (@x){
print $_.' = '.( ( ($_-int($_))<0.5) ? int($_) : int($_)+1 );
print "\n";
}
Возможно, я упускаю вашу мысль, но мне показалось, что это гораздо более чистый способ выполнить ту же работу.
Что это значит, так это пройтись по каждому положительному числу в элементе, распечатать число и округленное целое число в указанном вами формате.Код объединяет соответствующие округленные положительные целые числа только на основе десятичных знаков.int($_) в основном округлить число так ($-int($)) захватывает десятичные дроби.Если десятичные дроби (по определению) строго меньше 0,5, округлите число в меньшую сторону.Если нет, округлите до 1.
Следующие команды округляют положительные или отрицательные числа до заданной десятичной позиции:
sub round ()
{
my ($x, $pow10) = @_;
my $a = 10 ** $pow10;
return (int($x / $a + (($x < 0) ? -0.5 : 0.5)) * $a);
}
Ниже приведен пример пяти различных способов суммирования значений.Первый — это наивный способ суммирования (и он терпит неудачу).Вторая попытка использовать 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
.
Отрицательные числа могут добавить некоторые особенности, о которых людям следует знать.
printf
Подходы в стиле -style дают нам правильные цифры, но могут привести к странному отображению.Мы обнаружили, что этот метод (на мой взгляд, глупо) вводит -
подпишите, должно это или не должно.Например, -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;
Мое решение для 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 );
Если вас интересует только получение целочисленного значения из целого числа с плавающей запятой (т.12347.9999 или 54321.0001), этот подход (заимствованный и измененный выше) поможет:
my $rounded = floor($float + 0.1);
cat table |
perl -ne '/\d+\s+(\d+)\s+(\S+)/ && print "".**int**(log($1)/log(2))."\t$2\n";'