ความแตกต่างระหว่าง float และ double คืออะไร?


420

ฉันได้อ่านเกี่ยวกับความแตกต่างระหว่างความแม่นยำสองเท่าและความแม่นยำเดี่ยว อย่างไรก็ตามในกรณีส่วนใหญ่floatและdoubleดูเหมือนว่าจะใช้แทนกันคือการใช้อย่างใดอย่างหนึ่งดูเหมือนจะไม่ส่งผลกระทบต่อผล เป็นกรณีนี้จริงเหรอ? เมื่อไหร่ที่จะลอยและเปลี่ยนเป็นสองเท่าได้? ความแตกต่างระหว่างพวกเขาคืออะไร?

คำตอบ:


521

ความแตกต่างอย่างมาก

ในฐานะที่เป็นชื่อที่แสดงถึงการdoubleมี 2x ความแม่นยำของ[1] โดยทั่วไป a มีความแม่นยำ 15 หลักทศนิยมในขณะที่มี 7floatdoublefloat

นี่คือวิธีคำนวณจำนวนตัวเลข:

doubleมี 52 mantissa บิต ​​+ 1 บิตที่ซ่อน: บันทึก (2 53 ) ÷บันทึก (10) = 15.95 หลัก

floatมี 23 mantissa บิต ​​+ 1 บิตที่ซ่อน: บันทึก (2 24 ) ÷บันทึก (10) = 7.22 หลัก

การสูญเสียความแม่นยำนี้อาจนำไปสู่ข้อผิดพลาดที่ถูกตัดทอนมากขึ้นเมื่อมีการคำนวณซ้ำเช่น

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

ในขณะที่

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

นอกจากนี้ค่าสูงสุดของการลอยคือประมาณ3e38แต่เพิ่มขึ้นเป็นสองเท่า1.7e308ดังนั้นการใช้floatสามารถกด "อนันต์" (เช่นหมายเลขจุดลอยตัวพิเศษ) ได้ง่ายกว่าdoubleสิ่งที่ง่ายเช่นการคำนวณแฟคทอเรียลของ 60

ในระหว่างการทดสอบอาจมีบางกรณีทดสอบที่มีจำนวนมากซึ่งอาจทำให้โปรแกรมของคุณล้มเหลวหากคุณใช้โฟลต


แน่นอนบางครั้งแม้doubleจะไม่ถูกต้องดังนั้นบางครั้งเราจึงมีlong double[1] (ตัวอย่างข้างต้นให้ 9.000000000000000066 บน Mac) แต่ประเภทจุดลอยตัวทั้งหมดต้องทนทุกข์จากข้อผิดพลาดในการปัดเศษดังนั้นหากความแม่นยำมีความสำคัญมาก (เช่นเงิน กำลังประมวลผล) คุณควรใช้intหรือคลาสเศษส่วน


นอกจากนี้อย่าใช้+=เพื่อรวมจำนวนจุดลอยตัวเนื่องจากข้อผิดพลาดจะสะสมอย่างรวดเร็ว fsumหากคุณกำลังใช้งูหลามใช้ มิฉะนั้นพยายามที่จะดำเนินการตามขั้นตอนวิธีการบวก Kahan


[1]: ตัว C และ C ++ มาตรฐานไม่ได้ระบุตัวแทนของfloat, และdouble long doubleเป็นไปได้ว่าทั้งสามจะถูกนำมาใช้เป็น IEEE ความแม่นยำสองเท่า อย่างไรก็ตามสำหรับสถาปัตยกรรมมากที่สุด (GCC, MSVC; x86, x64, ARM) float เป็นแน่นอนแม่นยำเดียวจำนวนจุดลอยตัว IEEE (binary32) และdouble เป็นคู่ที่มีความแม่นยำลอยจำนวนจุด IEEE (binary64)


9
คำแนะนำสำหรับการรวมปกติคือการเรียงลำดับหมายเลขทศนิยมของคุณตามขนาด (เล็กที่สุดก่อน) ก่อนที่จะรวม
. GitHub หยุดช่วยน้ำแข็ง

โปรดทราบว่าในขณะที่ C / C ++ float และ double เกือบจะเสมอ IEEE เดี่ยวและคู่ที่มีความแม่นยำตามลำดับ C / C ++ ยาวสองเท่าจะแปรปรวนมากขึ้นอยู่กับ CPU, คอมไพเลอร์และระบบปฏิบัติการของคุณ บางครั้งมันก็เป็นสองเท่าบางครั้งมันเป็นรูปแบบการขยายเฉพาะระบบบางครั้งมันเป็นความแม่นยำรูปสี่เหลี่ยม IEEE
plugwash

@ R..GitHubSTOPHELPINGICE: ทำไม คุณช่วยอธิบายได้ไหม
เชื่อเมื่อ

@InQusitive: พิจารณาตัวอย่างของอาร์เรย์ที่ประกอบด้วยค่า 2 ^ 24 ตามด้วยซ้ำ 2 ^ 24 ของค่า 1 การหาผลรวมเพื่อสร้าง 2 ^ 24 การกลับด้านสร้าง 2 ^ 25 แน่นอนคุณสามารถทำตัวอย่าง (เช่นทำให้ซ้ำได้ 2 ^ 25 ครั้งที่ 1) ซึ่งคำสั่งใด ๆ ที่จบลงด้วยความหายนะที่เกิดขึ้นจากการสะสมเพียงครั้งเดียว แต่ขนาดที่เล็กที่สุดเป็นอันดับแรก เพื่อให้ดีขึ้นคุณต้องมีต้นไม้บางชนิด
. GitHub หยุดช่วยน้ำแข็ง

56

นี่คือสิ่งที่มาตรฐาน C99 (ISO-IEC 9899 6.2.5 §10) หรือ C ++ 2003 (ISO-IEC 14882-2003 3.1.9 §8) มาตรฐานพูดว่า:

มีสามประเภทลอยจุดคือfloat, และdouble long doubleประเภทdoubleให้อย่างน้อยเป็นความแม่นยำมากที่สุดเท่าที่floatและประเภทให้อย่างน้อยเป็นความแม่นยำมากที่สุดเท่าที่long double doubleชุดของค่าของประเภทfloatเป็นส่วนย่อยของชุดของค่าของประเภทdoubleนั้น ชุดของค่าชนิดที่เป็นส่วนหนึ่งของชุดของค่าจากประเภทที่doublelong double

มาตรฐาน C ++ เพิ่ม:

การแทนค่าของชนิดจุดลอยตัวนั้นถูกกำหนดตามการนำไปใช้งาน

ฉันขอแนะนำให้ดูที่ยอดเยี่ยมสิ่งที่นักวิทยาศาสตร์คอมพิวเตอร์ทุกคนควรรู้เกี่ยวกับเลขคณิตทศนิยมที่ครอบคลุมมาตรฐาน IEEE จุดลอยในเชิงลึก คุณจะเรียนรู้เกี่ยวกับรายละเอียดการเป็นตัวแทนและคุณจะรู้ว่ามีการแลกเปลี่ยนระหว่างขนาดและความแม่นยำ ความแม่นยำของการแสดงจุดลอยตัวเพิ่มขึ้นเมื่อขนาดลดลงดังนั้นตัวเลขจุดลอยตัวระหว่าง -1 ถึง 1 จึงแม่นยำที่สุด


27

รับสมการกำลังสอง: x 2  - 4.0000000  x  + 3.9999999 = 0, รากที่แท้จริงถึง 10 หลักที่สำคัญคือr 1  = 2.000316228 และr 2  = 1.999683772

ใช้floatและdoubleเราสามารถเขียนโปรแกรมทดสอบ:

#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;
}  

การใช้งานโปรแกรมให้ฉัน:

2.00000 2.00000
2.00032 1.99968

โปรดทราบว่าตัวเลขที่มีขนาดไม่ใหญ่มาก floatแต่ยังคงได้รับผลกระทบคุณยกเลิกการใช้

(อันที่จริงข้างต้นไม่ใช่วิธีที่ดีที่สุดในการแก้สมการกำลังสองโดยใช้ตัวเลขทศนิยมที่มีความแม่นยำเพียงจุดเดียวหรือสองครั้ง แต่คำตอบยังคงไม่เปลี่ยนแปลงแม้ว่าจะใช้วิธีที่มีเสถียรภาพมากขึ้นก็ตาม)


19
  • คู่คือ 64 และความแม่นยำเดียว (ลอย) คือ 32 บิต
  • double มี mantissa ที่ใหญ่กว่า (จำนวนเต็มบิตของจำนวนจริง)
  • ความไม่ถูกต้องใด ๆ จะเล็กลงในสองเท่า

12

ขนาดของตัวเลขที่เกี่ยวข้องกับการคำนวณทศนิยมนั้นไม่ใช่สิ่งที่เกี่ยวข้องมากที่สุด เป็นการคำนวณที่กำลังดำเนินการซึ่งเกี่ยวข้อง

โดยพื้นฐานแล้วหากคุณทำการคำนวณและผลลัพธ์นั้นเป็นจำนวนอตรรกยะหรือทศนิยมที่เกิดขึ้นซ้ำจะมีข้อผิดพลาดในการปัดเศษเมื่อตัวเลขนั้นถูกบีบลงในโครงสร้างข้อมูลขนาด จำกัด ที่คุณกำลังใช้ เนื่องจาก double เป็นสองเท่าของขนาดลอยดังนั้นข้อผิดพลาดในการปัดเศษจึงมีขนาดเล็กกว่ามาก

การทดสอบอาจใช้ตัวเลขโดยเฉพาะซึ่งจะทำให้เกิดข้อผิดพลาดประเภทนี้ดังนั้นจึงทดสอบว่าคุณใช้ประเภทที่เหมาะสมในรหัสของคุณ


9

ประเภททุ่นยาว 32 บิตมีความแม่นยำ 7 หลัก แม้ว่ามันอาจจะเก็บค่าที่มีช่วงที่มีขนาดใหญ่มากหรือเล็กมาก (+/- 3.4 * 10 ^ 38 หรือ * 10 ^ -38) แต่ก็มีเพียงตัวเลข 7 หลักเท่านั้น

พิมพ์สองเท่าความยาว 64 บิตมีช่วงที่ใหญ่กว่า (* 10 ^ + / - 308) และความแม่นยำ 15 หลัก

Type long double คือ 80 bits แม้ว่าการจับคู่คอมไพเลอร์ / ระบบปฏิบัติการที่กำหนดอาจจัดเก็บเป็น 12-16 ไบต์สำหรับการจัดตำแหน่ง double long มีเลขชี้กำลังที่ใหญ่มากและน่าจะมีความแม่นยำ 19 หลัก Microsoft ในภูมิปัญญาที่ไม่มีที่สิ้นสุดของพวกเขา จำกัด คู่ยาวถึง 8 ไบต์เช่นเดียวกับคู่ธรรมดา

โดยทั่วไปแล้วให้ใช้ type double เมื่อคุณต้องการค่า / ตัวแปรทศนิยม ค่าจุดลอยตัวตามตัวอักษรที่ใช้ในการแสดงออกจะได้รับการปฏิบัติเป็นสองเท่าโดยค่าเริ่มต้นและฟังก์ชั่นทางคณิตศาสตร์ส่วนใหญ่ที่ส่งกลับค่าจุดลอยตัวกลับมาเป็นสองเท่า คุณจะช่วยให้คุณปวดหัวและ typecastings มากมายหากคุณใช้สองครั้ง


ที่จริงสำหรับลอยมันเป็นระหว่างวันที่ 7 และ 8, 7.225 เป็นที่แน่นอน
Peter Mortensen

9

ฉันเพิ่งพบข้อผิดพลาดที่พาฉันไปตลอดกาลเพื่อคิดออกและอาจเป็นตัวอย่างที่ดีของความแม่นยำในการลอย

#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;
  }
}

ผลลัพธ์คือ

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

อย่างที่คุณเห็นหลังจาก 0.83 ความแม่นยำลดลงอย่างมาก

อย่างไรก็ตามหากฉันตั้งค่าtเป็นสองเท่าปัญหาดังกล่าวจะไม่เกิดขึ้น

ฉันใช้เวลาห้าชั่วโมงในการตระหนักถึงข้อผิดพลาดเล็กน้อยนี้ซึ่งทำให้โปรแกรมของฉันเสียหาย


4
เพียงเพื่อให้แน่ใจว่า: ทางออกของปัญหาของคุณควรใช้ int ดีกว่าหรือไม่ หากคุณต้องการวน 100 ครั้งคุณควรนับด้วย int แทนที่จะใช้ double
BlueTrin

8
การใช้doubleไม่ใช่ทางออกที่ดีที่นี่ คุณใช้intในการนับและทำการคูณภายในเพื่อรับค่าทศนิยมของคุณ
ริชาร์ด

8

ลอยมีความแม่นยำน้อยกว่าคู่ แม้ว่าคุณจะรู้อยู่แล้วอ่านสิ่งที่เราควรรู้เกี่ยวกับเลขทศนิยมสำหรับการทำความเข้าใจที่ดีขึ้น


ตัวอย่างเช่นAVR doubles ทั้งหมดเป็นแบบลอย (สี่ไบต์)
Peter Mortensen

3

เมื่อใช้ตัวเลขจุดลอยตัวคุณไม่สามารถวางใจได้ว่าการทดสอบในเครื่องของคุณจะเหมือนกับการทดสอบที่ทำบนฝั่งเซิร์ฟเวอร์ สภาพแวดล้อมและคอมไพเลอร์อาจแตกต่างกันในระบบโลคัลของคุณและการทดสอบขั้นสุดท้ายจะเริ่มขึ้น ฉันเคยเห็นปัญหานี้มาหลายครั้งแล้วในการแข่งขัน TopCoder โดยเฉพาะถ้าคุณพยายามเปรียบเทียบตัวเลขทศนิยมสองตัว


3

การดำเนินการเปรียบเทียบในตัวแตกต่างกันเมื่อคุณเปรียบเทียบ 2 ตัวเลขกับจุดลอยตัวความแตกต่างในประเภทข้อมูล (เช่นลอยหรือสองครั้ง) อาจส่งผลให้ผลลัพธ์ที่แตกต่างกัน


1

หากใช้งานได้กับการประมวลผลแบบฝังตัวในที่สุดฮาร์ดแวร์พื้นฐาน (เช่น FPGA หรือบางรุ่นตัวประมวลผล / ไมโครคอนโทรลเลอร์) จะมีการใช้งานแบบลอยตัวได้อย่างเหมาะสมที่สุดในฮาร์ดแวร์ในขณะที่สองครั้งจะใช้รูทีนซอฟต์แวร์ ดังนั้นหากความแม่นยำของโฟลว์เพียงพอที่จะรองรับความต้องการโปรแกรมจะทำงานเร็วขึ้นด้วยการลอยตัวสองเท่า ตามที่ระบุไว้ในคำตอบอื่น ๆ ระวังข้อผิดพลาดการสะสม


-1

ซึ่งแตกต่างจากint(จำนวนทั้งหมด) เป็นfloatมีจุดทศนิยมและอื่น ๆ doubleกระป๋อง แต่ความแตกต่างระหว่างทั้งสองก็คือ a doubleมีรายละเอียดสองเท่าตามที่floatหมายถึงว่ามันสามารถมีจำนวนเป็นสองเท่าของตัวเลขหลังจุดทศนิยม


4
มันไม่ได้หมายความอย่างนั้น มันจริงหมายถึงสองเท่าหนึ่งตัวเลขทศนิยมและมันก็เพิ่มขึ้นกว่าเท่าตัว ความสัมพันธ์ระหว่างตัวเลขเศษส่วนและความแม่นยำไม่ใช่เชิงเส้น: ขึ้นอยู่กับค่า: เช่น 0.5 มีความแม่นยำ แต่ 0.33333333333333333333 ไม่ใช่
มาร์ควิสแห่ง Lorne
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.