Là 'float a = 3.0;' một tuyên bố đúng?


86

Nếu tôi có khai báo sau:

float a = 3.0 ;

đó là một lỗi? Tôi đọc trong một cuốn sách đó 3.0là một doublegiá trị và tôi phải xác định nó là float a = 3.0f. Có phải vậy không?


2
Trình biên dịch sẽ chuyển đổi chữ kép 3.0thành một float cho bạn. Kết quả cuối cùng là không thể phân biệt được float a = 3.0f.
David Heffernan

6
@EdHeal: Đúng vậy, nhưng nó không liên quan đặc biệt đến câu hỏi này, đó là về các quy tắc C ++.
Keith Thompson

20
Vâng, ít nhất bạn cần một ;sau.
Hot Licks vào

3
10 phiếu phản đối và không có nhiều ý kiến ​​để giải thích chúng, rất không khuyến khích. Đây là câu hỏi đầu tiên của OP và nếu mọi người cảm thấy điều này xứng đáng với 10 phiếu phản đối thì nên có một số giải thích. Đây là một câu hỏi hợp lệ với hàm ý không rõ ràng và nhiều điều thú vị để học hỏi từ các câu trả lời và nhận xét.
Shafik Yaghmour

3
@HotLicks nó không phải là để cảm thấy xấu hay tốt, chắc chắn rằng nó có vẻ không công bằng nhưng đó là cuộc sống, họ là những điểm kỳ lân. Những phiếu bầu giảm giá chắc chắn không phải để hủy bỏ những phiếu bầu bạn không thích cũng giống như những phiếu bầu ủng hộ không phải để hủy bỏ những phiếu bầu bạn không thích. Nếu mọi người cảm thấy câu hỏi có thể được cải thiện, chắc chắn người hỏi lần đầu tiên sẽ nhận được một số phản hồi. Tôi không thấy có lý do gì để phản đối nhưng tôi muốn biết tại sao những người khác lại làm như vậy mặc dù họ có quyền không nói như vậy.
Shafik Yaghmour

Câu trả lời:


159

Nó không phải là một lỗi để khai báo float a = 3.0: nếu bạn làm vậy, trình biên dịch sẽ chuyển đổi chữ kép 3.0 thành một float cho bạn.


Tuy nhiên, bạn nên sử dụng ký hiệu float trong các trường hợp cụ thể.

  1. Vì lý do hiệu suất:

    Cụ thể, hãy xem xét:

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

    Ở đây trình biên dịch sẽ phát ra một chuyển đổi (mà bạn sẽ trả trong thời gian chạy) cho mỗi giá trị trả về. Để tránh nó, bạn nên khai báo:

    float foo(float x) { return x * 0.42f; } // OK, no conversion required
    
  2. Để tránh lỗi khi so sánh kết quả:

    ví dụ: so sánh sau không thành công:

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

    Chúng tôi có thể sửa nó bằng ký hiệu float:

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

    (Lưu ý: tất nhiên, đây không phải là cách bạn nên so sánh các số thực hoặc số kép cho bằng nhau nói chung )

  3. Để gọi đúng hàm quá tải (vì lý do tương tự):

    Thí dụ:

    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. Như đã lưu ý bởi Cyber , trong ngữ cảnh suy luận kiểu, cần phải giúp trình biên dịch suy ra float:

    Trong trường hợp auto:

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

    Và tương tự, trong trường hợp loại trừ kiểu mẫu:

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

Bản thử trực tiếp


2
Ở điểm 1 42là một số nguyên, được tự động thăng cấp float(và nó sẽ xảy ra tại thời điểm biên dịch trong bất kỳ trình biên dịch tốt nào), vì vậy không có hình phạt về hiệu suất. Có lẽ bạn muốn nói một cái gì đó như thế 42.0.
Matteo Italia

@MatteoItalia, vâng tôi có nghĩa là 42,0 OFC (chỉnh sửa, nhờ)
quantdev

2
@ChristianHackl Việc chuyển đổi 4.2thành 4.2fcó thể có tác dụng phụ là đặt FE_INEXACTcờ, tùy thuộc vào trình biên dịch và hệ thống, và một số (phải thừa nhận là một số chương trình) quan tâm đến hoạt động dấu phẩy động nào là chính xác và hoạt động nào không, và kiểm tra cờ đó . Điều này có nghĩa là chuyển đổi thời gian biên dịch hiển nhiên đơn giản sẽ thay đổi hành vi của chương trình.

6
float foo(float x) { return x*42.0; }có thể được biên dịch thành một phép nhân chính xác duy nhất và đã được biên dịch bởi Clang vào lần cuối tôi thử. Tuy nhiên, float foo(float x) { return x*0.1; }không thể được biên dịch thành một phép nhân chính xác duy nhất. Có thể hơi lạc quan trước bản vá này, nhưng sau bản vá, nó chỉ nên kết hợp convert-double_pre khít_op-convert thành single_pre khít_op khi kết quả luôn giống nhau. article.gmane.org/gmane.comp.compilers.llvm.cvs/167800/match=
Pascal Cuoq

1
Nếu một người muốn tính giá trị bằng một phần mười someFloat, thì biểu thức someFloat * 0.1sẽ cho kết quả chính xác hơn someFloat * 0.1f, trong khi trong nhiều trường hợp, nó rẻ hơn phép chia dấu phẩy động. Ví dụ: (float) (167772208.0f * 0.1) sẽ làm tròn chính xác thành 16777220 thay vì 16777222. Một số trình biên dịch có thể thay thế một doublephép nhân cho một phép chia dấu phẩy động, nhưng đối với những người đó thì không (nó an toàn cho nhiều người mặc dù không phải tất cả các giá trị ) phép nhân có thể là một cách tối ưu hóa hữu ích, nhưng chỉ khi được thực hiện với một phép doubleđối ứng.
supercat

22

Trình biên dịch sẽ biến bất kỳ ký tự nào sau đây thành float, bởi vì bạn đã khai báo biến là float.

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

Điều quan trọng là nếu bạn sử dụng auto(hoặc các phương pháp khấu trừ kiểu khác), ví dụ:

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

5
Các kiểu cũng được suy ra khi sử dụng các mẫu, vì vậy autokhông phải là trường hợp duy nhất.
Shafik Yaghmour

14

Các ký tự dấu chấm động không có hậu tố thuộc loại double , điều này được đề cập trong phần dự thảo tiêu chuẩn C ++ 2.14.4 Các ký tự nổi :

[...] Kiểu của một ký tự động là kép trừ khi được chỉ định rõ ràng bởi một hậu tố. [...]

vậy có phải lỗi khi gán 3.0một ký tự kép cho float không ?:

float a = 3.0

Không, không phải vậy, nó sẽ được chuyển đổi, được đề cập trong phần 4.8 Chuyển đổi dấu chấm động :

Giá trị prvalue của kiểu dấu phẩy động có thể được chuyển đổi thành giá trị prvalue của kiểu dấu phẩy động khác. Nếu giá trị nguồn có thể được biểu thị chính xác trong kiểu đích, thì kết quả của chuyển đổi là biểu diễn chính xác đó. Nếu giá trị nguồn nằm giữa hai giá trị đích liền kề, thì kết quả của chuyển đổi là lựa chọn do triển khai xác định của một trong hai giá trị đó. Nếu không, hành vi là không xác định.

Chúng ta có thể đọc thêm chi tiết về hàm ý của điều này trong GotW # 67: double or nothing nói:

Điều này có nghĩa là hằng số kép có thể được chuyển đổi hoàn toàn (tức là âm thầm) thành hằng số float, ngay cả khi làm như vậy làm mất độ chính xác (tức là dữ liệu). Điều này được phép duy trì vì lý do khả năng tương thích và khả năng sử dụng của C, nhưng điều đáng lưu ý khi bạn thực hiện công việc dấu phẩy động.

Một trình biên dịch chất lượng sẽ cảnh báo bạn nếu bạn cố gắng thực hiện hành vi không xác định, cụ thể là đặt một số lượng gấp đôi vào một float nhỏ hơn giá trị tối thiểu hoặc lớn hơn giá trị tối đa mà float có thể đại diện. Một trình biên dịch thực sự tốt sẽ cung cấp một cảnh báo tùy chọn nếu bạn cố gắng làm điều gì đó có thể được xác định nhưng có thể mất thông tin, cụ thể là đặt một số lượng gấp đôi vào một float nằm giữa các giá trị tối thiểu và tối đa có thể biểu diễn bằng một float, nhưng không thể được biểu diễn chính xác như một phao.

Vì vậy, có những lưu ý đối với trường hợp chung mà bạn cần lưu ý.

Từ góc độ thực tế, trong trường hợp này, kết quả rất có thể sẽ giống nhau mặc dù về mặt kỹ thuật có sự chuyển đổi, chúng ta có thể thấy điều này bằng cách thử đoạn mã sau trên chốt thần :

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

và chúng tôi thấy rằng kết quả cho func1func2giống hệt nhau, sử dụng cả hai clanggcc:

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

Như Pascal đã chỉ ra trong nhận xét này, bạn không phải lúc nào cũng có thể tin tưởng vào điều này. Việc sử dụng 0.10.1ftương ứng làm cho hợp ngữ được tạo ra khác nhau vì việc chuyển đổi bây giờ phải được thực hiện rõ ràng. Đoạn mã sau:

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

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

kết quả trong hội đồng sau:

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

Bất kể bạn có thể xác định xem chuyển đổi có tác động đến hiệu suất hay không, việc sử dụng đúng loại tài liệu tốt hơn ý định của bạn. Ví dụ: sử dụng một chuyển đổi rõ ràng static_castcũng giúp làm rõ chuyển đổi là nhằm mục đích thay vì ngẫu nhiên, điều này có thể biểu thị một lỗi hoặc lỗi tiềm ẩn.

Ghi chú

Như supercat đã chỉ ra, phép nhân với ví dụ 0.10.1fkhông tương đương. Tôi chỉ định trích dẫn nhận xét vì nó quá xuất sắc và một bản tóm tắt có lẽ sẽ không công bằng:

Ví dụ: nếu f bằng 100000224 (có thể biểu diễn chính xác dưới dạng số thực), nhân nó với một phần mười sẽ mang lại kết quả làm tròn xuống 10000022, nhưng nhân với 0,1f thay vào đó sẽ mang lại kết quả làm tròn sai lên đến 10000023 Nếu mục đích là chia cho mười, thì phép nhân với hằng số kép 0,1 có thể sẽ nhanh hơn phép chia cho 10f và chính xác hơn phép nhân với 0,1f.

Quan điểm ban đầu của tôi là chứng minh một ví dụ sai được đưa ra trong một câu hỏi khác nhưng điều này chứng minh rõ ràng các vấn đề tế nhị có thể tồn tại trong các ví dụ đồ chơi.


1
Có thể đáng chú ý là các biểu thức f = f * 0.1;f = f * 0.1f; làm những điều khác nhau . Ví dụ: nếu fbằng 100000224 (có thể biểu diễn chính xác là a float), nhân nó với một phần mười sẽ mang lại kết quả làm tròn xuống 10000022, nhưng nhân với 0,1f thay vào đó sẽ mang lại kết quả làm tròn sai lên đến 10000023. Nếu mục đích là chia cho mười, phép nhân với doublehằng số 0,1 có thể sẽ nhanh hơn phép chia cho 10fvà chính xác hơn phép nhân với 0.1f.
supercat

@supercat cảm ơn bạn vì ví dụ đẹp, tôi đã trích dẫn trực tiếp cho bạn, vui lòng chỉnh sửa khi bạn thấy phù hợp.
Shafik Yaghmour

4

Nó không phải là một lỗi mà trình biên dịch sẽ từ chối nó, nhưng nó là một lỗi theo nghĩa nó có thể không như những gì bạn muốn.

Như sách của bạn tuyên bố chính xác, 3.0là một giá trị của loại double. Có một chuyển đổi ngầm định từ doublethành float, vì vậyfloat a = 3.0; một định nghĩa hợp lệ về một biến.

Tuy nhiên, ít nhất về mặt khái niệm, điều này thực hiện một chuyển đổi không cần thiết. Tùy thuộc vào trình biên dịch, việc chuyển đổi có thể được thực hiện tại thời điểm biên dịch hoặc có thể được lưu trong thời gian chạy. Một lý do hợp lệ để tiết kiệm thời gian chạy là chuyển đổi dấu phẩy động rất khó và có thể có tác dụng phụ không mong muốn nếu giá trị không thể được biểu diễn chính xác và không phải lúc nào cũng dễ dàng xác minh xem giá trị có thể được biểu diễn chính xác hay không.

3.0f tránh được vấn đề đó: mặc dù về mặt kỹ thuật, trình biên dịch vẫn được phép tính hằng số tại thời điểm chạy (nó luôn như vậy), ở đây, hoàn toàn không có lý do gì khiến bất kỳ trình biên dịch nào có thể làm được điều đó.


Thật vậy, trong trường hợp trình biên dịch chéo, việc chuyển đổi được thực hiện tại thời điểm biên dịch sẽ không chính xác vì nó sẽ xảy ra trên nền tảng sai.
Marquis of Lorne

2

Mặc dù không phải là một lỗi, nhưng nó hơi cẩu thả. Bạn biết bạn muốn có một phao, vì vậy hãy khởi tạo nó bằng một phao.
Một lập trình viên khác có thể đến cùng và không chắc phần khai báo nào là đúng, kiểu hay bộ khởi tạo. Tại sao cả hai đều không đúng?
float Đáp án = 42.0f;


0

Khi bạn xác định một biến, nó sẽ được khởi tạo bằng bộ khởi tạo được cung cấp. Điều này có thể yêu cầu chuyển đổi giá trị của trình khởi tạo thành loại biến đang được khởi tạo. Đó là những gì đang xảy ra khi bạn nói float a = 3.0;: Giá trị của trình khởi tạo được chuyển đổi thành floatvà kết quả của chuyển đổi trở thành giá trị ban đầu củaa .

Nói chung là ổn, nhưng không có hại gì khi viết 3.0fđể thể hiện rằng bạn nhận thức được những gì bạn đang làm, và đặc biệt là nếu bạn muốn viết auto a = 3.0f.


0

Nếu bạn thử những cách sau:

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

bạn sẽ nhận được đầu ra là:

4:8

điều đó cho thấy, kích thước 3.2f được lấy bằng 4 byte trên máy 32 bit trong khi 3.2 được hiểu là giá trị kép lấy 8 byte trên máy 32 bit. Điều này sẽ cung cấp câu trả lời mà bạn đang tìm kiếm.


Đó là chương trình đó doublefloatlà khác nhau, nó không trả lời cho dù bạn có thể khởi tạo một floattừ một đôi đen
Jonathan Wakely

tất nhiên bạn có thể khởi tạo một phao từ một đối tượng giá trị gấp đôi số liệu cắt ngắn, nếu áp dụng
TS Debasish Jana

4
Vâng, tôi biết, nhưng đó là câu hỏi của OP, vì vậy câu trả lời của bạn không thực sự trả lời được, mặc dù đã tuyên bố cung cấp câu trả lời!
Jonathan Wakely

0

Trình biên dịch suy ra loại phù hợp nhất từ ​​các nghĩa đen hoặc theo nghĩa mà nó cho là phù hợp nhất. Điều đó khá mất hiệu quả so với độ chính xác, tức là sử dụng double thay vì float. Nếu nghi ngờ, hãy sử dụng dấu ngoặc nhọn để làm rõ ràng:

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

Câu chuyện trở nên thú vị hơn nếu bạn khởi tạo từ một biến khác có áp dụng quy tắc chuyển đổi kiểu: Mặc dù hợp pháp để hiểu dạng kép là một chữ, nhưng nó không thể được cấu trúc từ một int mà không thể thu hẹp:

auto xxx = double{i} // warning ! narrowing conversion of 'i' from 'int' to 'double' 
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.