คุณจะปัดเลขทศนิยมใน Perl ได้อย่างไร?


174

ฉันจะปัดเศษทศนิยม (ทศนิยม) เป็นจำนวนเต็มที่ใกล้ที่สุดได้อย่างไร

เช่น

1.2 = 1
1.7 = 2

คำตอบ:


196

ผลผลิตของ perldoc -q round

Perl มีฟังก์ชัน round () หรือไม่? สิ่งที่เกี่ยวกับเพดาน () และพื้น ()? ฟังก์ชั่น Trig หรือไม่

โปรดจำไว้ว่าเพียงตัดทอนไปยัง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
Thariama ^ ทำไมจะถูกคัดค้าน ceil? มันไม่ได้คัดค้านใน POSIX หรือ Perl เท่าที่ฉันรู้ ต้องการการอ้างอิง!
Sam Watkins

3
@ ผู้เริ่มต้นอย่าพยายามใช้printfหากคุณต้องการผลลัพธ์ในตัวแปรให้ใช้sprintf... หวังว่านี่จะช่วยคุณประหยัดเวลาในการดีบัก :-P
Boris Däppen

ฉันสามารถใช้int()กับ PDL ได้หรือไม่
CinCout

1
ใช้ POSIX; <br/> $ x = ($ x - floor ($ x)> = .5)? ceil ($ x): floor ($ x);
Joseph Argenio

136

ในขณะที่ไม่เห็นด้วยกับคำตอบที่ซับซ้อนเกี่ยวกับเครื่องหมายครึ่งทางและอื่น ๆ สำหรับกรณีการใช้งานทั่วไป (และอาจเป็นเรื่องเล็กน้อย):

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

UPDATE

หากเป็นไปได้ที่คุณ$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
หมายเหตุว่ามันลอย $ = 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

ขอบคุณที่ชี้นำสิ่งนี้ แม่นยำมากขึ้นชื่อของวิธีการคือ 'Round Half to Even'
Jean Vincent

คำตอบทั้งหมดที่กล่าวถึง printf หรือ sprintf ควรพูดถึงสิ่งนี้
บ้า

นี่เป็นข้อมูลที่สำคัญอย่างยิ่ง ฉันมีข้อผิดพลาดครั้งที่เซิร์ฟเวอร์ในซอฟต์แวร์เพราะฉันคิดว่า 5 จะถูกปัดเศษขึ้นเสมอ ในที่สุดฉันก็พบว่าทำไม perl ไม่เคยทำสิ่งที่ฉันต้องการ ขอบคุณที่ชี้นำสิ่งนี้
Boris Däppen

ที่จริงแล้วมันขึ้นอยู่กับระบบปฏิบัติการ! ใน Windows มันจะรอบครึ่งห่างจากศูนย์และ UNIX เหมือนรอบครึ่งประสงค์ที่จะได้: exploringbinary.com/...
APOC

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 นั้นใช้งานได้ดีอย่างเท่าเทียมกัน
Joel Berger

1
นี่ไม่ซับซ้อนจริงๆและคำตอบของ RET นั้นเกี่ยวข้องกับคณิตศาสตร์หลายอย่างที่ก) ความเสี่ยงในเชิงทฤษฎีมากเกินไป b) ใช้เวลานานกว่าและ c) ไม่จำเป็นต้องแนะนำ 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(เมื่อคุณต้องการทศนิยมมันคือเมื่อ จอแสดงผลได้รับ wonky)

ในขณะที่มันถูกต้องและสำหรับคณิตศาสตร์ไม่ใช่เรื่องใหญ่สำหรับการแสดงมันก็ดูแปลก ๆ ที่แสดงบางอย่างเช่น "-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]);
Jarett Lloyd

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.