Sự khác biệt giữa float và double là gì?


420

Tôi đã đọc về sự khác biệt giữa độ chính xác kép và độ chính xác đơn. Tuy nhiên, trong hầu hết các trường hợp floatdoubledường như có thể thay thế cho nhau, tức là sử dụng cái này hay cái kia dường như không ảnh hưởng đến kết quả. Đây đúng là tình trạng đó phải không? Khi nào phao và đôi có thể hoán đổi cho nhau? Sự khác biệt giữa chúng là gì?

Câu trả lời:


521

Sự khác biệt lớn.

Như tên của nó, a doublecó độ chính xác gấp 2 lần [1] . Nói chung a có 15 chữ số thập phân chính xác, trong khi có 7.floatdoublefloat

Đây là cách tính số chữ số:

doublecó 52 bit mantissa + 1 bit ẩn: log (2 53 ) log (10) = 15,95 chữ số

floatcó 23 bit mantissa + 1 bit ẩn: log (2 24 ) log (10) = 7,22 chữ số

Mất chính xác này có thể dẫn đến các lỗi cắt ngắn lớn hơn được tích lũy khi thực hiện các phép tính lặp lại, ví dụ

float a = 1.f / 81;
float b = 0;
for (int i = 0; i < 729; ++ i)
    b += a;
printf("%.7g\n", b); // prints 9.000023

trong khi

double a = 1.0 / 81;
double b = 0;
for (int i = 0; i < 729; ++ i)
    b += a;
printf("%.15g\n", b); // prints 8.99999999999996

Ngoài ra, giá trị tối đa của float là về 3e38, nhưng gấp đôi là về 1.7e308, vì vậy việc sử dụng floatcó thể đạt "vô cực" (tức là một số dấu phẩy động đặc biệt) dễ dàng hơn nhiều so doublevới một cái gì đó đơn giản, ví dụ như tính toán giai thừa 60.

Trong quá trình thử nghiệm, có thể một vài trường hợp thử nghiệm chứa những con số khổng lồ này, điều này có thể khiến các chương trình của bạn bị lỗi nếu bạn sử dụng phao.


Tất nhiên, đôi khi, thậm chí doublekhông đủ chính xác, do đó đôi khi chúng ta có long double[1] (ví dụ trên cho 9.000000000000000066 trên Mac), nhưng tất cả các loại dấu phẩy động đều bị lỗi làm tròn , vì vậy nếu độ chính xác là rất quan trọng (ví dụ: tiền xử lý) bạn nên sử dụng inthoặc một lớp phân số.


Hơn nữa, không sử dụng +=để tổng hợp nhiều số dấu phẩy động, vì các lỗi tích lũy nhanh chóng. Nếu bạn đang sử dụng Python, hãy sử dụng fsum. Nếu không, hãy thử thực hiện thuật toán tổng hợp Kahan .


[1]: Tiêu chuẩn C và C ++ không chỉ định các đại diện của float, doublelong double. Có thể là cả ba đều được triển khai dưới dạng chính xác kép của IEEE. Tuy nhiên, đối với hầu hết kiến trúc (gcc, MSVC; x86, x64, ARM) float thực sự là một IEEE đơn chính xác số điểm (binary32) nổi, và double một đôi có độ chính xác nổi số điểm IEEE (binary64).


9
Lời khuyên thông thường cho tổng kết là sắp xếp các số dấu phẩy động của bạn theo độ lớn (nhỏ nhất trước) trước khi tính tổng.
R .. GitHub DỪNG GIÚP ICE

Lưu ý rằng trong khi C / C ++ float và double gần như luôn luôn là độ chính xác đơn và đôi của C, thì C / C ++ dài gấp đôi có thể thay đổi nhiều hơn tùy thuộc vào CPU, trình biên dịch và HĐH của bạn. Đôi khi, nó giống như gấp đôi, đôi khi nó là một định dạng mở rộng dành riêng cho hệ thống, Đôi khi, đó là độ chính xác của bộ tứ.
cắm vào

@ R..GitHubSTOPHELPINGICE: tại sao? Bạn có thể giải thích?
InQusitive

@InQusitive: Ví dụ, hãy xem xét một mảng bao gồm giá trị 2 ^ 24 sau đó là 2 ^ 24 lần lặp lại giá trị 1. Tổng hợp theo thứ tự tạo ra 2 ^ 24. Đảo ngược tạo ra 2 ^ 25. Tất nhiên, bạn có thể tạo ra các ví dụ (ví dụ: thực hiện 2 ^ 25 lần lặp lại 1) trong đó bất kỳ đơn hàng nào kết thúc là sai lầm nghiêm trọng với một bộ tích lũy duy nhất nhưng trước hết là nhỏ nhất là tốt nhất. Để làm tốt hơn bạn cần một số loại cây.
R .. GitHub DỪNG GIÚP ICE

56

Dưới đây là những gì các tiêu chuẩn C99 (ISO-IEC 9899 6.2.5 §10) hoặc C ++ 2003 (ISO-IEC 14882-2003 3.1.9 §8) nói:

Có ba loại dấu chấm động: float, double, và long double. Loại doublecung cấp ít nhất độ chính xác như float, và loại long doublecung cấp ít nhất độ chính xác như double. Tập hợp các giá trị của loại floatlà tập hợp con của tập hợp các giá trị của loại double; tập hợp các giá trị của kiểu doublelà tập hợp con của tập hợp các giá trị của kiểu long double.

Tiêu chuẩn C ++ cho biết thêm:

Biểu diễn giá trị của các kiểu dấu phẩy động được xác định theo thực hiện.

Tôi muốn đề xuất một cái nhìn tuyệt vời về những gì mà mọi nhà khoa học máy tính nên biết về số học dấu phẩy động bao trùm tiêu chuẩn của dấu phẩy động theo chiều sâu. Bạn sẽ tìm hiểu về các chi tiết đại diện và bạn sẽ nhận ra có sự đánh đổi giữa cường độ và độ chính xác. Độ chính xác của biểu diễn điểm nổi tăng khi cường độ giảm, do đó các số dấu phẩy động giữa -1 và 1 là các số có độ chính xác cao nhất.


27

Cho phương trình bậc hai: x 2  - 4.0000000  x  + 3.9999999 = 0, gốc chính xác đến 10 chữ số có nghĩa là, r 1  = 2.000316228 và r 2  = 1.999683772.

Sử dụng floatdouble, chúng tôi có thể viết một chương trình thử nghiệm:

#include <stdio.h>
#include <math.h>

void dbl_solve(double a, double b, double c)
{
    double d = b*b - 4.0*a*c;
    double sd = sqrt(d);
    double r1 = (-b + sd) / (2.0*a);
    double r2 = (-b - sd) / (2.0*a);
    printf("%.5f\t%.5f\n", r1, r2);
}

void flt_solve(float a, float b, float c)
{
    float d = b*b - 4.0f*a*c;
    float sd = sqrtf(d);
    float r1 = (-b + sd) / (2.0f*a);
    float r2 = (-b - sd) / (2.0f*a);
    printf("%.5f\t%.5f\n", r1, r2);
}   

int main(void)
{
    float fa = 1.0f;
    float fb = -4.0000000f;
    float fc = 3.9999999f;
    double da = 1.0;
    double db = -4.0000000;
    double dc = 3.9999999;
    flt_solve(fa, fb, fc);
    dbl_solve(da, db, dc);
    return 0;
}  

Chạy chương trình cho tôi:

2.00000 2.00000
2.00032 1.99968

Lưu ý rằng số lượng không lớn, nhưng bạn vẫn nhận được hiệu ứng hủy bằng cách sử dụng float.

(Trên thực tế, ở trên không phải là cách tốt nhất để giải phương trình bậc hai bằng cách sử dụng các số dấu phẩy động chính xác đơn hoặc kép, nhưng câu trả lời vẫn không thay đổi ngay cả khi sử dụng phương pháp ổn định hơn .)


19
  • Một nhân đôi là 64 và độ chính xác đơn (float) là 32 bit.
  • Nhân đôi có mantissa lớn hơn (các bit nguyên của số thực).
  • Bất kỳ sự không chính xác sẽ nhỏ hơn trong gấp đôi.

12

Kích thước của các số liên quan đến tính toán dấu phẩy động không phải là điều phù hợp nhất. Đó là phép tính đang được thực hiện có liên quan.

Về bản chất, nếu bạn đang thực hiện một phép tính và kết quả là một số vô tỷ hoặc số thập phân lặp lại, thì sẽ có các lỗi làm tròn khi số đó được nén vào cấu trúc dữ liệu kích thước hữu hạn mà bạn đang sử dụng. Vì gấp đôi kích thước của float, lỗi làm tròn sẽ nhỏ hơn rất nhiều.

Các thử nghiệm có thể sử dụng cụ thể các số gây ra loại lỗi này và do đó đã kiểm tra rằng bạn đã sử dụng loại thích hợp trong mã của mình.


9

Loại float, dài 32 bit, có độ chính xác 7 chữ số. Mặc dù nó có thể lưu trữ các giá trị với phạm vi rất lớn hoặc rất nhỏ (+/- 3,4 * 10 ^ 38 hoặc * 10 ^ -38), nó chỉ có 7 chữ số có nghĩa.

Nhập gấp đôi, dài 64 bit, có phạm vi lớn hơn (* 10 ^ + / - 308) và độ chính xác 15 chữ số.

Kiểu dài gấp đôi là 80 bit, mặc dù một trình biên dịch / ghép nối nhất định có thể lưu trữ nó dưới dạng 12-16 byte cho mục đích căn chỉnh. Bộ đôi dài có số mũ rất lớn một cách lố bịch và nên có độ chính xác 19 chữ số. Microsoft, theo trí tuệ vô hạn của họ, giới hạn dài gấp đôi đến 8 byte, giống như gấp đôi đơn giản.

Nói chung, chỉ sử dụng loại double khi bạn cần một giá trị / biến dấu phẩy động. Giá trị dấu phẩy động được sử dụng trong các biểu thức sẽ được coi là nhân đôi theo mặc định và hầu hết các hàm toán học trả về giá trị dấu phẩy động trả về gấp đôi. Bạn sẽ tiết kiệm cho mình nhiều vấn đề đau đầu và lỗi đánh máy nếu bạn chỉ sử dụng gấp đôi.


Trên thực tế, đối với float , chính xác là từ 7 đến 8, 7.225 .
Peter Mortensen

9

Tôi vừa gặp phải một lỗi khiến tôi phải tìm ra mãi mãi và có khả năng có thể cho bạn một ví dụ điển hình về độ chính xác của float.

#include <iostream>
#include <iomanip>

int main(){
  for(float t=0;t<1;t+=0.01){
     std::cout << std::fixed << std::setprecision(6) << t << std::endl;
  }
}

Đầu ra là

0.000000
0.010000
0.020000
0.030000
0.040000
0.050000
0.060000
0.070000
0.080000
0.090000
0.100000
0.110000
0.120000
0.130000
0.140000
0.150000
0.160000
0.170000
0.180000
0.190000
0.200000
0.210000
0.220000
0.230000
0.240000
0.250000
0.260000
0.270000
0.280000
0.290000
0.300000
0.310000
0.320000
0.330000
0.340000
0.350000
0.360000
0.370000
0.380000
0.390000
0.400000
0.410000
0.420000
0.430000
0.440000
0.450000
0.460000
0.470000
0.480000
0.490000
0.500000
0.510000
0.520000
0.530000
0.540000
0.550000
0.560000
0.570000
0.580000
0.590000
0.600000
0.610000
0.620000
0.630000
0.640000
0.650000
0.660000
0.670000
0.680000
0.690000
0.700000
0.710000
0.720000
0.730000
0.740000
0.750000
0.760000
0.770000
0.780000
0.790000
0.800000
0.810000
0.820000
0.830000
0.839999
0.849999
0.859999
0.869999
0.879999
0.889999
0.899999
0.909999
0.919999
0.929999
0.939999
0.949999
0.959999
0.969999
0.979999
0.989999
0.999999

Như bạn có thể thấy sau 0,83, độ chính xác giảm đáng kể.

Tuy nhiên, nếu tôi thiết lập tgấp đôi, vấn đề như vậy sẽ không xảy ra.

Tôi mất năm giờ để nhận ra lỗi nhỏ này, làm hỏng chương trình của tôi.


4
chỉ để chắc chắn: giải pháp cho vấn đề của bạn nên sử dụng một int tốt nhất? Nếu bạn muốn lặp lại 100 lần, bạn nên tính bằng số nguyên thay vì sử dụng số kép
BlueTrin

8
Sử dụng doublekhông phải là một giải pháp tốt ở đây. Bạn sử dụng intđể đếm và thực hiện phép nhân nội bộ để nhận giá trị dấu phẩy động của mình.
Richard


3

Khi sử dụng số dấu phẩy động, bạn không thể tin rằng các kiểm tra cục bộ của mình sẽ giống hệt như các kiểm tra được thực hiện ở phía máy chủ. Môi trường và trình biên dịch có thể khác nhau trên hệ thống cục bộ của bạn và nơi các bài kiểm tra cuối cùng được chạy. Tôi đã thấy vấn đề này nhiều lần trước đây trong một số cuộc thi TopCoder đặc biệt là nếu bạn cố gắng so sánh hai số dấu phẩy động.


3

Các hoạt động so sánh tích hợp khác nhau như khi bạn so sánh 2 số với dấu phẩy động, sự khác biệt về kiểu dữ liệu (tức là float hoặc double) có thể dẫn đến các kết quả khác nhau.


1

Nếu một người làm việc với xử lý nhúng, cuối cùng, phần cứng cơ bản (ví dụ: FPGA hoặc một số mô hình bộ vi xử lý / vi điều khiển cụ thể) sẽ được triển khai tối ưu trong phần cứng trong khi nhân đôi sẽ sử dụng các phần mềm. Vì vậy, nếu độ chính xác của một float là đủ để đáp ứng các nhu cầu, chương trình sẽ thực hiện nhanh hơn một vài lần với float sau đó tăng gấp đôi. Như đã lưu ý về các câu trả lời khác, hãy cẩn thận với các lỗi tích lũy.


-1

Không giống như int(toàn bộ số), a floatcó dấu thập phân và a cũng có thể double. Nhưng sự khác biệt giữa hai là a doublechi tiết gấp đôi so với a float, nghĩa là nó có thể có gấp đôi số lượng sau dấu thập phân.


4
Nó không có nghĩa là ở tất cả. Nó thực sự có nghĩa là gấp đôi số thập phân tích phân, và nó nhiều hơn gấp đôi. Mối quan hệ giữa các chữ số phân số và độ chính xác không phải là tuyến tính: nó phụ thuộc vào giá trị: ví dụ 0,5 là chính xác nhưng 0,33333333333333333333 thì không.
Hầu tước Lorne
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.