Khi nào tôi nên sử dụng gấp đôi thay vì thập phân?


265

Tôi có thể đặt tên cho ba lợi thế để sử dụng double(hoặc float) thay vì decimal:

  1. Sử dụng ít bộ nhớ.
  2. Nhanh hơn vì các phép toán dấu phẩy động được hỗ trợ bởi các bộ xử lý.
  3. Có thể đại diện cho một phạm vi lớn hơn của số.

Nhưng những lợi thế này dường như chỉ áp dụng cho việc tính toán các hoạt động chuyên sâu, chẳng hạn như những lợi ích được tìm thấy trong phần mềm mô hình hóa. Tất nhiên, không nên sử dụng gấp đôi khi cần độ chính xác, chẳng hạn như tính toán tài chính. Vậy có lý do thực tế nào để chọn double(hoặc float) thay vì decimaltrong các ứng dụng "thông thường" không?

Chỉnh sửa để thêm: Cảm ơn tất cả các phản hồi tuyệt vời, tôi đã học được từ họ.

Một câu hỏi nữa: Một vài người đưa ra quan điểm rằng nhân đôi có thể đại diện chính xác hơn cho số thực. Khi tuyên bố tôi sẽ nghĩ rằng họ cũng thường đại diện chính xác hơn cho họ. Nhưng đó có phải là một tuyên bố đúng rằng độ chính xác có thể giảm (đôi khi đáng kể) khi các thao tác dấu phẩy động được thực hiện?



5
Điều này được nâng cấp khá thường xuyên và tôi vẫn đấu tranh với nó. Ví dụ: tôi đang làm việc trên một ứng dụng tính toán tài chính nên tôi đang sử dụng số thập phân xuyên suốt. Nhưng các hàm Math và VisualBasic.Fin finance sử dụng gấp đôi nên có rất nhiều chuyển đổi khiến tôi liên tục đoán lần thứ hai về việc sử dụng số thập phân.
Jamie Ide

@JamieIde thật điên rồ khi các chức năng Tài chính sử dụng gấp đôi, tiền phải luôn ở dạng thập phân.
Chris Marisic

@ChrisMarisic Nhưng jamie Ide có thể làm gì với crap di sản bằng cách sử dụng gấp đôi? Sau đó, bạn nên sử dụng gấp đôi, nhiều chuyển đổi sẽ gây ra lỗi làm tròn ... không có gì lạ khi anh ấy đề cập đến VisualBasic pfffhh .....
Elisabeth

@Elisabeth tôi có thể sẽ sử dụng một thư viện khác hỗ trợ thập phân đúng. Dù VisualBasic. Tài chính cung cấp có khả năng tồn tại trong nhiều thư viện khác hiện nay
Chris Marisic

Câu trả lời:


306

Tôi nghĩ rằng bạn đã tóm tắt những lợi thế khá tốt. Tuy nhiên, bạn đang thiếu một điểm. Các decimalloại chỉ chính xác hơn tại đại diện cho cơ sở 10 số (ví dụ những người sử dụng bằng tiền / tính toán tài chính). Nói chung, doubleloại này sẽ cung cấp ít nhất là độ chính xác tuyệt vời (ai đó sửa tôi nếu tôi sai) và chắc chắn tốc độ lớn hơn cho các số thực tùy ý. Kết luận đơn giản là: khi xem xét sử dụng cái nào, luôn luôn sử dụng doubletrừ khi bạn cần base 10độ chính xác decimalcung cấp.

Biên tập:

Về câu hỏi bổ sung của bạn về việc giảm độ chính xác của các số dấu phẩy động sau các hoạt động, đây là một vấn đề tinh tế hơn một chút. Thật vậy, độ chính xác (tôi sử dụng thuật ngữ thay thế cho độ chính xác ở đây) sẽ giảm dần sau mỗi thao tác được thực hiện. Điều này là do hai lý do:

  1. thực tế là một số số nhất định (rõ ràng là số thập phân) không thể được biểu diễn thực sự ở dạng dấu phẩy động
  2. xảy ra lỗi làm tròn, giống như bạn đang thực hiện tính toán bằng tay. Nó phụ thuộc rất lớn vào bối cảnh (bạn thực hiện bao nhiêu thao tác) xem các lỗi này có đủ quan trọng để đảm bảo nhiều suy nghĩ hay không.

Trong mọi trường hợp, nếu bạn muốn so sánh hai số dấu phẩy động trên lý thuyết là tương đương (nhưng được sử dụng các phép tính khác nhau), bạn cần cho phép một mức độ nhất định (bao nhiêu thay đổi, nhưng thường rất nhỏ) .

Để biết tổng quan chi tiết hơn về các trường hợp cụ thể có thể đưa ra các lỗi về độ chính xác, hãy xem phần Độ chính xác của bài viết Wikipedia . Cuối cùng, nếu bạn muốn một cuộc thảo luận chuyên sâu (và toán học) nghiêm túc về các số / phép toán dấu phẩy động ở cấp độ máy, hãy thử đọc bài báo được trích dẫn Điều mà mọi nhà khoa học máy tính nên biết về số học dấu phẩy động .


1
Bạn có thể cung cấp một examp, e của một số cơ sở 10 mà độ chính xác bị mất khi chuyển đổi sang cơ sở 2 không?
Mark Cidade

@Mark: 1.000001 là một ví dụ, ít nhất là theo Jon Skeet. (Xem câu hỏi 3 của trang này: yoda.arachsys.com/csharp/teasers-answers.html )
Noldorin

25
@Mark: ví dụ rất đơn giản: 0,1 là một phần tuần hoàn trong cơ sở 2 vì vậy nó không thể được thể hiện chính xác trong a double. Các máy tính hiện đại vẫn sẽ in đúng giá trị nhưng chỉ vì chúng đoán ra kết quả - không phải vì nó thực sự được thể hiện chính xác.
Konrad Rudolph

1
Các Decimalloại có 93-bit chính xác trong mantissa, so với khoảng 52 cho double. Tuy nhiên, tôi muốn Microsoft hỗ trợ định dạng IEEE 80 bit, ngay cả khi nó phải được đệm tới 16 byte; nó sẽ cho phép một phạm vi lớn hơn doublehoặc Decimal, tốc độ tốt hơn nhiều so với Decimal, hỗ trợ cho các hoạt động siêu việt (ví dụ: sin (x), log (x), v.v.) và độ chính xác mà không tốt hơn Decimalsẽ tốt hơn double.
supercat

@charlotte: Nếu bạn đọc bài viết đầy đủ của tôi, bạn sẽ thấy điều đó được giải thích.
Noldorin

59

Bạn dường như chú ý đến những lợi ích của việc sử dụng loại dấu phẩy động. Tôi có xu hướng thiết kế các số thập phân trong mọi trường hợp và dựa vào một trình lược tả để cho tôi biết nếu các thao tác trên thập phân có gây ra tắc nghẽn hoặc chậm lại. Trong những trường hợp đó, tôi sẽ "giảm" xuống gấp đôi hoặc thả nổi, nhưng chỉ thực hiện bên trong và cố gắng cẩn thận để quản lý mất độ chính xác bằng cách giới hạn số chữ số có nghĩa trong hoạt động toán học đang được thực hiện.

Nói chung, nếu giá trị của bạn là nhất thời (không được sử dụng lại), bạn an toàn khi sử dụng loại dấu phẩy động. Vấn đề thực sự với các loại dấu phẩy động là ba kịch bản sau đây.

  1. Bạn đang tổng hợp các giá trị dấu phẩy động (trong trường hợp đó là hợp chất lỗi chính xác)
  2. Bạn xây dựng các giá trị dựa trên giá trị dấu phẩy động (ví dụ: trong thuật toán đệ quy)
  3. Bạn đang làm toán với rất nhiều chữ số có nghĩa (ví dụ 123456789.1 * .000000000000000987654321:)

BIÊN TẬP

Theo tài liệu tham khảo về số thập phân C # :

Các chữ số thập phân từ khóa biểu thị một kiểu dữ liệu 128-bit. So với các loại dấu phẩy động, loại thập phân có độ chính xác cao hơn và phạm vi nhỏ hơn, điều này làm cho nó phù hợp cho các tính toán tài chính và tiền tệ.

Vì vậy, để làm rõ tuyên bố trên của tôi:

Tôi có xu hướng thiết kế các số thập phân trong mọi trường hợp và dựa vào một trình lược tả để cho tôi biết nếu các thao tác trên thập phân có gây ra tắc nghẽn hoặc chậm lại.

Tôi chỉ từng làm việc trong các ngành công nghiệp nơi số thập phân thuận lợi. Nếu bạn đang làm việc trên các công cụ đồ họa hoặc đồ họa, có lẽ sẽ có ích hơn nhiều khi thiết kế cho loại dấu phẩy động (float hoặc double).

Số thập phân không chính xác vô hạn (không thể biểu thị độ chính xác vô hạn cho tính không tách rời trong một kiểu dữ liệu nguyên thủy), nhưng nó chính xác hơn nhiều so với gấp đôi:

  • số thập phân = 28-29 chữ số có nghĩa
  • gấp đôi = 15-16 chữ số có nghĩa
  • float = 7 chữ số có nghĩa

CHỈNH SỬA 2

Đáp lại bình luận của Konrad Rudolph , mục số 1 (ở trên) hoàn toàn chính xác. Tập hợp của sự thiếu chính xác thực sự hợp chất. Xem mã dưới đây để biết ví dụ:

private const float THREE_FIFTHS = 3f / 5f;
private const int ONE_MILLION = 1000000;

public static void Main(string[] args)
{
    Console.WriteLine("Three Fifths: {0}", THREE_FIFTHS.ToString("F10"));
    float asSingle = 0f;
    double asDouble = 0d;
    decimal asDecimal = 0M;

    for (int i = 0; i < ONE_MILLION; i++)
    {
        asSingle += THREE_FIFTHS;
        asDouble += THREE_FIFTHS;
        asDecimal += (decimal) THREE_FIFTHS;
    }
    Console.WriteLine("Six Hundred Thousand: {0:F10}", THREE_FIFTHS * ONE_MILLION);
    Console.WriteLine("Single: {0}", asSingle.ToString("F10"));
    Console.WriteLine("Double: {0}", asDouble.ToString("F10"));
    Console.WriteLine("Decimal: {0}", asDecimal.ToString("F10"));
    Console.ReadLine();
}

Điều này xuất ra như sau:

Three Fifths: 0.6000000000
Six Hundred Thousand: 600000.0000000000
Single: 599093.4000000000
Double: 599999.9999886850
Decimal: 600000.0000000000

Như bạn có thể thấy, mặc dù chúng tôi đã thêm từ cùng một nguồn, nhưng kết quả của nhân đôi ít chính xác hơn (mặc dù có thể sẽ làm tròn chính xác) và độ nổi ít chính xác hơn, đến mức nó đã được giảm xuống chỉ còn hai chữ số có nghĩa.


1
Điểm 1 không chính xác. Lỗi chính xác / làm tròn chỉ xảy ra trong quá trình đúc, không phải trong tính toán. Đó tất nhiên đúng là hầu hết các phép toán không ổn định, do đó nhân lỗi. Nhưng đây là một vấn đề khác và nó áp dụng tương tự cho tất cả các loại dữ liệu có độ chính xác hạn chế, đặc biệt là cho số thập phân.
Konrad Rudolph

1
@Konrad Rudolph, xem ví dụ trong "EDIT 2" là bằng chứng về điểm mà tôi đã cố gắng thực hiện trong mục số 1. Thông thường, vấn đề này không biểu hiện vì sự thiếu cân bằng tích cực với sự thiếu quyết đoán tiêu cực và họ rửa sạch tổng hợp, nhưng tổng hợp cùng một số (như tôi đã làm trong ví dụ) nêu bật vấn đề.
Michael Meadows

Ví dụ tuyệt vời. Chỉ cần hiển thị nó cho các nhà phát triển cơ sở của tôi, những đứa trẻ đã rất ngạc nhiên.
Machado

Bây giờ bạn có thể làm điều tương tự với 2/3 thay vì 3/5 ... Bạn nên tìm hiểu về hệ thống số giới tính xử lý 2/3 hoàn toàn tốt.
gnasher729

1
@ gnasher729, sử dụng 2/3 thay vì 3/5 không được xử lý hoàn hảo tốt cho các loại khác nhau. Thật thú vị, giá trị float mang lại Single: 667660.400000000000trong khi giá trị thập phân mang lại Decimal: 666666.7000000000. Giá trị float nhỏ hơn một nghìn so với giá trị đúng.
jhenninger

25

Sử dụng số thập phân cho các giá trị cơ sở 10, ví dụ như tính toán tài chính, như những người khác đã đề xuất.

Nhưng gấp đôi thường chính xác hơn cho các giá trị tính toán tùy ý.

Ví dụ: nếu bạn muốn tính trọng số của từng dòng trong danh mục đầu tư, hãy sử dụng gấp đôi vì kết quả sẽ gần như thêm tới 100%.

Trong ví dụ sau, doubleResult gần với 1 hơn binaryResult:

// Add one third + one third + one third with decimal
decimal decimalValue = 1M / 3M;
decimal decimalResult = decimalValue + decimalValue + decimalValue;
// Add one third + one third + one third with double
double doubleValue = 1D / 3D;
double doubleResult = doubleValue + doubleValue + doubleValue;

Vì vậy, một lần nữa lấy ví dụ về một danh mục đầu tư:

  • Giá trị thị trường của mỗi dòng trong danh mục đầu tư là một giá trị tiền tệ và có lẽ sẽ được biểu thị tốt nhất dưới dạng thập phân.

  • Trọng số của mỗi dòng trong danh mục đầu tư (= Giá trị thị trường / SUM (Giá trị thị trường)) thường được biểu thị tốt hơn là gấp đôi.


6

Sử dụng phao đôi hoặc phao khi bạn không cần độ chính xác, ví dụ, trong trò chơi platformer tôi đã viết, tôi đã sử dụng phao để lưu trữ vận tốc của người chơi. Rõ ràng là tôi không cần siêu chính xác ở đây vì cuối cùng tôi cũng làm tròn một Int để vẽ trên màn hình.


3
Chính xác là lợi thế DUY NHẤT của số thập phân, điều này là đúng. Bạn không nên hỏi khi nào bạn nên sử dụng số dấu phẩy động trên số thập phân. Đó nên là suy nghĩ đầu tiên của bạn. Câu hỏi sau đó là khi nào bạn nên sử dụng số thập phân (và câu trả lời ở ngay đây ... khi độ chính xác có vấn đề).
Thợ săn sơ thẩm

3
@Daniel Straight, Thật buồn cười, nhưng tôi có ý kiến ​​ngược lại. Tôi nghĩ rằng việc sử dụng một loại ít chính xác hơn vì các đặc tính hiệu suất của nó tương đương với việc chuẩn bị trước. Bạn có khả năng sẽ phải trả tiền cho việc tiền sản xuất đó nhiều lần trước khi bạn nhận ra lợi ích của nó.
Michael Meadows

3
@Michael Meadows, tôi có thể hiểu lập luận này. Mặc dù vậy, một điều cần lưu ý là một trong những phàn nàn chính với tối ưu hóa sớm là các lập trình viên không có xu hướng biết điều gì sẽ chậm. Tuy nhiên, chúng tôi biết rằng số thập phân chậm hơn gấp đôi. Tuy nhiên, tôi cho rằng trong hầu hết các trường hợp, cải thiện hiệu suất sẽ không được người dùng chú ý. Tất nhiên, trong hầu hết các trường hợp, độ chính xác cũng không cần thiết. Heh.
Thợ săn sơ thẩm

Dấu phẩy động thập phân thực sự là LESS chính xác hơn dấu phẩy động nhị phân sử dụng cùng số bit. Lợi thế của thập phân là có thể biểu diễn chính xác các phân số DECIMAL như 0,01 thường thấy trong tính toán tài chính.
dan04

Chà, điều này không hoàn toàn chính xác :) - trong nhiều trò chơi, số dấu phẩy động có thể không được chấp nhận, vì thực tế là chúng không nhất quán. Xem tại đây
BlueRaja - Danny Pflughoeft

4

Trong một số Kế toán, hãy xem xét khả năng sử dụng các loại tích phân thay thế hoặc kết hợp. Ví dụ: giả sử rằng các quy tắc bạn hoạt động theo yêu cầu mọi kết quả tính toán được chuyển tiếp với ít nhất 6 vị trí thập phân và kết quả cuối cùng sẽ được làm tròn đến đồng xu gần nhất.

Một phép tính 1/6 của $ 100 mang lại $ 16,66666666666666 ..., vì vậy giá trị được thực hiện trong một bảng tính sẽ là $ 16,666667. Cả hai số thập phân và số thập phân sẽ mang lại kết quả chính xác đến 6 chữ số thập phân. Tuy nhiên, chúng ta có thể tránh bất kỳ lỗi tích lũy nào bằng cách mang kết quả về phía trước dưới dạng số nguyên 16666667. Mỗi phép tính tiếp theo có thể được thực hiện với cùng độ chính xác và được chuyển tiếp tương tự. Tiếp tục ví dụ, tôi tính thuế bán hàng Texas cho số tiền đó (16666667 * .0825 = 1375000). Thêm hai (đó là một bảng tính ngắn) 1666667 + 1375000 = 18041667. Di chuyển dấu thập phân trở lại cho chúng ta 18.041667, hoặc $ 18.04.

Mặc dù ví dụ ngắn này sẽ không mang lại lỗi tích lũy khi sử dụng gấp đôi hoặc thập phân, nhưng khá dễ dàng để hiển thị các trường hợp chỉ cần tính toán gấp đôi hoặc thập phân và chuyển tiếp sẽ tích lũy lỗi đáng kể. Nếu quy tắc bạn hoạt động theo yêu cầu số lượng vị trí thập phân có giới hạn, hãy lưu trữ từng giá trị dưới dạng số nguyên bằng cách nhân với 10 ^ (bắt buộc # vị trí thập phân), sau đó chia cho 10 ^ (bắt buộc # số thập phân) để có được thực tế giá trị sẽ tránh bất kỳ lỗi tích lũy.

Trong trường hợp phân số đồng xu không xảy ra (ví dụ: máy bán hàng tự động), không có lý do gì để sử dụng các loại không tách rời cả. Đơn giản chỉ cần nghĩ về nó như đếm xu, không phải đô la. Tôi đã thấy mã trong đó mọi phép tính chỉ liên quan đến toàn bộ đồng xu, nhưng việc sử dụng gấp đôi dẫn đến sai sót! Số nguyên chỉ toán loại bỏ vấn đề. Vì vậy, câu trả lời độc đáo của tôi là, khi có thể, từ bỏ cả hai lần và thập phân.


3

Nếu bạn cần xen kẽ nhị phân với các ngôn ngữ hoặc nền tảng khác, thì bạn có thể cần sử dụng float hoặc double, được chuẩn hóa.


2

Lưu ý: bài đăng này dựa trên thông tin về các khả năng của loại thập phân từ http://csharpindepth.com/Articles/General/Decimal.aspx và cách hiểu của tôi về ý nghĩa của nó. Tôi sẽ giả sử Double là độ chính xác gấp đôi bình thường của IEEE.

Lưu ý2: nhỏ nhất và lớn nhất trong bài đăng này giới thiệu về độ lớn của số.

Ưu điểm của "thập phân".

  • "Số thập phân" có thể biểu thị chính xác các số có thể được viết dưới dạng phân số thập phân (đủ ngắn), gấp đôi không thể. Điều này rất quan trọng trong sổ cái tài chính và tương tự, điều quan trọng là kết quả hoàn toàn khớp với những gì con người thực hiện các tính toán sẽ đưa ra.
  • "Số thập phân" có mantissa lớn hơn nhiều so với "gấp đôi". Điều đó có nghĩa là đối với các giá trị trong phạm vi "thập phân" được chuẩn hóa sẽ có độ chính xác cao hơn nhiều so với gấp đôi.

Nhược điểm của thập phân

  • Nó sẽ chậm hơn nhiều (tôi không có điểm chuẩn nhưng tôi đoán ít nhất là một thứ tự cường độ có thể nhiều hơn), số thập phân sẽ không được hưởng lợi từ bất kỳ gia tốc phần cứng và số học nào trên đó sẽ yêu cầu nhân / chia tương đối đắt tiền với lũy thừa 10 ( tốn kém hơn nhiều so với phép nhân và chia cho lũy thừa bằng 2) để khớp với số mũ trước khi cộng / trừ và để đưa số mũ trở lại phạm vi sau khi nhân / chia.
  • thập phân sẽ tràn ra trước tha đôi. số thập phân chỉ có thể đại diện cho số lên tới ± 2 96 -1. Bằng cách so sánh, đôi có thể biểu thị các số lên tới gần ± 2 1024
  • thập phân sẽ tràn vào sớm hơn. Các số nhỏ nhất có thể biểu thị bằng số thập phân là ± 10 -28 . Bằng cách so sánh, đôi có thể biểu thị các giá trị xuống 2 -149 (khoảng 10 -45 ) nếu các số phụ được hỗ trợ và 2 -126 (khoảng 10 -38 ) nếu không.
  • số thập phân chiếm gấp đôi bộ nhớ gấp đôi.

Ý kiến ​​của tôi là bạn nên mặc định sử dụng "số thập phân" cho công việc kiếm tiền và các trường hợp khác trong đó tính toán chính xác của con người là quan trọng và bạn nên sử dụng gấp đôi làm lựa chọn mặc định của mình trong thời gian còn lại.


2

Phụ thuộc vào những gì bạn cần nó cho.

Bởi vì float và double là các kiểu dữ liệu nhị phân, bạn có một số diifculties và các lỗi theo cách tính theo số vòng, do đó, double sẽ làm tròn 0,1 đến 0,00000001490116, double cũng sẽ làm tròn 1/3 đến 0,3333334326441. Đơn giản chỉ cần đặt không phải tất cả các số thực có biểu diễn chính xác trong hai loại

May mắn thay, C # cũng hỗ trợ cái gọi là số học dấu phẩy động thập phân, trong đó các số được biểu diễn thông qua hệ thống số thập phân chứ không phải hệ thống nhị phân. Do đó, số học dấu phẩy động thập phân không mất độ chính xác khi lưu trữ và xử lý số dấu phẩy động. Điều này làm cho nó vô cùng phù hợp với các tính toán khi cần mức độ chính xác cao.


0

Sử dụng các dấu phẩy động nếu bạn đánh giá hiệu suất trên độ chính xác.


6
Số thập phân không chính xác hơn, ngoại trừ trong một số trường hợp giới hạn nhất định đôi khi (không có nghĩa là luôn luôn) quan trọng.
David Thornley

0

Chọn loại trong chức năng của ứng dụng của bạn. Nếu bạn cần độ chính xác như trong phân tích tài chính, bạn đã trả lời câu hỏi của mình. Nhưng nếu ứng dụng của bạn có thể giải quyết với ước tính ok của bạn với gấp đôi.

Là ứng dụng của bạn cần một phép tính nhanh hay anh ta sẽ có toàn bộ thời gian trên thế giới để cho bạn một câu trả lời? Nó thực sự phụ thuộc vào loại ứng dụng.

Đồ họa đói? nổi hoặc gấp đôi là đủ. Phân tích dữ liệu tài chính, sao băng tấn công một loại hành tinh chính xác? Những người sẽ cần một chút chính xác :)


8
Số thập phân là ước tính, quá. Chúng phù hợp với các quy ước của số học tài chính, nhưng không có lợi thế trong việc tính toán liên quan đến vật lý.
David Thornley

0

Decimal có byte rộng hơn, double được hỗ trợ bởi CPU. Số thập phân là cơ số 10, do đó, chuyển đổi thập phân thành gấp đôi đang diễn ra trong khi số thập phân được tính.

For accounting - decimal
For finance - double
For heavy computation - double

Hãy nhớ rằng .NET CLR chỉ hỗ trợ Math.Pow (gấp đôi, gấp đôi). Số thập phân không được hỗ trợ.

.Khung lưới 4

[SecuritySafeCritical]
public static extern double Pow(double x, double y);

0

Một giá trị kép sẽ tuần tự hóa thành ký hiệu khoa học theo mặc định nếu ký hiệu đó ngắn hơn hiển thị thập phân. (ví dụ: 0,00000003 sẽ là 3e-8) Các giá trị thập phân sẽ không bao giờ được tuần tự hóa thành ký hiệu khoa học. Khi xê-ri hóa để tiêu thụ bởi một bên ngoài, đây có thể là một sự cân nhắc.

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.