2013-05-04 36 views
32

Tôi tò mò là tại sao literals phao phải được khai báo như vậy:Khai báo phao nổi, tại sao loại mặc định tăng gấp đôi?

float f = 0.1f; 

Thay vì

float f = 0.1; 

Tại sao mặc định gõ một đôi, tại sao có thể không phải là trình biên dịch suy luận rằng đó là một phao từ nhìn vào leftside của nhiệm vụ? Google chỉ đưa ra giải thích về những giá trị mặc định là gì, chứ không phải lý do tại sao chúng là như vậy.

+2

Có rất nhiều thông tin về tăng gấp đôi trên [trang này] (http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.2.3), nó là một đọc tuyệt vời cho hiểu biết nổi và IEEE 754 tốt hơn. – RyPope

+0

Tôi đã luôn luôn tự hỏi điều này thực sự. Nó xuất hiện vấn đề nằm với cách trình biên dịch phát hiện số thập phân để bắt đầu với. Nó xuất hiện để mặc định để tăng gấp đôi chữ ... Tôi đã luôn luôn có vấn đề cá nhân với việc sử dụng đôi anyway (tại sao bạn sẽ cần rất nhiều chính xác, ngay cả trong các lĩnh vực khoa học hoặc kỹ thuật?) – Singular1ty

+0

Tôi nghĩ rằng sẽ không có nhược điểm để trình biên dịch có thể nhận ra điều đó. Vì vậy, theo như tôi có thể thấy không có lý do thực sự đằng sau nó. Một googling nhanh chóng không chỉ cho tôi một lý do hợp lệ, hoặc, nhưng tôi có thể sai. – DPM

Trả lời

28

Tại sao loại mặc định tăng gấp đôi?

Đó là câu hỏi được các nhà thiết kế ngôn ngữ Java yêu cầu tốt nhất. Họ là những người duy nhất biết được số tiền thực sự lý do tại sao quyết định thiết kế ngôn ngữ đó được thực hiện. Nhưng tôi cho rằng lý do là một cái gì đó dọc theo các dòng sau:

Chúng cần phân biệt giữa hai loại chữ vì chúng thực sự có nghĩa là các giá trị khác nhau ... từ góc độ toán học.

Giả sử họ đã làm "phao" mặc định cho literals, hãy xem xét ví dụ này

// (Hypothetical "java" code ...) 
double d = 0.1; 
double d2 = 0.1d; 

Ở phía trên, các dd2 sẽ thực sự có giá trị khác nhau. Trong trường hợp đầu tiên, giá trị độ chính xác thấp float được chuyển đổi thành giá trị chính xác cao hơn double tại thời điểm gán. Nhưng bạn không thể phục hồi độ chính xác mà không có ở đó.

I posit là thiết kế ngôn ngữ nơi hai câu lệnh này đều hợp pháp và ý nghĩa khác nhau là ý tưởng BAD ... xem xét ý nghĩa thực tế của câu đầu tiên khác với ý nghĩa "tự nhiên".

Bằng cách làm việc đó theo cách mà họ đã làm được điều đó:

double d = 0.1f; 
double d2 = 0.1; 

đều quy phạm pháp luật, và có nghĩa là những thứ khác nhau một lần nữa. Nhưng trong tuyên bố đầu tiên, ý định của lập trình viên là rõ ràng, và câu lệnh thứ hai ý nghĩa "tự nhiên" là những gì người lập trình nhận được. Và trong trường hợp này:

float f = 0.1f; 
float f2 = 0.1; // compilation error! 

... trình biên dịch chọn không khớp.


Tôi đoán sử dụng phao nổi là ngoại lệ và không phải là quy tắc (sử dụng đôi thay vì) với phần cứng hiện đại như vậy tại một thời điểm nào nó sẽ làm cho tinh thần để giả định rằng người dùng có ý định 0.1f khi ông viết float f = 0.1;

Họ có thể đã làm điều đó rồi. Nhưng vấn đề là đưa ra một bộ quy tắc chuyển đổi loại hoạt động ... và đơn giản là bạn không cần một mức độ trong Java-ology để thực sự hiểu. Có 0.1 có nghĩa là những thứ khác nhau trong bối cảnh khác nhau sẽ gây nhầm lẫn.Và xem xét điều này:

void method(float f) { ... } 
void method(double d) { ... } 

// Which overload is called in the following? 
this.method(1.0); 

Thiết kế ngôn ngữ lập trình rất khó. Một sự thay đổi trong một khu vực có thể gây hậu quả cho người khác.


CẬP NHẬT để giải quyết một số điểm nêu ra bởi @supercat.

@supercat: Do quá tải trên, phương thức nào sẽ được gọi cho phương thức (16777217)? Đó có phải là lựa chọn tốt nhất không?

Tôi đã nhận xét sai ... lỗi biên dịch. Trong thực tế, câu trả lời là method(float).

Các JLS nói điều này:

15.12.2.5. Choosing the Most Specific Method

Nếu có nhiều hơn một phương pháp thành viên vừa tiếp cận và áp dụng đối với một phương pháp gọi , nó là cần thiết để lựa chọn một để cung cấp các mô tả cho thời gian chạy phương pháp công văn. Ngôn ngữ lập trình Java sử dụng quy tắc mà phương pháp cụ thể nhất được chọn.

...

[Các biểu tượng m1 và m2 biểu thị phương pháp được áp dụng.]

[Nếu] m2 là không chung chung, và m1 và m2 được áp dụng bởi nghiêm ngặt hoặc lỏng lẻo gọi, và nơi m1 có các kiểu tham số chính thức S1, ..., Sn và m2 có kiểu tham số chính thức T1, ..., Tn, loại Si là cụ thể hơn Ti cho đối số ei cho tất cả i (1 ≤ i ≤ n, n = k).

...

Các điều kiện trên là những trường hợp duy nhất mà theo đó một phương pháp có thể cụ thể hơn nữa.

Loại S cụ thể hơn loại T cho bất kỳ biểu thức nào nếu S <: T (§4.10).

Trong trường hợp này, chúng tôi đang so sánh method(float)method(double) cả hai đều có thể áp dụng cho cuộc gọi. Kể từ float <: double, nó là cụ thể hơn và do đó method(float) sẽ được chọn.

@supercat: Hành vi như vậy có thể gây ra sự cố nếu ví dụ:một biểu như int2 = (int) Math.Round(int1 * 3.5) hoặc long2 = Math.Round(long1 * 3.5) được thay thế bằng int1 = (int) Math.Round(int2 * 3) hoặc long2 = Math.Round(long1 * 3)

Sự thay đổi sẽ trông vô hại, nhưng hai biểu thức đầu tiên là sửa lên đến 613566756 hoặc 2573485501354568 và sau này hai thất bại trên 5592405 [là người cuối cùng hoàn toàn không có thật trên 715827882].

Nếu bạn đang nói về một người thực hiện thay đổi đó ... vâng.

Tuy nhiên, trình biên dịch sẽ không thực hiện thay đổi đó sau lưng của bạn. Ví dụ: int1 * 3.5 có loại double (số int được chuyển đổi thành double), vì vậy bạn sẽ kết thúc cuộc gọi Math.Round(double).

Như một quy tắc chung, số học Java sẽ chuyển đổi hoàn toàn từ các kiểu số "nhỏ hơn" sang "lớn hơn", nhưng không phải từ "lớn hơn" thành "nhỏ hơn".

Tuy nhiên, bạn vẫn cần phải cẩn thận từ (trong ví dụ làm tròn của bạn):

  • sản phẩm của một số nguyên và dấu chấm động có thể không biểu diễn với đủ độ chính xác bởi vì (nói) một float có ít bit chính xác hơn int.

  • truyền kết quả của Math.round(double) vào một loại số nguyên có thể dẫn đến chuyển đổi thành giá trị nhỏ nhất/lớn nhất của loại số nguyên.

Nhưng tất cả điều này minh họa rằng hỗ trợ số học trong ngôn ngữ lập trình là khó khăn, và không thể tránh khỏi cho một lập trình viên mới hoặc không cần thiết.

+0

Mặc dù sự khác biệt về độ chính xác rõ ràng nhưng tôi cho rằng điều đó có nghĩa là họ quyết định yêu cầu người dùng phải rõ ràng về lựa chọn nhưng tôi tự hỏi nó sẽ có ý nghĩa bao lâu. Tôi đoán bằng cách sử dụng phao là ngoại lệ và không phải là quy tắc (sử dụng đôi thay vì) với phần cứng hiện đại vì vậy tại một số điểm nó sẽ có ý nghĩa để giả định rằng người dùng dự định 0.1f khi ông viết float f = 0,1; – arynaq

+0

Với quá tải trên, phương thức nào sẽ được gọi cho 'phương thức (16777217)'? Đó có phải là lựa chọn tốt nhất không? – supercat

+0

@supercat - Tôi nghĩ rằng đó sẽ là lỗi biên dịch - một "ứng dụng phương pháp không rõ ràng". (Hãy thử và xem ...) –

1

Ha, đây chỉ là đỉnh của tảng băng trôi mà bạn tôi.

Các lập trình viên đến từ các ngôn ngữ khác chắc chắn không nhớ phải thêm một chút F đến một chữ so với:

SomeReallyLongClassName x = new SomeReallyLongClassName(); 

Khá dư thừa, phải không?

Đúng là bạn phải tự nói chuyện với các nhà phát triển Java cốt lõi để có thêm nền tảng. Nhưng như một lời giải thích cấp độ bề mặt thuần túy, một khái niệm quan trọng cần hiểu là biểu thức là. Trong Java (Tôi không có chuyên gia để lấy điều này với một hạt muối), tôi tin ở cấp độ trình biên dịch mã của bạn được phân tích về mặt biểu thức; vậy:

float f 

có một loại, và

0.1f 

cũng có một kiểu (float).

Nói chung, nếu bạn định gán một biểu thức cho một biểu thức khác, các loại phải đồng ý. Có một vài trường hợp rất cụ thể trong đó quy tắc này được giải phóng (ví dụ:, đấm bốc nguyên thủy như int trong một loại tham chiếu chẳng hạn như Integer); nhưng nói chung nó giữ.

Nó có vẻ ngớ ngẩn trong trường hợp này, nhưng đây là một trường hợp rất giống nhau mà nó dường như không quá ngớ ngẩn:

double getDouble() { 
    // some logic to return a double 
} 

void example() { 
    float f = getDouble(); 
} 

Bây giờ trong trường hợp này, chúng ta có thể thấy rằng nó có ý nghĩa đối với trình biên dịch để phát hiện một cái gì đó sai. Giá trị trả về bởi getDouble sẽ có 64 bit chính xác trong khi f chỉ có thể chứa 32 bit; vì vậy, không có diễn viên rõ ràng, có thể lập trình viên đã phạm sai lầm.

Hai trường hợp này rõ ràng khác với quan điểm của con người; nhưng quan điểm của tôi về các biểu thức là khi mã được chia thành các biểu thức đầu tiên và sau đó được phân tích, chúng giống nhau.

Tôi chắc chắn rằng các tác giả biên dịch có thể đã viết một số logic không thông minh để diễn giải lại các từ ngữ dựa trên các loại biểu thức mà chúng được gán cho; họ chỉ đơn giản là không. Có lẽ nó không được coi là giá trị nỗ lực so với các tính năng khác.

Đối với phối cảnh, nhiều ngôn ngữ có thể thực hiện suy luận kiểu; trong C#, ví dụ, bạn có thể làm điều này:

var x = new SomeReallyLongClassName(); 

Và loại x sẽ được suy ra bởi trình biên dịch dựa trên phân công đó.

Đối với các chữ, mặc dù C# giống với Java theo khía cạnh này.

0

float có độ chính xác rất ít và do đó, một câu hỏi thú vị hơn là; tại sao nó được hỗ trợ ở tất cả? Có những tình huống hiếm khi khi float có thể giống một số bộ nhớ (nếu bạn có hàng triệu bộ nhớ) hoặc bạn cần chúng để trao đổi dữ liệu với dữ liệu nào đó dự kiến ​​sẽ trôi nổi.

Nói chung, sử dụng double là lựa chọn tốt hơn, gần như nhanh đối với máy tính hiện đại và tiết kiệm bộ nhớ nhỏ hơn so với độ chính xác bổ sung mà nó mang lại.

Java không nhìn vào phía bên trái trong mọi tình huống để xem cách sử dụng giá trị. ví dụ. kiểu trả về của một phương thức không phải là một phần của chữ ký. Nó sẽ bỏ hoàn toàn trong một số trường hợp cho nhiệm vụ gán và toán tử, nhưng điều này thường là để giữ một số khả năng tương thích với C và thay vì ad hoc IMHO.

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