คือ 'float a = 3.0;' คำสั่งที่ถูกต้อง?


86

หากฉันมีคำประกาศดังต่อไปนี้:

float a = 3.0 ;

เป็นข้อผิดพลาดหรือไม่? ผมอ่านในหนังสือที่3.0เป็นคุณค่าและว่าผมจะต้องระบุว่าเป็นdouble float a = 3.0fมันเป็นอย่างนั้นเหรอ?


2
คอมไพเลอร์จะแปลงลิเทอรัลคู่3.0เป็นโฟลตให้คุณ float a = 3.0fผลลัพธ์ที่ได้คือแยกไม่ออกจาก
David Heffernan

6
@EdHeal: เป็นเช่นนั้น แต่ไม่เกี่ยวข้องกับคำถามนี้เป็นพิเศษซึ่งเกี่ยวกับกฎ C ++
Keith Thompson

20
อย่างน้อยที่สุดคุณต้องมีสัก;หลัง
Hot Licks

3
10 downvotes และมีความคิดเห็นไม่มากพอที่จะอธิบายได้ทำให้ท้อใจมาก นี่เป็นคำถามแรกของ OPs และหากผู้คนรู้สึกว่าสิ่งนี้คุ้มค่ากับการโหวต 10 ครั้งควรมีคำอธิบาย นี่เป็นคำถามที่ถูกต้องโดยมีนัยยะที่ไม่ชัดเจนและมีสิ่งที่น่าสนใจมากมายให้เรียนรู้จากคำตอบและความคิดเห็น
Shafik Yaghmour

3
@HotLicks มันไม่ได้เกี่ยวกับความรู้สึกแย่หรือดีแน่นอนว่ามันอาจดูไม่ยุติธรรม แต่นั่นคือชีวิตพวกเขาเป็นคะแนนยูนิคอร์น Dowvotes จะไม่ยกเลิกการโหวตที่คุณไม่ชอบเช่นเดียวกับการเพิ่มคะแนนไม่ใช่การยกเลิกการโหวตที่คุณไม่ชอบ หากผู้คนรู้สึกว่าคำถามสามารถปรับปรุงได้ผู้ถามครั้งแรกควรได้รับคำติชม ฉันไม่เห็นเหตุผลที่จะลงคะแนน แต่ฉันอยากรู้ว่าทำไมคนอื่นถึงทำทั้งๆที่พวกเขามีอิสระที่จะไม่พูดเช่นนั้น
Shafik Yaghmour

คำตอบ:


159

ไม่ใช่ข้อผิดพลาดในการประกาศfloat a = 3.0: ถ้าคุณทำคอมไพเลอร์จะแปลง 3.0 ลิเทอรัลคู่เป็นโฟลตให้คุณ


อย่างไรก็ตามคุณควรใช้สัญกรณ์ตัวอักษรลอยในสถานการณ์เฉพาะ

  1. ด้วยเหตุผลด้านประสิทธิภาพ:

    โดยเฉพาะให้พิจารณา:

    float foo(float x) { return x * 0.42; }
    

    ที่นี่คอมไพเลอร์จะปล่อยการแปลง (ซึ่งคุณจะจ่ายเมื่อรันไทม์) สำหรับแต่ละค่าที่ส่งคืน เพื่อหลีกเลี่ยงสิ่งนี้คุณควรประกาศ:

    float foo(float x) { return x * 0.42f; } // OK, no conversion required
    
  2. เพื่อหลีกเลี่ยงข้อบกพร่องเมื่อเปรียบเทียบผลลัพธ์:

    เช่นการเปรียบเทียบต่อไปนี้ล้มเหลว:

    float x = 4.2;
    if (x == 4.2)
       std::cout << "oops"; // Not executed!
    

    เราสามารถแก้ไขได้ด้วยสัญกรณ์ตัวอักษรลอย:

    if (x == 4.2f)
       std::cout << "ok !"; // Executed!
    

    (หมายเหตุ: แน่นอนว่านี่ไม่ใช่วิธีที่คุณควรเปรียบเทียบจำนวนทศนิยมหรือเลขคู่เพื่อความเท่าเทียมกันโดยทั่วไป )

  3. ในการเรียกใช้ฟังก์ชันโอเวอร์โหลดที่ถูกต้อง (ด้วยเหตุผลเดียวกัน):

    ตัวอย่าง:

    void foo(float f) { std::cout << "\nfloat"; }
    
    void foo(double d) { std::cout << "\ndouble"; }
    
    int main()
    {       
        foo(42.0);   // calls double overload
        foo(42.0f);  // calls float overload
        return 0;
    }
    
  4. ตามที่ระบุไว้ใน Cyberในบริบทการหักแบบประเภทจำเป็นที่จะต้องช่วยคอมไพเลอร์อนุมาน a float:

    ในกรณีauto:

    auto d = 3;      // int
    auto e = 3.0;    // double
    auto f = 3.0f;   // float
    

    และในทำนองเดียวกันในกรณีของการหักประเภทเทมเพลต:

    void foo(float f) { std::cout << "\nfloat"; }
    
    void foo(double d) { std::cout << "\ndouble"; }
    
    template<typename T>
    void bar(T t)
    {
          foo(t);
    }
    
    int main()
    {   
        bar(42.0);   // Deduce double
        bar(42.0f);  // Deduce float
    
        return 0;
    }
    

การสาธิตสด


2
ในจุดที่ 1 42เป็นจำนวนเต็มซึ่งได้รับการเลื่อนตำแหน่งโดยอัตโนมัติfloat(และจะเกิดขึ้นในเวลาคอมไพล์ในคอมไพเลอร์ที่เหมาะสม) ดังนั้นจึงไม่มีการลงโทษด้านประสิทธิภาพ 42.0คุณอาจหมายถึงสิ่งที่ต้องการ
Matteo Italia

@MatteoItalia ใช่ฉันหมายถึง 42.0 ofc (แก้ไขแล้วขอบคุณ)
quantdev

2
@ChristianHackl การแปลง4.2เป็น4.2fอาจมีผลข้างเคียงของการตั้งค่าFE_INEXACTแฟล็กขึ้นอยู่กับคอมไพเลอร์และระบบและบางโปรแกรม (ยอมรับน้อยมาก) จะสนใจว่าการดำเนินการทศนิยมใดที่แน่นอนและไม่เป็นเช่นนั้นและทดสอบแฟล็กนั้น . ซึ่งหมายความว่าการแปลงเวลาคอมไพล์อย่างง่ายที่ชัดเจนจะเปลี่ยนแปลงพฤติกรรมของโปรแกรม

6
float foo(float x) { return x*42.0; }สามารถรวบรวมเป็นการคูณที่มีความแม่นยำเดียวและรวบรวมโดย Clang ในครั้งสุดท้ายที่ฉันลอง อย่างไรก็ตามfloat foo(float x) { return x*0.1; }ไม่สามารถรวบรวมเป็นการคูณที่มีความแม่นยำเดียวได้ ก่อนหน้านี้แพตช์นี้อาจจะมองโลกในแง่ดีไปหน่อย แต่หลังจากแพตช์แล้วควรรวม Conversion-double_precision_op-conversion เป็น single_precision_op เมื่อผลลัพธ์เป็นแบบเดียวกันเสมอ article.gmane.org/gmane.comp.compilers.llvm.cvs/167800/match=
Pascal Cuoq

1
หากต้องการคำนวณค่าซึ่งเป็นหนึ่งในสิบของsomeFloatนิพจน์someFloat * 0.1จะได้ผลลัพธ์ที่แม่นยำกว่าsomeFloat * 0.1fในขณะที่ในหลาย ๆ กรณีมีราคาถูกกว่าการหารทศนิยม ตัวอย่างเช่น (float) (167772208.0f * 0.1) จะปัดเศษเป็น 16777220 ได้อย่างถูกต้องแทนที่จะเป็น 16777222 คอมไพเลอร์บางตัวอาจแทนที่การdoubleคูณสำหรับการหารทศนิยม แต่สำหรับผู้ที่ไม่มี (ปลอดภัยสำหรับหลายค่าแม้ว่าจะไม่ใช่ทุกค่าก็ตาม ) การคูณอาจเป็นการเพิ่มประสิทธิภาพที่มีประโยชน์ แต่ถ้าดำเนินการโดยใช้doubleการแลกเปลี่ยน
supercat

22

คอมไพเลอร์จะเปลี่ยนลิเทอรัลใด ๆ ต่อไปนี้ให้เป็นโฟลตเนื่องจากคุณประกาศตัวแปรเป็นโฟลต

float a = 3;     // converted to float
float b = 3.0;   // converted to float
float c = 3.0f;  // float

มันจะสำคัญถ้าคุณใช้auto(หรือวิธีการหักแบบอื่น ๆ ) ตัวอย่างเช่น:

auto d = 3;      // int
auto e = 3.0;    // double
auto f = 3.0f;   // float

5
ประเภทจะถูกอนุมานเมื่อใช้เทมเพลตดังนั้นจึงautoไม่ใช่กรณีเดียว
Shafik Yaghmour

14

ตัวอักษรทศนิยมที่ไม่มีคำต่อท้ายเป็นประเภทdoubleซึ่งครอบคลุมอยู่ในส่วนมาตรฐาน C ++ แบบร่าง2.14.4 ตัวอักษรลอย :

[... ] ประเภทของลิเทอรัลลอยเป็นสองเท่าเว้นแต่จะระบุไว้อย่างชัดเจนโดยคำต่อท้าย [... ]

ดังนั้นมันจึงเป็นข้อผิดพลาดในการกำหนดอักษรคู่ไปลอย :3.0

float a = 3.0

ไม่มันจะถูกแปลงซึ่งครอบคลุมอยู่ในหัวข้อ4.8 การแปลงทศนิยม :

ค่า prvalue ของประเภทจุดลอยตัวสามารถแปลงเป็นราคาล่วงหน้าของประเภทจุดลอยตัวอื่นได้ หากสามารถแสดงค่าต้นทางในประเภทปลายทางได้ทั้งหมดผลลัพธ์ของการแปลงคือการแสดงที่แน่นอน หากค่าต้นทางอยู่ระหว่างค่าปลายทางสองค่าที่อยู่ติดกันผลลัพธ์ของการแปลงจะเป็นตัวเลือกที่กำหนดโดยการนำไปใช้ของค่าเหล่านั้น มิฉะนั้นจะไม่มีการกำหนดพฤติกรรม

เราสามารถอ่านรายละเอียดเพิ่มเติมเกี่ยวกับผลกระทบของสิ่งนี้ได้ในGotW # 67: double or nothingซึ่งระบุว่า:

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

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

ดังนั้นจึงมีข้อแม้สำหรับกรณีทั่วไปที่คุณควรทราบ

จากมุมมองในทางปฏิบัติในกรณีนี้ผลลัพธ์มักจะเหมือนกันแม้ว่าในทางเทคนิคจะมีการแปลงเราสามารถเห็นสิ่งนี้ได้โดยลองใช้รหัสต่อไปนี้บนgodbolt :

#include <iostream>

float func1()
{
  return 3.0; // a double literal
}


float func2()
{
  return 3.0f ; // a float literal
}

int main()
{  
  std::cout << func1() << ":" << func2() << std::endl ;
  return 0;
}

และเราจะเห็นว่าผลลัพธ์สำหรับfunc1และfunc2เหมือนกันโดยใช้ทั้งสองclangและgcc:

func1():
    movss   xmm0, DWORD PTR .LC0[rip]
    ret
func2():
    movss   xmm0, DWORD PTR .LC0[rip]
    ret

ดังที่Pascal ชี้ให้เห็นในความคิดเห็นนี้คุณจะไม่สามารถนับสิ่งนี้ได้เสมอไป การใช้0.1และ0.1fตามลำดับทำให้แอสเซมบลีที่สร้างขึ้นแตกต่างกันเนื่องจากตอนนี้ต้องทำการแปลงอย่างชัดเจน รหัสต่อไปนี้:

float func1(float x )
{
  return x*0.1; // a double literal
}

float func2(float x)
{
  return x*0.1f ; // a float literal
}

ผลลัพธ์ในการประกอบต่อไปนี้:

func1(float):  
    cvtss2sd    %xmm0, %xmm0    # x, D.31147    
    mulsd   .LC0(%rip), %xmm0   #, D.31147
    cvtsd2ss    %xmm0, %xmm0    # D.31147, D.31148
    ret
func2(float):
    mulss   .LC2(%rip), %xmm0   #, D.31155
    ret

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

บันทึก

ดังที่ supercat ชี้ให้เห็นการคูณด้วยเช่น0.1และ0.1fไม่เทียบเท่า ฉันแค่จะอ้างความคิดเห็นเพราะมันยอดเยี่ยมและบทสรุปอาจไม่ยุติธรรม:

ตัวอย่างเช่นถ้า f เท่ากับ 100000224 (ซึ่งแทนค่าได้ว่าเป็นค่าลอย) การคูณด้วยหนึ่งในสิบควรให้ผลลัพธ์ที่ปัดเศษลงเป็น 10000022 แต่การคูณด้วย 0.1f จะให้ผลลัพธ์ที่ปัดเศษได้ถึง 10000023 อย่างผิดพลาด ถ้าตั้งใจจะหารด้วยสิบการคูณด้วยค่าคงที่คู่ 0.1 น่าจะเร็วกว่าการหารด้วย 10f และแม่นยำกว่าการคูณด้วย 0.1f

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


1
มันอาจจะเป็นที่น่าสังเกตว่าการแสดงออกf = f * 0.1;และทำสิ่งที่แตกต่างกันf = f * 0.1f; ตัวอย่างเช่นถ้าfเท่ากับ 100000224 (ซึ่งแทนค่าได้อย่างแน่นอนว่า a float) การคูณด้วยหนึ่งในสิบควรให้ผลลัพธ์ที่ปัดเศษลงเป็น 10000022 แต่การคูณด้วย 0.1f จะให้ผลลัพธ์ที่ปัดเศษได้สูงถึง 10000023 อย่างผิดพลาดหาก ความตั้งใจที่จะหารด้วยสิบคูณโดยdoubleคงที่ 0.1 มีแนวโน้มที่จะเร็วกว่าการหารด้วยและแม่นยำมากขึ้นกว่าการคูณโดย10f 0.1f
supercat

@supercat ขอบคุณสำหรับตัวอย่างที่ดีที่ฉันยกมาให้คุณโดยตรงโปรดอย่าลังเลที่จะแก้ไขตามที่เห็นสมควร
Shafik Yaghmour

4

ไม่ใช่ข้อผิดพลาดในแง่ที่คอมไพเลอร์จะปฏิเสธ แต่เป็นข้อผิดพลาดในแง่ที่อาจไม่ใช่สิ่งที่คุณต้องการ

ในฐานะที่เป็นหนังสือของคุณได้อย่างถูกต้องกล่าวคือค่าของชนิด3.0 doubleมีการแปลงโดยนัยจากdoubleเป็นfloatดังนั้นfloat a = 3.0;คำจำกัดความที่ถูกต้องของตัวแปร

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

3.0f หลีกเลี่ยงปัญหานั้น: แม้ว่าในทางเทคนิคแล้วคอมไพเลอร์ยังคงได้รับอนุญาตให้คำนวณค่าคงที่ในขณะรัน (เป็นเสมอ) ที่นี่ไม่มีเหตุผลอย่างแน่นอนว่าทำไมคอมไพเลอร์ใด ๆ จึงอาจทำเช่นนั้นได้


อันที่จริงในกรณีของคอมไพเลอร์ข้ามมันค่อนข้างไม่ถูกต้องสำหรับการแปลงที่จะดำเนินการในเวลาคอมไพล์เพราะมันจะเกิดขึ้นบนแพลตฟอร์มที่ไม่ถูกต้อง
user207421

2

แม้ว่าจะไม่ใช่ข้อผิดพลาด แต่ก็ค่อนข้างเลอะเทอะเล็กน้อย คุณรู้ว่าคุณต้องการลอยดังนั้นเริ่มต้นด้วยการลอย
โปรแกรมเมอร์คนอื่นอาจเข้ามาและไม่แน่ใจว่าส่วนใดของการประกาศที่ถูกต้องประเภทหรือตัวเริ่มต้น ทำไมไม่มีทั้งสองอย่างถูกต้อง?
คำตอบลอย = 42.0f;


0

เมื่อคุณกำหนดตัวแปรตัวแปรจะเริ่มต้นด้วย initializer ที่ให้มา ซึ่งอาจต้องมีการแปลงค่าของ initializer เป็นประเภทของตัวแปรที่กำลังเริ่มต้น นั่นคือสิ่งที่เกิดขึ้นเมื่อคุณพูดว่าfloat a = 3.0;: ค่าของการเริ่มต้นที่จะถูกแปลงและผลของการแปลงจะกลายเป็นค่าเริ่มต้นของfloata

นั่นคือโดยทั่วไปดี แต่มันไม่ได้เจ็บที่จะเขียนเพื่อแสดงว่าคุณกำลังตระหนักถึงสิ่งที่คุณกำลังทำและโดยเฉพาะอย่างยิ่งถ้าคุณต้องการที่จะเขียน3.0fauto a = 3.0f


0

หากคุณลองทำสิ่งต่อไปนี้:

std::cout << sizeof(3.2f) <<":" << sizeof(3.2) << std::endl;

คุณจะได้ผลลัพธ์เป็น:

4:8

ที่แสดงให้เห็นว่าขนาด 3.2f ถูกใช้เป็น 4 ไบต์บนเครื่อง 32 บิตเมื่อ 3.2 ถูกตีความว่าเป็นค่าสองเท่าโดยใช้ 8 ไบต์บนเครื่อง 32 บิต สิ่งนี้ควรให้คำตอบที่คุณกำลังมองหา


นั่นแสดงให้เห็นว่าdoubleและfloatแตกต่างกันก็ไม่ได้ตอบว่าคุณสามารถเริ่มต้น a floatจากตัวอักษรคู่ได้หรือไม่
Jonathan Wakely

แน่นอนคุณสามารถเริ่มต้นการลอยตัวจากค่าสองเท่าภายใต้การตัดทอนข้อมูลหากมี
ดร. Debasish Jana

4
ใช่ฉันรู้ แต่นั่นเป็นคำถามของ OP ดังนั้นคำตอบของคุณจึงไม่สามารถตอบได้จริงแม้ว่าจะอ้างว่าให้คำตอบก็ตาม!
Jonathan Wakely

0

คอมไพเลอร์จะอนุมานประเภทที่เหมาะสมที่สุดออกจากตัวอักษรหรือโดยไม่คำนึงถึงสิ่งที่คิดว่าเหมาะสมที่สุด นั่นค่อนข้างจะสูญเสียประสิทธิภาพมากกว่าความแม่นยำกล่าวคือใช้ double แทนการลอย หากมีข้อสงสัยให้ใช้ brace-intializers เพื่อทำให้ชัดเจน:

auto d = double{3}; // make a double
auto f = float{3}; // make a float
auto i = int{3}; // make a int

เรื่องราวจะน่าสนใจยิ่งขึ้นหากคุณเริ่มต้นจากตัวแปรอื่นที่ใช้กฎการแปลงประเภท: แม้ว่าจะถูกกฎหมายที่จะสร้างรูปแบบสองเท่าเป็นตัวอักษร แต่ก็ไม่สามารถสร้างโครงสร้างจาก int ได้โดยไม่ต้อง จำกัด ให้แคบลง:

auto xxx = double{i} // warning ! narrowing conversion of 'i' from 'int' to 'double' 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.