2017-11-30 18 views
6

Hãy xem mã này. 10/3 return 3.333333 604736328125000 và khi tôi nhân với 3 trong calcutor tôi nhận được 9,99, nhưng nếu làm như vậy bằng mã tôi nhận được chính xác 10,00. Làm thế nào nó có thể thực hiện được?Tại sao 10/3 nó chính xác trong C?

#include <stdlib.h> 
#include <stdio.h> 

int main() { 

    float v = 10.f/3.f; 
    float test = v*3.f; 
    printf("10/3 => %25.25f \n (10/3)*3 => %25.25f\n",v,test); 
    return 0; 
} 

Đây là mã lắp ráp mà không printf, biên soạn sử dụng mặc định gcc 7.2.1 thông số:

0000000000400497 <main>: 
    400497:  55      push rbp 
    400498:  48 89 e5    mov rbp,rsp 
    40049b:  f3 0f 10 05 b1 00 00 movss xmm0,DWORD PTR [rip+0xb1]  # 400554 <_IO_stdin_used+0x4> 
    4004a2:  00 
    4004a3:  f3 0f 11 45 fc   movss DWORD PTR [rbp-0x4],xmm0 
    4004a8:  f3 0f 10 4d fc   movss xmm1,DWORD PTR [rbp-0x4] 
    4004ad:  f3 0f 10 05 a3 00 00 movss xmm0,DWORD PTR [rip+0xa3]  # 400558 <_IO_stdin_used+0x8> 
    4004b4:  00 
    4004b5:  f3 0f 59 c1    mulss xmm0,xmm1 
    4004b9:  f3 0f 11 45 f8   movss DWORD PTR [rbp-0x8],xmm0 
    4004be:  b8 00 00 00 00   mov eax,0x0 
    4004c3:  5d      pop rbp 
    4004c4:  c3      ret  
    4004c5:  66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0] 
    4004cc:  00 00 00 
    4004cf:  90      nop 

Tôi nghĩ mulss được làm tròn bởi một tính năng CPU.

Đối với lưu ý, 10/3 trong chương trình GNU BC trả 3,3333333333333333333333 (* 3 => 9,9999) và ở SciLab trả 3,333333333333333 631 (* 3 => 10).

+0

Nhận xét không dành cho thảo luận mở rộng; cuộc hội thoại này đã được [chuyển sang trò chuyện] (http://chat.stackoverflow.com/rooms/160240/discussion-on-question-by-amanda-osvaldo-why-10-3-its-exact-in-c) . – Andy

+1

Kết quả 'mulss' được làm tròn bằng cách sử dụng chế độ làm tròn IEEE754 mặc định, là vòng tròn gần nhất, thậm chí là điểm ngắt. (Chương trình của bạn không sử dụng '#pragma FENV_ACCESS ON' và đặt chế độ làm tròn FP thành một cái gì đó khác, do đó trình biên dịch tạo ra một chương trình sử dụng chế độ làm tròn mặc định.) –

Trả lời

8

Bạn sẽ nhận được chính xác 10 kết quả vì biểu diễn diễn ra theo cách đó. Tôi nhận được như vậy trên thực hiện của tôi cho cả float và double.

Hãy xem xét một ví dụ sử dụng kép:

Nếu chúng tôi in ra 10./3. trong hệ thập lục phân nổi ký hiệu điểm sử dụng %a, chúng tôi có được điều này:

0x1.aaaaaaaaaaaabp+1 

này phù hợp với các IEEE754 đại diện đôi 0x401aaaaaaaaaaaab.

Số trên bình thường là:

0x3.5555555555558 

Trong nhị phân:

11.0101010101010101010101010101010101010101010101011 

Để giữ cho mọi thứ đơn giản, chúng ta hãy thêm ba lần thay vì nhân với 3:

 11.0101010101010101010101010101010101010101010101011 
+ 11.0101010101010101010101010101010101010101010101011 
--------------------------------------------------------- 
    110.1010101010101010101010101010101010101010101010111 
+ 11.0101010101010101010101010101010101010101010101011 
--------------------------------------------------------- 
    1010.0000000000000000000000000000000000000000000000000 

nào chính xác là 10.

CHỈNH SỬA:

Có vẻ như tôi đã quản lý được phép tính toán trên một vài chữ số cuối cùng. Tổng thực tế:

 11.0101010101010101010101010101010101010101010101011 
+ 11.0101010101010101010101010101010101010101010101011 
--------------------------------------------------------- 
    110.1010101010101010101010101010101010101010101010110 
+ 11.0101010101010101010101010101010101010101010101011 
--------------------------------------------------------- 
    1010.0000000000000000000000000000000000000000000000001 

Vì vậy, nó không chính xác 10, nhưng tắt bởi các bit ít quan trọng.

Tôi nhận thấy sự khác biệt tương tự khi sử dụng phao.

10.f/3.f in với %a:

0x1.aaaaaap+1 

bình thường hóa:

0x3.555554 

Trong nhị phân:

11.0101010101010101010101 

Sau đó chúng ta thêm:

 11.0101010101010101010101 
+ 11.0101010101010101010101 
------------------------------ 
    110.1010101010101010101010 
+ 11.0101010101010101010101 
------------------------------ 
    1001.1111111111111111111111 

Một lần nữa, tắt theo bit ít quan trọng nhất.

Đối với cách kết quả thực tế được làm tròn, tôi không thể nói.

+0

Đó có phải là quyền bổ sung đầu tiên không? Các bit ngoài cùng bên phải là các bit, không nên '1 + 1' bằng' 10', thay vì '11' như được hiển thị ở đây? Và tôi nghĩ rằng việc bổ sung nên để lại một đi lạc '1' một nơi nào đó ở vị trí ngoài cùng bên phải. Nó sẽ có thể được làm tròn ra sau đó, nhưng ... – ilkkachu

+0

@ilkkachu: FP nhân không tròn cho đến khi kết thúc, vì vậy bất kỳ nỗ lực để tái tạo nó với bổ sung cần phải giữ thêm bit. Mặc dù tôi nghĩ rằng đây là nghĩa vụ phải được toán học nhị phân đồng bằng, không exponent/mantissa (với điểm radix ở đúng nơi để tách nguyên từ phần phân đoạn). Tôi đồng ý điều này có vẻ tanh, nhưng sửa chữa đúng là giữ độ chính xác cao hơn. –

+1

@PeterCordes, vâng, tôi có nghĩa là làm tròn vào cuối. Kết quả có nhiều bit quan trọng hơn trong phần nguyên, do đó một số bit phải làm tròn từ phần phân số – ilkkachu

2

Lý do cho sự khác biệt giữa những gì bạn thấy trong C và những gì bạn nhìn thấy trong SciLab là bạn đang sử dụng đơn -precision giá trị dấu chấm động (float) trong C, trong khi SciLab dường như sử dụng đôi - giá trị chính xác (double) theo mặc định.

Bạn có thể thấy sự khác biệt here (chỉ cần xóa hậu tố f khỏi số của bạn và đặt double thay vì float).

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