【发布时间】:2010-09-15 18:22:37
【问题描述】:
如何将十进制数(浮点数)四舍五入到最接近的整数?
例如
1.2 = 1
1.7 = 2
【问题讨论】:
标签: perl floating-point rounding
如何将十进制数(浮点数)四舍五入到最接近的整数?
例如
1.2 = 1
1.7 = 2
【问题讨论】:
标签: perl floating-point rounding
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 说我们必须做 这。绝对值为
2**31下的整数的 Perl 数(在 32 位机器)将非常像数学整数一样工作。 不保证其他数字。
【讨论】:
printf,如果你想要变量中的结果,使用sprintf...希望这可以节省你一些调试时间:-P
int() 吗?
虽然不同意关于中途标记等的复杂答案,但对于更常见(并且可能微不足道)的用例:
my $rounded = int($float + 0.5);
更新
如果您的$float 可能为负数,则以下变体将产生正确的结果:
my $rounded = int($float + $float/abs($float*2 || 1));
通过此计算,-1.4 舍入为 -1,-1.6 舍入为 -2,零不会爆炸。
【讨论】:
您可以使用 Math::Round 之类的模块:
use Math::Round;
my $rounded = round( $float );
或者你可以用粗略的方式来做:
my $rounded = sprintf "%.0f", $float;
【讨论】:
如果您决定使用 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
【讨论】:
请记住,
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 位机器)将非常像数学整数一样工作。 不保证其他数字。
【讨论】:
您不需要任何外部模块。
$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 风格的方法给了我们正确的数字,但它们可能会导致一些奇怪的显示。我们发现这种方法(在我看来是愚蠢的)会添加一个- 符号,无论它是否应该。例如,四舍五入到小数点后一位的 -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);
【讨论】:
使用Math::BigFloat,您可以执行以下操作:
use Math::BigFloat;
print Math::BigFloat->new(1.2)->bfround(1); ## 1
print Math::BigFloat->new(1.7)->bfround(1); ## 2
这可以包装在一个子程序中
use Math::BigFloat;
sub round {
Math::BigFloat->new(shift)->bfround(1);
}
print round(1.2); ## 1
print round(1.7); ## 2
【讨论】:
大量阅读有关如何舍入数字的文档,许多专家建议编写自己的舍入例程,因为您的语言提供的“固定”版本可能不够精确,或者包含错误。然而,我想,他们说的是许多小数位,而不仅仅是一位、两位或三位。考虑到这一点,这是我的解决方案(尽管并不完全符合我的要求,因为我需要显示美元——不过,这个过程并没有太大的不同)。
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]);
cat table |
perl -ne '/\d+\s+(\d+)\s+(\S+)/ && print "".**int**(log($1)/log(2))."\t$2\n";'
【讨论】: