Chuyển đổi float thành double mà không làm mất độ chính xác


97

Tôi có một float nguyên thủy và tôi cần như một double nguyên thủy. Đơn giản chỉ cần ép phao lên gấp đôi mang lại cho tôi độ chính xác kỳ lạ. Ví dụ:

float temp = 14009.35F;
System.out.println(Float.toString(temp)); // Prints 14009.35
System.out.println(Double.toString((double)temp)); // Prints 14009.349609375

Tuy nhiên, nếu thay vì ép kiểu, tôi xuất float dưới dạng một chuỗi và phân tích cú pháp chuỗi dưới dạng kép, tôi sẽ nhận được những gì tôi muốn:

System.out.println(Double.toString(Double.parseDouble(Float.toString(temp))));
// Prints 14009.35

Có cách nào tốt hơn là vào Chuỗi và quay lại không?

Câu trả lời:


124

Không phải là bạn đang thực sự nhận được độ chính xác cao hơn - đó là phao không thể hiện chính xác con số mà bạn đang nhắm tới ban đầu. Double đại diện chính xác cho float gốc; toStringđang hiển thị dữ liệu "bổ sung" đã có.

Ví dụ: (và những con số này không đúng, tôi chỉ đang bịa ra), giả sử bạn có:

float f = 0.1F;
double d = f;

Khi đó giá trị của fcó thể chính xác là 0,100000234523. dsẽ có cùng một giá trị, nhưng khi bạn chuyển đổi nó thành một chuỗi, nó sẽ "tin tưởng" rằng nó chính xác với độ chính xác cao hơn, vì vậy sẽ không làm tròn sớm và bạn sẽ thấy các "chữ số bổ sung" đã ở đó, nhưng ẩn với bạn.

Khi bạn chuyển đổi thành chuỗi và quay lại, bạn sẽ nhận được một giá trị kép gần với giá trị chuỗi hơn giá trị float ban đầu - nhưng điều đó chỉ tốt nếu bạn thực sự tin rằng giá trị chuỗi là thứ bạn thực sự muốn.

Bạn có chắc chắn rằng float / double là loại thích hợp để sử dụng ở đây thay vì BigDecimal? Nếu bạn đang cố gắng sử dụng các số có giá trị thập phân chính xác (ví dụ: tiền), thì BigDecimalIMO là loại thích hợp hơn.


40

Tôi thấy chuyển đổi sang biểu diễn nhị phân dễ dàng hơn để nắm bắt vấn đề này.

float f = 0.27f;
double d2 = (double) f;
double d3 = 0.27d;

System.out.println(Integer.toBinaryString(Float.floatToRawIntBits(f)));
System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(d2)));
System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(d3)));

Bạn có thể thấy float được mở rộng thành double bằng cách thêm các số 0 vào cuối, nhưng biểu diễn kép của 0,27 là 'chính xác hơn', do đó có vấn đề.

   111110100010100011110101110001
11111111010001010001111010111000100000000000000000000000000000
11111111010001010001111010111000010100011110101110000101001000

25

Điều này là do hợp đồng Float.toString(float), trong đó nói một phần:

Có bao nhiêu chữ số phải được in cho phần phân số […]? Phải có ít nhất một chữ số để đại diện cho phần phân số và hơn thế nữa, nhưng chỉ cần nhiều chữ số hơn để phân biệt duy nhất giá trị đối số với các giá trị liền kề của kiểu float. Nghĩa là, giả sử rằng x là giá trị toán học chính xác được biểu diễn bằng biểu diễn thập phân được tạo ra bởi phương pháp này cho một đối số hữu hạn khác không f. Khi đó f phải là giá trị float gần nhất với x; hoặc, nếu hai giá trị float gần bằng nhau thì f phải là một trong số chúng và bit có nghĩa nhỏ nhất của f phải là 0.


13

Tôi đã gặp sự cố này hôm nay và không thể sử dụng refactor thành BigDecimal, vì dự án thực sự rất lớn. Tuy nhiên, tôi đã tìm thấy giải pháp bằng cách sử dụng

Float result = new Float(5623.23)
Double doubleResult = new FloatingDecimal(result.floatValue()).doubleValue()

Và điều này hoạt động.

Lưu ý rằng việc gọi result.doubleValue () trả về 5623.22998046875

Nhưng gọi doubleResult.doubleValue () trả về chính xác 5623,23

Nhưng tôi không hoàn toàn chắc chắn liệu nó có phải là giải pháp chính xác hay không.


1
Đã làm cho tôi. Cũng rất nhanh. Không chắc tại sao điều này không được đánh dấu là câu trả lời.
Sameer

Đây là phương pháp tôi sử dụng và bạn không cần phần Float mới ... Chỉ cần tạo FloatingDecimal với phần nguyên thủy. Nó sẽ tự động được đóng hộp ... Và cũng hoạt động nhanh ... Có một nhược điểm, FloatingDecimal là bất biến, vì vậy bạn cần tạo một mã cho mỗi float ... hãy tưởng tượng mã tính toán của O (1e10)! !! Tất nhiên BigDecimal có nhược điểm giống nhau ...
Mostafa Zeinali

6
Hạn chế của giải pháp này là sun.misc.FloatingDecimal là lớp bên trong của JVM và chữ ký của phương thức khởi tạo của nó đã được thay đổi trong Java 1.8. Không ai nên sử dụng các lớp nội bộ trong ứng dụng đời thực.
Igor Bljahhin

8

Tôi đã tìm thấy giải pháp sau:

public static Double getFloatAsDouble(Float fValue) {
    return Double.valueOf(fValue.toString());
}

Nếu bạn sử dụng floatdouble thay vì FloatDouble, hãy sử dụng như sau:

public static double getFloatAsDouble(float value) {
    return Double.valueOf(Float.valueOf(value).toString()).doubleValue();
}

7

Sử dụng một BigDecimalthay vì float/ double. Có rất nhiều số không thể được biểu diễn dưới dạng dấu phẩy động nhị phân (ví dụ 0.1:). Vì vậy, bạn phải luôn làm tròn kết quả đến một độ chính xác đã biết hoặc sử dụng BigDecimal.

Xem http://en.wikipedia.org/wiki/Floating_point để biết thêm thông tin.


Giả sử bạn có 10 triệu giá giao dịch float trong bộ nhớ cache của mình, bạn vẫn cân nhắc sử dụng BigDecimal và khởi tạo duy nhất, giả sử các đối tượng ~ 6 triệu (để giảm trọng lượng bay / không thay đổi) .. hay còn hơn thế nữa?
Sendi_t

1
@Sendi_t Vui lòng không sử dụng nhận xét để đặt các câu hỏi phức tạp :-)
Aaron Digulla

Cảm ơn bạn đã xem xét! .. chúng tôi sử dụng phao ngay bây giờ như đã được thống nhất một thập kỷ trước - Tôi đang cố gắng xem quan điểm của bạn về tình huống này khi theo dõi -. cảm ơn!
Sendi_t

@Sendi_t Hiểu lầm. Tôi không thể trả lời câu hỏi của bạn trong 512 ký tự. Vui lòng đặt một câu hỏi thích hợp và gửi cho tôi một liên kết.
Aaron Digulla

1
@GKFX Không có "một kích thước phù hợp với tất cả" khi nói đến xử lý số thập phân trên máy tính. Nhưng vì hầu hết mọi người không hiểu, tôi chỉ cho họ BigDecimalvì điều đó sẽ bắt được hầu hết các lỗi phổ biến. Nếu mọi thứ quá chậm, họ cần học hỏi thêm và tìm cách tối ưu hóa vấn đề của mình. Tối ưu hóa sớm là gốc rễ của mọi điều ác - DE Knuth.
Aaron Digulla

1

Về bản chất, Floats là không chính xác và luôn có những "vấn đề" làm tròn gọn gàng. Nếu độ chính xác là quan trọng thì bạn có thể xem xét việc cấu trúc lại ứng dụng của mình để sử dụng Decimal hoặc BigDecimal.

Có, float về mặt tính toán nhanh hơn số thập phân vì hỗ trợ bộ xử lý. Tuy nhiên, bạn muốn nhanh hay chính xác?


1
Số học thập phân cũng không chính xác. (ví dụ: 1/3 * 3 == 0,9999999999999999999999999999) Tất nhiên, tốt hơn để biểu diễn các đại lượng thập phân chính xác như tiền, nhưng đối với các phép đo vật lý thì nó không có lợi thế.
dan04

2
Nhưng 1 == 0,9999999999999999999999999999 :)
Emmanuel Bourg

0

Để biết thông tin, điều này có trong Mục 48 - Tránh float và double khi các giá trị chính xác được yêu cầu, của Hiệu quả Java phiên bản thứ 2 của Joshua Bloch. Cuốn sách này được đóng gói với những thứ tốt và chắc chắn đáng xem.


0

Điều này có hiệu quả không?

float flt = 145.664454;

Double dbl = 0.0;
dbl += flt;

0

Một giải pháp đơn giản hoạt động tốt là phân tích cú pháp kép từ biểu diễn chuỗi của float:

double val = Double.valueOf(String.valueOf(yourFloat));

Không phải là siêu hiệu quả, nhưng nó hoạt động!

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.