2013-02-06 28 views
5

Tôi có một đơn giản cho vòng lặp trong PerlPerl cho vòng lặp đi haywire

for ($i=0; $i <= 360; $i += 0.01) 
{ 
print "$i "; 
} 

Tại sao nó rằng khi tôi chạy mã này tôi nhận được đầu ra sau, nơi mà ngay sau khi nó được đến 0,81 nó thình lình bắt đầu thêm vào một tải thêm chữ số thập phân? Tôi biết tôi chỉ đơn giản là có thể làm tròn để tránh vấn đề này nhưng tôi đã tự hỏi tại sao nó lại xảy ra. Sự gia tăng 0,01 dường như không hề điên rồ.

0.77 
0.78 
0.79 
0.8 
0.81 
0.820000000000001 
0.830000000000001 
0.840000000000001 
0.850000000000001 
0.860000000000001 
0.870000000000001 
+2

http://learn.perl.org/faq/perlfaq4.html#Why-am-I-getting-long-decimals-eg-19.9499999999999-instead-of-the- number-I-should-be-getting-eg-19.95- – mob

+0

http://stackoverflow.com/questions/10908825/, http://stackoverflow.com/questions/9790048, http://stackoverflow.com/questions/ 3916314, http://stackoverflow.com/questions/7066636, http://stackoverflow.com/questions/2080629, http://stackoverflow.com/questions/2056681, http://stackoverflow.com/questions/14204125 – mob

+0

8.1 có vẻ là một con số đáng kể để chuyển đổi. Nhiều năm trước, tôi đã có một dự án mini cố gắng tìm một số cách để thao tác một giá trị cụ thể: '8.10' chính xác. Cả Java và Perl, đều gặp vấn đề với con số này. – Axeman

Trả lời

15

Máy tính sử dụng biểu diễn nhị phân. Không phải tất cả các số dấu chấm động thập phân đều có các biểu diễn chính xác trong ký pháp nhị phân, do đó một số lỗi có thể xảy ra (sự khác biệt thực sự của nó). Đây là lý do tương tự why you shouldn't use floating point numbers for monetary values:

messed up recepit http://img.thedailywtf.com/images/200902/errord/DSC00669.JPG

(Ảnh lấy từ dailywtf)

cách thanh lịch nhất để có được xung quanh vấn đề này là sử dụng số nguyên để tính toán, phân chia chúng cho chính xác số thập phân địa điểm và sử dụng sprintf để giới hạn số chữ số thập phân được in.Đây sẽ vừa đảm bảo:

  • Luôn luôn để sửa kết quả in
  • Các lỗi làm tròn không tích lũy

Hãy thử mã này:

#!/usr/bin/perl 
for ($i=0; $i <= 360*100; $i += 1) { 
    printf "%.2f \n", $i/100; 
} 
+7

+1 cho bằng chứng tài liệu về thực hành lập trình không chính xác trong tự nhiên! –

7

Về cơ bản, vì số thập phân 0,01 không có đại diện chính xác trong dấu phẩy động, nên theo thời gian, thêm gần đúng 0,01 lệch khỏi câu trả lời bạn muốn.

Đây là thuộc tính cơ bản của số học dấu chấm động (nhị phân) và không đặc thù với Perl. What Every Computer Scientist Should Know About Floating-Point Arithmetic là tham chiếu chuẩn và bạn có thể dễ dàng tìm thấy nó bằng tìm kiếm của Google.

Xem thêm: C compiler bug (floating point arithmetic) và không nghi ngờ gì nữa vô số câu hỏi khác.


Kernighan & Plauger nói, trong cũ nhưng cổ điển cuốn sách của họ "The Elements of Programming Style", rằng:

  • Một lập trình viên cũ khôn ngoan đã từng nói "nổi số điểm giống như cọc ít cát; mỗi khi bạn di chuyển một , bạn mất một ít cát và có được một ít bụi bẩn ".

Họ cũng nói:

  • 10 * 0,1 là hầu như không bao giờ hết 1,0

Cả hai câu nói chỉ ra rằng điểm số học nổi là không chính xác.

Lưu ý rằng một số CPU hiện đại (IBM PowerPC) có tích hợp số thập phân dấu chấm động IEEE 754: 2008 được tích hợp sẵn. Nếu Perl sử dụng đúng loại (có thể là không), thì tính toán của bạn sẽ chính xác.

2

1/10 là định kỳ trong nhị phân giống như 1/3 là định kỳ theo số thập phân. Như vậy, nó không thể được lưu trữ chính xác trong một số dấu chấm động.

>perl -E"say sprintf '%.17f', 0.1" 
0.10000000000000001 

Hoặc làm việc với số nguyên

for (0*100..360*100) { 
    my $i = $_/100; 
    print "$i "; 
} 

Hoặc làm nhiều làm tròn

for (my $i=0; $i <= 360; $i = sprintf('%.2f', $i + 0.01)) { 
    print "$i "; 
} 
4

Để chứng minh câu trả lời của Jonathan, nếu bạn mã lên cấu trúc vòng lặp tương tự trong C, bạn sẽ nhận được cùng một kết quả. Mặc dù C và Perl có thể biên dịch khác nhau và được chạy trên các máy khác nhau, các quy tắc số học dấu chấm động nằm bên dưới sẽ gây ra các đầu ra nhất quán. Lưu ý: Perl sử dụng điểm nổi chính xác kép cho biểu diễn dấu phẩy động của nó trong khi trong C coder chọn rõ ràng float hoặc double.

Vòng trong C:

#include <stdio.h> 

    int main() { 
     double i; 
     for(i=0;i<=1;i+=.01) { 
      printf("%.15f\n",i); 
     } 
     } 

Output:

0.790000000000000 
    0.800000000000000 
    0.810000000000000 
    0.820000000000001 
    0.830000000000001 
    0.840000000000001 
    0.850000000000001 

Để chứng minh quan điểm thậm chí hơn nữa, mã vòng lặp trong C nhưng bây giờ sử dụng chính xác đơn điểm số học nổi và thấy rằng sản lượng ít chính xác hơn và thậm chí còn thất thường hơn.

Output:

0.000000000000000 
    0.009999999776483 
    0.019999999552965 
    0.029999999329448 
    0.039999999105930 
    0.050000000745058 
    0.060000002384186 
    0.070000000298023 
    0.079999998211861 
    0.089999996125698 
    0.099999994039536 
Các vấn đề liên quan