2014-07-05 11 views
6

Khi chuyển đổi độ chính xác "cao" thành nhị phân, tôi mất độ chính xác với Convert.ToDecimal hoặc truyền sang (Thập phân) do Làm tròn.Double to Decimal mà không làm tròn sau 15 chữ số

Ví dụ:

double d = -0.99999999999999956d; 
decimal result = Convert.ToDecimal(d); // Result = -1 
decimal result = (Decimal)(d); // Result = -1 

Giá trị thập phân được trả về bởi Convert.ToDecimal (double) chứa tối đa là 15 chữ số có nghĩa. Nếu tham số giá trị chứa hơn 15 chữ số có nghĩa, nó được làm tròn bằng cách làm tròn đến gần nhất.

Vì vậy, tôi để giữ cho độ chính xác của tôi, tôi phải chuyển đổi gấp đôi của tôi vào một String và sau đó gọi Convert.ToDecimal (String):

decimal result = System.Convert.ToDecimal(d.ToString("G20")); // Result = -0.99999999999999956d 

Phương pháp này đang làm việc nhưng tôi muốn tránh sử dụng một biến String để chuyển đổi một đôi để thập phân mà không làm tròn sau 15 chữ số?

+0

Bạn có biết trước phạm vi cho 'd' không? Tôi có thể cung cấp một số giải pháp nhẹ trong mã giả (tôi không đủ quen thuộc với C# để viết chúng trong C#) nếu bạn biết rằng 'd' nằm trong phạm vi như [-2..2] –

+0

d sẽ luôn ở giữa [- 1,1] –

+0

Tôi giả định đây là một 'double' đến từ một nơi khác mà bạn không thể thay đổi? Nếu nó được khai báo như một số thập phân từ get-go ('decimal d = -0.99999999999999956m;') thì nó vẫn duy trì độ chính xác đó. –

Trả lời

4

Một giải pháp có thể là phân tách d với tổng số tiền chính xác gấp đôi, số cuối cùng nhỏ và chứa tất cả các chữ số quan trọng mà bạn mong muốn khi được chuyển đổi sang thập phân và số đầu tiên (n-1) trong đó chuyển đổi chính xác thành thập phân.

Đối với nguồn đôi d giữa -1.0 và 1.0:

decimal t = 0M; 
    bool b = d < 0; 
    if (b) d = -d; 
    if (d >= 0.5) { d -= 0.5; t = 0.5M; } 
    if (d >= 0.25) { d -= 0.25; t += 0.25M; } 
    if (d >= 0.125) { d -= 0.125; t += 0.125M; } 
    if (d >= 0.0625) { d -= 0.0625; t += 0.0625M; } 
    t += Convert.ToDecimal(d); 
    if (b) t = -t; 

Test it on ideone.com.

Lưu ý rằng các hoạt động d -= là chính xác, thậm chí nếu C# tính hoạt động nổi-điểm nhị phân ở một độ chính xác cao hơn double (mà nó cho phép chính nó để làm).

Giá trị này rẻ hơn chuyển đổi từ double thành chuỗi và cung cấp thêm một vài chữ số chính xác trong kết quả (bốn bit chính xác cho bốn số liệu trên nếu có).

Ghi chú: nếu C# không cho phép bản thân để làm phép tính dấu chấm động tại một độ chính xác cao hơn, một thủ thuật tốt có thể đã được sử dụng Dekker tách tách d vào hai giá trị d1d2 rằng sẽ chuyển đổi mỗi chính xác để thập phân. Than ôi, Dekker tách chỉ hoạt động với một cách giải thích nghiêm ngặt về phép nhân và phép cộng IEEE 754.


ý tưởng khác là sử dụng phiên bản C# 's của frexp để có được những significand s và mũ e của d, và để tính toán (Decimal)((long) (s * 4503599627370496.0d)) * <however one computes 2^e in Decimal>.

+0

Tôi đã thử mẫu mã của bạn nhưng tôi nhận được -1. Bạn có thể sao chép nó trên mặt của bạn? –

+0

@StevenMuhr Xin lỗi, tôi không có C# hoặc thậm chí bất kỳ nền tảng nào mà tôi có thể chạy mã .NET, nhưng tôi sẽ cố gắng chơi với ideone.com và quay lại với thứ gì đó hoạt động. –

+0

@StevenMuhr Phương thức 1, kết quả: 0,9999999999999996. Bạn cần thêm xử lý cho dấu 'd'. http://ideone.com/MevINX –

1

Có hai cách tiếp cận, một trong số đó sẽ hoạt động với các giá trị dưới 2^63 và một phương pháp khác sẽ hoạt động với các giá trị lớn hơn 2^53.

Chia các giá trị nhỏ hơn thành toàn bộ số và các phần phân đoạn. Toàn bộ số có thể được đúc chính xác đến long và sau đó Decimal [lưu ý rằng một dàn diễn viên trực tiếp tới Decimal có thể không chính xác!] Phần phân số có thể được nhân với 9007199254740992.0 (2^53), được chuyển đổi thành long và sau đó Decimal, và sau đó chia cho 9007199254740992.0m. Việc thêm kết quả của phân chia đó vào phần toàn bộ số sẽ mang lại giá trị Decimal trong vòng một chữ số có nghĩa là chính xác [nó có thể không được làm tròn chính xác, nhưng vẫn tốt hơn nhiều so với chuyển đổi tích hợp!]

Đối với các giá trị lớn hơn, nhân với (1.0/281474976710656.0) (2^-48), lấy toàn bộ số của kết quả đó, nhân nó lại với 281474976710656.0 và trừ nó khỏi kết quả ban đầu. Chuyển đổi các kết quả toàn bộ số từ phép chia và phép trừ thành Decimal (chúng phải chuyển đổi chính xác), nhân số trước bằng 281474976710656m và thêm số thứ hai.

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