2010-02-22 47 views
6

Làm thế nào đến ceil() làm tròn lên một phần nổi thậm chí không có phần phân đoạn?
Khi tôi cố gắng để làm điều này:math.h ceil không hoạt động như mong đợi trong C

double x = 2.22; 
x *= 100; //which becomes 222.00... 
printf("%lf", ceil(x)); //prints 223.00... (?) 

Nhưng khi tôi thay đổi giá trị 2,22 đến 2,21

x *= 100; //which becomes 221.00... 
printf("%lf", ceil(x)); //prints 221.00... as expected 

tôi đã cố gắng để làm điều đó một cách khác như thế này sử dụng modf() và gặp phải khác lạ điều:

double x = 2.22 * 100; 
double num, fraction; 
fraction = modf(x, &num); 
if(fraction > 0) 
    num += 1; //goes inside here even when fraction is 0.00... 

Vậy điều gì xảy ra là 0,000 ... lớn hơn 0?
Có ai giải thích tại sao cả hai tình huống này đang xảy ra? Ngoài ra tôi đang biên dịch bằng cách sử dụng phiên bản cc 4.1.2 trong RedHat.

+0

Bản sao: http://stackoverflow.com/questions/177506/why-do-i-see-a-double-variable-initialized-to-some-value-like-21-4-as-21-39999961 , http://stackoverflow.com/questions/1089018/why-cant-decimal-numbers-be-represented-exactly-in-binary –

Trả lời

12

Điều này là bình thường vì các số được lưu trữ bằng cách sử dụng nhị phân. Trong khi các số của bạn có thể được viết bằng số hữu hạn các chữ số bằng cách sử dụng số thập phân, thì số này không giữ cho nhị phân.

Bạn nên đọc báo cáo của Goldberg có tên là What Every Computer Scientist Should Know About Floating-Point Arithmetic.

+2

Mọi người luôn chỉ vào bài viết đó, nhưng nó không phải là tuyệt vời. Bạn nên đọc wikipedia –

12

Câu trả lời cơ bản là số lượng điểm nổi bạn nhận được với:

double x = 2.22; 

thực sự là một chút lớn hơn giá trị 2.22, và giá trị mà bạn nhận được với

double x = 2.21; 

là một nhỏ hơn một chút so với 2.21.

2

Đó là bởi vì 2.22 là không chính xác 2.22 và do đó 2,22 * 100 không phải là 222 nhưng 222.000000000x

double x = 2.22; 
printf("%.20lf\n", x); //prints 2.22000000000000019540 
x *= 100; 
printf("%.20lf\n", x); //prints 222.00000000000002842171 

Nếu bạn cần số nguyên chính xác (ví dụ như để tính tiền những thứ liên quan) sử dụng số học không thể thiếu (ví dụ tính toán cent thay vì đô la).

+3

Đó là một lỗi phổ biến để đề xuất số nguyên hoặc số học điểm cố định cho các ứng dụng tài chính, trong thực tế dấu chấm thập phân (ngược với dấu phẩy động nhị phân) được sử dụng. Đối với tính toán tỷ lệ phần trăm (lãi suất hoặc thuế chẳng hạn), việc tính bằng xu dẫn đến trường hợp Siêu nhân III/Office Space! ;-) – Clifford

+0

@Clifford: bạn có một con trỏ như thế nào bằng cách sử dụng dấu chấm thập phân sửa chữa các kịch bản Superman III/Office Space? (Tôi không nói nó không - tôi thành thật tò mò). –

+0

@Clifford - AFAIK GnuCash đang sử dụng số học điểm cố định vì vậy tôi nghi ngờ sử dụng điểm cố định chính xác là không thể. – diciu

7

Không phải tất cả các giá trị dấu phẩy động đều được thể hiện chính xác theo các loại floatdouble C - rất có thể là những gì bạn nghĩ là 222 thực sự giống như 222.00000000000000001.

Có một tiêu chuẩn, nhưng cách ít được biết đến để có được xung quanh nó - sử dụng nextafter() C99 chức năng:

printf("%lf", ceil(nextafter(x, 0))); 

Xem man nextafter để biết chi tiết.

+3

Thú vị! Xem thêm 'nearbyint' và 'rint'. –

+0

@Jonathan 'nearbyint()' vòng để đóng số nguyên, 'nextafter()' cho phép bạn chỉ định hướng - 'INFINITY',' 0', '-INFINITY' hoặc bất kỳ số tùy ý nào. – qrdl

+1

Vâng, tôi nhận ra điều đó. Nhưng 'nextafter()' tạo ra giá trị điểm nổi với bit ít quan trọng nhất của mantissa được điều chỉnh bởi một: 'một giá trị tương ứng với giá trị của bit ít quan trọng nhất trong phần được thêm hoặc trừ, phụ thuộc vào hướng'. Hàm 'nextafter()' sẽ hoạt động miễn là lỗi không nhiều hơn 1 trong bit ít quan trọng nhất - có khả năng là trường hợp ở đây. Tôi đã không đề nghị rằng 'gần đó' hoặc 'rint' là một sự thay thế cho 'nextafter' (hoặc 'nexttoward'); chỉ là họ cũng thú vị và có thể có liên quan đến OP. –

0

Bạn có thể muốn xem xét sử dụng decimal floating point types để nhận được kết quả mong muốn từ số học thập phân. Phần cứng trên bộ vi xử lý máy tính để bàn điển hình chỉ hỗ trợ dấu chấm động nhị phân, vì vậy không thể mong đợi để tạo ra kết quả tương tự như phép tính thập phân. Tất nhiên, nếu không được hỗ trợ trong phần cứng, dấu phẩy động thập phân sẽ chậm hơn. Lưu ý rằng dấu phẩy thập phân không nhất thiết chính xác hơn (ví dụ 1/3 không thể được biểu diễn chính xác, cũng giống như có giá trị trong FP nhị phân không thể được biểu diễn), nó sẽ chỉ tạo ra kết quả được mong đợi là nếu bạn thực hiện phép tính dài trong thập phân.

1

Nếu bạn đã viết:

const int x = 1.2; 

trong một chương trình C, điều gì sẽ xảy ra? Chữ số 1.2 không thể đại diện dưới dạng giá trị số nguyên, do đó trình biên dịch sẽ chuyển đổi theo các quy tắc thông thường thành số nguyên và x sẽ được gán giá trị 1.

Điều tương tự cũng xảy ra ở đây.

Bạn đã viết:

double x = 2.22; 

2.22 không phải là biểu diễn bằng số đôi có độ chính xác, vì vậy trình biên dịch chuyển nó theo các quy tắc trong tiêu chuẩn C. Bạn nhận được các số gần nhất biểu diễn đôi chính xác, đó là chính xác:

2.220000000000000195399252334027551114559173583984375 

Khi giá trị này được nhân với 100 trong double, kết quả là:

222.000000000000028421709430404007434844970703125 

và khi bạn gọi ceil() với giá trị đó như một đối số, thư viện toán học trả về chính xác 223.0.

Các vấn đề liên quan