Có thể làm gì để lập trình ngôn ngữ để tránh cạm bẫy dấu phẩy động?


28

Sự hiểu lầm về số học dấu phẩy động và các lần xuất hiện ngắn của nó là nguyên nhân chính gây bất ngờ và nhầm lẫn trong lập trình (xem xét số lượng câu hỏi trên Stack Overflow liên quan đến "số không thêm chính xác"). Xem xét nhiều lập trình viên vẫn chưa hiểu ý nghĩa của nó, nó có khả năng giới thiệu nhiều lỗi tinh vi (đặc biệt là vào phần mềm tài chính). Những ngôn ngữ lập trình có thể làm để tránh những cạm bẫy của nó đối với những người không quen với các khái niệm, trong khi vẫn cung cấp tốc độ của nó khi độ chính xác là không quan trọng đối với những người làm hiểu các khái niệm?


26
Điều duy nhất một ngôn ngữ lập trình có thể làm để tránh những cạm bẫy của xử lý dấu phẩy động là cấm nó. Lưu ý rằng điều này bao gồm cả dấu phẩy động cơ sở 10, nói chung là không chính xác, ngoại trừ các ứng dụng tài chính được điều chỉnh trước cho nó.
David Thornley

4
Đây là những gì "Phân tích số" dành cho. Tìm hiểu làm thế nào để giảm thiểu tổn thất chính xác - còn gọi là cạm bẫy điểm nổi.

Một ví dụ điển hình về vấn đề dấu phẩy động: stackoverflow.com/questions/10303762/0-0-0-0-0
Austin Henley

Câu trả lời:


47

Bạn nói "đặc biệt là phần mềm tài chính", điều này mang đến một trong những thú cưng của tôi: tiền không phải là một khoản nổi, đó là một int .

Chắc chắn, nó trông giống như một cái phao. Nó có một dấu thập phân trong đó. Nhưng đó chỉ là vì bạn đã quen với các đơn vị gây nhầm lẫn vấn đề. Tiền luôn có số lượng lớn. Ở Mỹ, đó là xu. (Trong một số bối cảnh nhất định, tôi nghĩ rằng nó có thể là các nhà máy , nhưng bỏ qua điều đó ngay bây giờ.)

Vì vậy, khi bạn nói $ 1,23, đó thực sự là 123 xu. Luôn luôn, luôn luôn, luôn luôn làm toán của bạn trong những điều khoản đó, và bạn sẽ ổn thôi. Để biết thêm thông tin, xem:

Trả lời câu hỏi trực tiếp, ngôn ngữ lập trình chỉ nên bao gồm một loại Tiền như là một nguyên thủy hợp lý.

cập nhật

Ok, tôi chỉ nên nói "luôn luôn" hai lần, thay vì ba lần. Tiền thực sự luôn luôn là một int; những người nghĩ khác được hoan nghênh thử gửi cho tôi 0,3 xu và cho tôi xem kết quả trên bảng sao kê ngân hàng của bạn. Nhưng như các nhà bình luận chỉ ra, có những trường hợp ngoại lệ hiếm khi bạn cần làm toán học dấu phẩy động trên các con số giống như tiền. Ví dụ, một số loại giá hoặc tính lãi. Ngay cả sau đó, những người nên được coi là ngoại lệ. Tiền đến và đi ra dưới dạng số nguyên, vì vậy hệ thống của bạn càng gần với điều đó, nó sẽ càng sạch hơn.


20
@JoelFan: bạn đang nhầm một khái niệm cho việc triển khai cụ thể nền tảng.
whatsisname

12
Nó không hoàn toàn đơn giản. Tính toán lãi suất, trong số những người khác, tạo ra xu phân số, và phải được làm tròn tại một số điểm theo một phương pháp được chỉ định.
kevin cline

24
Tiểu thuyết -1, vì tôi thiếu đại diện cho một downvote :) ... Điều này có thể đúng với bất cứ điều gì trong ví của bạn nhưng có rất nhiều tình huống kế toán mà bạn có thể xử lý với một phần mười xu hoặc phân số nhỏ hơn. Decimallà hệ thống lành mạnh duy nhất để giải quyết vấn đề này và nhận xét của bạn "bỏ qua điều đó ngay bây giờ" là điềm báo về sự diệt vong cho các lập trình viên ở khắp mọi nơi: P
gièm pha

9
@kevin cline: Có một phần xu trong tính toán, nhưng có những quy ước về cách xử lý chúng. Mục tiêu cho các tính toán tài chính không phải là tính chính xác toán học, nhưng nhận được kết quả chính xác giống như một nhân viên ngân hàng với máy tính.
David Thornley

6
Mọi thứ sẽ hoàn hảo bằng cách thay thế từ "số nguyên" bằng "hợp lý" -
Emilio Garavaglia

15

Cung cấp hỗ trợ cho một loại thập phân giúp trong nhiều trường hợp. Nhiều ngôn ngữ có loại thập phân, nhưng chúng không được sử dụng.

Hiểu gần đúng xảy ra khi làm việc với biểu diễn số thực là rất quan trọng. Sử dụng cả hai loại dấu thập phân và dấu phẩy động 9 * (1/9) != 1là một tuyên bố chính xác. Khi các hằng số tối ưu hóa có thể tối ưu hóa tính toán sao cho đúng.

Cung cấp một toán tử gần đúng sẽ giúp. Tuy nhiên, so sánh như vậy là có vấn đề. Lưu ý rằng 0,9999 nghìn tỷ đô la tương đương với 1 nghìn tỷ đô la. Bạn có thể vui lòng gửi tiền chênh lệch trong tài khoản ngân hàng của tôi không?


2
0.9999...nghìn tỷ đô la chính xác bằng 1 nghìn tỷ đô la thực sự.
CHỈ CẦN HOẠT ĐỘNG CỦA TÔI

5
@ CHỈ: Có nhưng tôi chưa gặp máy tính nào có thanh ghi sẽ giữ 0.99999.... Tất cả đều cắt ngắn tại một số điểm dẫn đến sự bất bình đẳng. 0.9999là đủ bằng cho kỹ thuật. Đối với mục đích tài chính, nó không phải là.
BillThor

2
Nhưng loại hệ thống nào đã sử dụng hàng nghìn tỷ đô la làm đơn vị cơ sở thay vì đơn vị đô la?
Brad

@Brad Hãy thử tính toán (1 nghìn tỷ / 3) * 3 trên máy tính của bạn. Bạn nhận được giá trị gì?
BillThor

8

Chúng tôi được cho biết phải làm gì trong bài giảng năm thứ nhất (năm thứ hai) về khoa học máy tính khi tôi đến trường đại học, (khóa học này là điều kiện tiên quyết cho hầu hết các khóa học khoa học)

Tôi nhớ lại rằng giảng viên nói "Số dấu phẩy động là xấp xỉ. Sử dụng các loại số nguyên cho tiền. Sử dụng FORTRAN hoặc ngôn ngữ khác với số BCD để tính toán chính xác." (và sau đó ông chỉ ra phép tính gần đúng, sử dụng ví dụ kinh điển đó là 0,2 không thể biểu diễn chính xác trong dấu phẩy động nhị phân). Điều này cũng bật lên trong tuần đó trong các bài tập trong phòng thí nghiệm.

Bài giảng tương tự: "Nếu bạn phải có được độ chính xác cao hơn từ dấu phẩy động, hãy sắp xếp các thuật ngữ của bạn. Thêm các số nhỏ lại với nhau, không cho các số lớn." Điều đó bị mắc kẹt trong tâm trí của tôi.

Vài năm trước tôi có một số hình học hình cầu cần phải rất chính xác, và vẫn còn nhanh. Nhân đôi 80 bit trên PC không cắt được, vì vậy tôi đã thêm một số loại vào chương trình đã sắp xếp các thuật ngữ trước khi thực hiện các hoạt động giao hoán. Vấn đề được giải quyết.

Trước khi bạn phàn nàn về chất lượng của guitar, hãy học chơi.

Tôi đã có một đồng nghiệp bốn năm trước, người đã làm việc cho JPL. Ông bày tỏ sự không tin rằng chúng tôi đã sử dụng FORTRAN cho một số thứ. (Chúng tôi cần mô phỏng số siêu chính xác được tính ngoại tuyến.) "Chúng tôi đã thay thế tất cả FORTRAN đó bằng C ++", ông tự hào nói. Tôi ngừng tự hỏi tại sao họ bỏ lỡ một hành tinh.


2
+1 công cụ phù hợp cho công việc phù hợp. Mặc dù tôi không thực sự sử dụng FORTRAN. Rất may tôi không làm việc trên các hệ thống tài chính của chúng tôi tại nơi làm việc.
James Khoury

"Nếu bạn phải có được độ chính xác cao hơn từ dấu phẩy động, hãy sắp xếp các thuật ngữ của bạn. Cộng các số nhỏ lại với nhau, không cho các số lớn." Bất kỳ mẫu trên này?
mamcx

@mamcx Hãy tưởng tượng một số dấu phẩy động thập phân chỉ có một chữ số trước. Tính toán 1.0 + 0.1 + ... + 0.1(lặp lại 10 lần) trả về 1.0khi mọi kết quả trung gian được làm tròn. Làm điều đó các vòng cách khác, bạn sẽ có được kết quả trung gian của 0.2, 0.3, ..., 1.0và cuối cùng 2.0. Đây là một ví dụ cực đoan, nhưng với các số dấu phẩy động thực tế, các vấn đề tương tự xảy ra. Ý tưởng cơ bản là việc thêm các số có kích thước tương tự dẫn đến sai số nhỏ nhất. Bắt đầu với các số nhỏ nhất vì tổng của chúng lớn hơn và do đó phù hợp hơn với các số lớn hơn.
maaartinus

Công cụ điểm nổi trong Fortran và C ++ sẽ gần như giống hệt nhau. Cả hai đều chính xác và ngoại tuyến, và tôi khá chắc chắn Fortran không có thực tế BCD bản địa ...
Mark

8

Cảnh báo: System.Double loại dấu phẩy động thiếu độ chính xác để kiểm tra đẳng thức trực tiếp.

double x = CalculateX();
if (x == 0.1)
{
    // ............
}

Tôi không tin bất cứ điều gì có thể hoặc nên được thực hiện ở cấp độ ngôn ngữ.


1
Tôi đã không sử dụng phao hoặc gấp đôi trong một thời gian dài, vì vậy tôi tò mò. Đó có phải là một cảnh báo trình biên dịch hiện có thực sự hay chỉ là một cảnh báo bạn muốn xem?
Karl Bielefeldt

1
@Karl - Cá nhân tôi chưa thấy nó hoặc cần nó nhưng tôi tưởng tượng nó có thể hữu ích cho các nhà phát triển chuyên dụng nhưng xanh.
ChaosPandion

1
Các loại dấu phẩy động nhị phân không tốt hơn hoặc kém hơn về chất lượng so với Decimalkhi kiểm tra tính bằng. Sự khác biệt giữa 1.0m/7.0m*7.0m1.0mcó thể là nhiều bậc có độ lớn nhỏ hơn chênh lệch giữa 1.0/7.0*7.0, nhưng nó không bằng không.
supercat

1
@Patrick - Tôi không chắc chắn những gì bạn đang nhận được. Có một sự khác biệt rất lớn giữa điều gì đó đúng với một trường hợp và đúng với mọi trường hợp.
ChaosPandion

1
@ChaosPandion Vấn đề với ví dụ trong bài viết này không phải là so sánh bình đẳng, đó là nghĩa đen của dấu phẩy động. Không có float với giá trị chính xác 1.0 / 10. Toán học dấu phẩy động cho kết quả chính xác 100% khi tính toán với các số nguyên phù hợp trong lớp phủ.
Patrick

7

Theo mặc định, các ngôn ngữ nên sử dụng các tỷ lệ hợp lý chính xác tùy ý cho các số không nguyên.

Những người cần tối ưu hóa luôn có thể yêu cầu nổi. Sử dụng chúng như một ý nghĩa mặc định trong C và các ngôn ngữ lập trình hệ thống khác, nhưng không phải trong hầu hết các ngôn ngữ phổ biến hiện nay.


1
Làm thế nào để bạn đối phó với số vô tỷ sau đó?
dsimcha

3
Bạn làm điều đó giống như với float: xấp xỉ.
Waquo

1
Tôi phải nói rằng tôi nghĩ điều này rất có ý nghĩa, hầu hết những người cần số chính xác đều cần những lý do không hợp lý (khoa học và kỹ thuật có thể sử dụng những bất hợp lý nhưng sau đó bạn lại quay trở lại cõi gần đúng hoặc bạn đang làm một số toán học thuần túy khá chuyên biệt)
jk.

1
Các tính toán với các tỷ lệ hợp lý có độ chính xác tùy ý thường sẽ là các đơn đặt hàng có cường độ chậm hơn (có thể là NHIỀU đơn hàng có cường độ chậm hơn) so với các tính toán có hỗ trợ phần cứng double. Nếu một phép tính cần chính xác đến một phần triệu, thì tốt hơn là dành một phần triệu giây để tính toán trong một vài phần tỷ, hơn là sử dụng một máy tính thứ hai hoàn toàn chính xác.
supercat

5
@supercat: Những gì bạn đề xuất chỉ là một con đẻ của tối ưu hóa sớm. Tình hình hiện nay là đại đa số các lập trình viên không cần bất cứ điều gì để học toán nhanh, và sau đó bị cắn bởi hành vi khó hiểu (mis), do đó số lượng lập trình viên tương đối cần toán học nhanh có được nó mà không cần phải có để nhập một ký tự phụ. Điều này có ý nghĩa vào những năm bảy mươi, bây giờ nó chỉ là vô nghĩa. Mặc định phải an toàn. Những người cần nhanh chóng nên yêu cầu nó.
Chờ

4

Hai vấn đề lớn nhất liên quan đến số dấu phẩy động là:

  • các đơn vị không nhất quán được áp dụng cho các tính toán (lưu ý điều này cũng ảnh hưởng đến số học số nguyên theo cùng một cách)
  • không hiểu rằng số FP là một xấp xỉ và làm thế nào để xử lý thông minh với làm tròn.

Loại thất bại đầu tiên chỉ có thể được khắc phục bằng cách cung cấp loại hỗn hợp bao gồm giá trị và thông tin đơn vị. Ví dụ: một lengthhoặc areagiá trị kết hợp đơn vị (mét hoặc mét vuông hoặc feet và feet vuông tương ứng). Nếu không, bạn phải siêng năng về việc luôn làm việc với một loại đơn vị đo lường và chỉ chuyển đổi sang loại khác khi chúng tôi chia sẻ câu trả lời với con người.

Loại thất bại thứ hai là một thất bại về khái niệm. Những thất bại thể hiện khi mọi người nghĩ về chúng như những con số tuyệt đối . Nó ảnh hưởng đến các hoạt động bình đẳng, lỗi làm tròn tích lũy, v.v. Ví dụ, có thể đúng rằng đối với một hệ thống, hai phép đo tương đương trong một phạm vi sai số nhất định. Tức là .999 và 1.001 gần giống như 1.0 khi bạn không quan tâm đến sự khác biệt nhỏ hơn +/- .1. Tuy nhiên, không phải tất cả các hệ thống đều khoan dung.

Nếu có bất kỳ cơ sở ngôn ngữ nào cần thiết, thì tôi sẽ gọi nó là độ chính xác bình đẳng . Trong NUnit, JUnit và các khung kiểm tra được xây dựng tương tự, bạn có thể kiểm soát độ chính xác được coi là chính xác. Ví dụ:

Assert.That(.999, Is.EqualTo(1.001).Within(10).Percent);
// -- or --
Assert.That(.999, Is.EqualTo(1.001).Within(.1));

Ví dụ, nếu C # hoặc Java bị thay đổi để bao gồm một toán tử chính xác, thì nó có thể trông giống như thế này:

if(.999 == 1.001 within .1) { /* do something */ }

Tuy nhiên, nếu bạn cung cấp một tính năng như vậy, bạn cũng phải xem xét trường hợp bình đẳng là tốt nếu các mặt +/- không giống nhau. Ví dụ: + 1 / -10 sẽ xem xét hai số tương đương nếu một trong số chúng nằm trong khoảng 1 nhiều hơn hoặc ít hơn 10 số so với số đầu tiên. Để xử lý trường hợp này, bạn cũng có thể cần phải thêm một rangetừ khóa:

if(.999 == 1.001 within range(.001, -.1)) { /* do something */ }

2
Tôi sẽ chuyển thứ tự. Vấn đề khái niệm là phổ biến. Vấn đề chuyển đổi đơn vị là tương đối nhỏ bằng cách so sánh.
S.Lott

Tôi thích khái niệm về một toán tử chính xác nhưng khi bạn đề cập thêm về nó chắc chắn sẽ cần phải được suy nghĩ kỹ. Cá nhân tôi sẽ có xu hướng xem nó như là cấu trúc cú pháp hoàn chỉnh của riêng nó.
ChaosPandion

Nó cũng có thể rất dễ dàng được thực hiện trong một thư viện.
Michael K

1
@ dan04: Tôi đã suy nghĩ nhiều hơn về "tất cả các tính toán chính xác trong vòng một phần trăm" hoặc tương tự. Tôi đã nhìn thấy hố nhựa là đơn vị xử lý biện pháp và tôi đang tránh xa.
TMN

1
Khoảng 25 năm trước, tôi đã thấy một gói số có một loại bao gồm một cặp số dấu phẩy động đại diện cho các giá trị tối đa và tối thiểu có thể có cho một số lượng. Khi các con số được chuyển qua các tính toán, sự khác biệt giữa tối đa và tối thiểu sẽ tăng lên. Thực tế, điều này cung cấp một phương tiện để biết có bao nhiêu độ chính xác thực có trong một giá trị được tính toán.
supercat

3

Ngôn ngữ lập trình có thể làm gì? Không biết có câu trả lời nào cho câu hỏi đó không, bởi vì bất cứ điều gì trình biên dịch / trình thông dịch làm thay mặt cho lập trình viên để làm cho cuộc sống của anh ấy / cô ấy dễ dàng hơn thường chống lại hiệu suất, sự rõ ràng và dễ đọc. Tôi nghĩ cả cách C ++ (chỉ trả cho những gì bạn cần) và cách Perl (nguyên tắc ít gây ngạc nhiên nhất) đều hợp lệ, nhưng nó phụ thuộc vào ứng dụng.

Các lập trình viên vẫn cần phải làm việc với ngôn ngữ và hiểu cách nó xử lý các dấu phẩy động, bởi vì nếu không, họ sẽ đưa ra các giả định và một ngày nào đó hành vi bị kết án sẽ không khớp với các giả định của họ.

Tôi đảm nhận những gì lập trình viên cần biết:

  • Những loại dấu phẩy động nào có sẵn trên hệ thống và bằng ngôn ngữ
  • Loại nào là cần thiết
  • Làm thế nào để thể hiện ý định của loại cần thiết trong mã
  • Làm thế nào để tận dụng chính xác bất kỳ khuyến mãi loại tự động nào để cân bằng sự rõ ràng và hiệu quả trong khi duy trì tính chính xác

3

Ngôn ngữ lập trình có thể làm gì để tránh cạm bẫy [dấu phẩy động]?

Sử dụng các giá trị mặc định hợp lý, ví dụ: hỗ trợ tích hợp cho các decmials.

Groovy thực hiện điều này khá độc đáo, mặc dù với một chút nỗ lực, bạn vẫn có thể viết mã để giới thiệu sự thiếu chính xác của dấu phẩy động.


3

Tôi đồng ý không có gì để làm ở cấp độ ngôn ngữ. Các lập trình viên phải hiểu rằng các máy tính là rời rạc và hạn chế, và nhiều khái niệm toán học được trình bày trong chúng chỉ là gần đúng.

Đừng bận tâm điểm nổi. Người ta phải hiểu rằng một nửa các mẫu bit được sử dụng cho các số âm và 2 ^ 64 thực sự khá nhỏ để tránh các vấn đề điển hình với số học số nguyên.


không đồng ý, hầu hết các ngôn ngữ hiện cung cấp quá nhiều hỗ trợ cho các loại dấu phẩy động nhị phân (tại sao == thậm chí được xác định cho số float?) và không đủ hỗ trợ cho các tỷ lệ hoặc số thập phân
jk.

@jk: Ngay cả khi kết quả của bất kỳ tính toán nào sẽ không bao giờ được đảm bảo bằng kết quả của bất kỳ tính toán nào khác, so sánh bằng vẫn sẽ hữu ích trong trường hợp cùng một giá trị được gán cho hai biến (mặc dù các quy tắc bình đẳng thường được thực hiện quá lỏng lẻo, vì x== ykhông có nghĩa là thực hiện tính toán trên xsẽ mang lại kết quả tương tự như thực hiện cùng một tính toán trên y).
supercat

@supercat bạn vẫn cần so sánh, nhưng tôi muốn ngôn ngữ yêu cầu tôi chỉ định dung sai cho mỗi so sánh dấu phẩy động, sau đó tôi vẫn có thể quay lại trạng thái bình đẳng bằng cách chọn dung sai = 0, nhưng ít nhất tôi buộc phải thực hiện điều đó sự lựa chọn
jk.

3

Một điều ngôn ngữ có thể làm - loại bỏ so sánh đẳng thức khỏi các loại dấu phẩy động khác với so sánh trực tiếp với các giá trị NAN.

Kiểm tra đẳng thức sẽ chỉ tồn tại dưới dạng gọi hàm lấy hai giá trị và delta hoặc cho các ngôn ngữ như C # cho phép các loại có phương thức EqualsTo lấy giá trị khác và delta.


3

Tôi thấy lạ là không ai chỉ ra thủ thuật số hợp lý của gia đình Lisp.

Nghiêm túc, mở sbcl và làm điều này: (+ 1 3)và bạn nhận được 4. Nếu *( 3 2)bạn nhận được 6. Bây giờ hãy thử (/ 5 3)và bạn nhận được 5/3, hoặc 5 phần ba.

Điều đó sẽ giúp phần nào trong một số tình huống, không nên?


Tôi tự hỏi, nếu có thể để biết nếu một kết quả cần phải được biểu thị bằng 1/3 hoặc có thể là một số thập phân chính xác?
mamcx

gợi ý hay
Peter Porfy 10/07/2015

3

Một điều tôi muốn nhìn thấy sẽ là một sự công nhận rằng doubleđể floatnên được coi là một sự chuyển đổi mở rộng, trong khi floatđể doubleđược thu hẹp (*). Điều đó có vẻ phản trực giác, nhưng hãy xem xét các loại thực sự có nghĩa là gì:

  • 0,1f có nghĩa là "13,421,773,5 / 134,217,728, cộng hoặc trừ 1 / 268,435,456 hoặc hơn".
  • 0,1 thực sự có nghĩa là 3,602,879,701,896,394 / 36,028,797,018,963,968, cộng hoặc trừ 1 / 72,057,594,037,927,936 hoặc hơn "

Nếu một người có doubleđại diện tốt nhất cho đại lượng "một phần mười" và chuyển đổi nó thành float, kết quả sẽ là "13,421,773,5 / 134,217,728, cộng hoặc trừ 1 / 268,435,456 hoặc hơn", đó là mô tả chính xác của giá trị.

Ngược lại, nếu một người có floatđại diện tốt nhất cho đại lượng "một phần mười" và chuyển đổi nó thành double, kết quả sẽ là "13,421,773,5 / 134,217,728, cộng hoặc trừ 1 / 72,057,594,037,927,936 hoặc hơn" - một mức độ chính xác ngụ ý đó là sai bởi một yếu tố của hơn 53 triệu.

Mặc dù tiêu chuẩn IEEE-744 yêu cầu các phép toán dấu phẩy động phải được thực hiện như thể mọi số dấu phẩy động đại diện cho số lượng chính xác chính xác ở trung tâm của phạm vi của nó, điều đó không nên dùng để ám chỉ rằng các giá trị dấu phẩy động thực sự đại diện cho chính xác số lượng. Thay vào đó, yêu cầu rằng các giá trị được giả định là ở giữa phạm vi của chúng bắt nguồn từ ba sự kiện: (1) tính toán phải được thực hiện như thể các toán hạng có một số giá trị chính xác cụ thể; (2) các giả định nhất quán và được ghi lại là hữu ích hơn so với các giả định không nhất quán hoặc không có giấy tờ; (3) nếu một người sẽ đưa ra một giả định nhất quán, không có giả định nhất quán nào khác có thể tốt hơn giả định một đại lượng đại diện cho trung tâm của phạm vi của nó.

Tình cờ, tôi nhớ khoảng 25 năm trước, một người nào đó đã nghĩ ra một gói số cho C sử dụng "các loại phạm vi", mỗi loại bao gồm một cặp phao 128 bit; tất cả các tính toán sẽ được thực hiện theo cách tính toán giá trị tối thiểu và tối đa có thể cho mỗi kết quả. Nếu một người thực hiện một phép tính lặp dài lớn và đưa ra giá trị [12.53401391134 12.53902812673], người ta có thể tin tưởng rằng trong khi nhiều chữ số chính xác bị mất cho các lỗi làm tròn, kết quả vẫn có thể được biểu thị một cách hợp lý là 12,54 (và nó không phải là ' t thực sự 12,9 hoặc 53,2). Tôi ngạc nhiên tôi chưa thấy bất kỳ sự hỗ trợ nào cho các loại như vậy trong bất kỳ ngôn ngữ chính nào, đặc biệt là vì chúng có vẻ phù hợp với các đơn vị toán học có thể hoạt động song song trên nhiều giá trị.

(*) Trong thực tế, thường hữu ích khi sử dụng các giá trị độ chính xác kép để giữ các tính toán trung gian khi làm việc với các số chính xác đơn, do đó việc phải sử dụng một kiểu chữ cho tất cả các hoạt động như vậy có thể gây khó chịu. Các ngôn ngữ có thể giúp đỡ bằng cách có loại "mờ đôi", sẽ thực hiện tính toán thành gấp đôi và có thể được tự do chuyển sang và từ đơn; điều này đặc biệt hữu ích nếu các hàm lấy tham số loại doublevà trả về doublecó thể được đánh dấu để chúng tự động tạo ra quá tải chấp nhận và trả về "mờ gấp đôi".


2

Nếu nhiều ngôn ngữ lập trình lấy một trang từ cơ sở dữ liệu và cho phép các nhà phát triển chỉ định độ dài và độ chính xác của các loại dữ liệu số của họ, thì họ có thể giảm đáng kể xác suất xảy ra lỗi liên quan đến dấu phẩy động. Nếu một ngôn ngữ cho phép nhà phát triển khai báo một biến là Float (2), chỉ ra rằng họ cần một số dấu phẩy động có hai chữ số thập phân chính xác, nó có thể thực hiện các phép toán một cách an toàn hơn nhiều. Nếu nó đã làm như vậy bằng cách biểu diễn biến dưới dạng một số nguyên bên trong và chia cho 100 trước khi hiển thị giá trị, nó có thể cải thiện tốc độ bằng cách sử dụng các đường dẫn số học số nguyên nhanh hơn. Các ngữ nghĩa của Float (2) cũng sẽ cho phép các nhà phát triển tránh nhu cầu làm tròn dữ liệu liên tục trước khi xuất dữ liệu vì Float (2) vốn đã làm tròn dữ liệu đến hai điểm thập phân.

Tất nhiên, bạn cần cho phép nhà phát triển yêu cầu giá trị dấu phẩy động có độ chính xác tối đa khi nhà phát triển cần có độ chính xác đó. Và bạn sẽ đưa ra các vấn đề trong đó các biểu thức hơi khác nhau của cùng một hoạt động toán học tạo ra kết quả có khả năng khác nhau do các hoạt động làm tròn trung gian khi các nhà phát triển không mang đủ độ chính xác trong các biến của họ. Nhưng ít nhất trong thế giới cơ sở dữ liệu, điều đó dường như không phải là vấn đề quá lớn. Hầu hết mọi người không thực hiện các loại tính toán khoa học đòi hỏi nhiều độ chính xác trong kết quả trung gian.


Chỉ định độ dài và độ chính xác sẽ làm rất ít hữu ích. Có cơ sở điểm 10 cố định sẽ hữu ích cho xử lý tài chính, điều này sẽ loại bỏ phần lớn những điều bất ngờ mà mọi người nhận được từ điểm nổi.
David Thornley

@David - Có lẽ tôi đang thiếu một cái gì đó nhưng làm thế nào một kiểu dữ liệu cơ sở 10 điểm cố định khác với những gì tôi đề xuất ở đây? Một số float (2) trong ví dụ của tôi sẽ có 2 chữ số thập phân cố định và sẽ tự động làm tròn đến hàng trăm gần nhất, đó là những gì bạn có thể sử dụng cho các tính toán tài chính đơn giản. Các tính toán phức tạp hơn sẽ yêu cầu nhà phát triển phân bổ số chữ số thập phân lớn hơn.
Hang động Justin

1
Những gì bạn ủng hộ là kiểu dữ liệu cơ sở 10 điểm cố định với độ chính xác do lập trình viên chỉ định. Tôi đang nói rằng độ chính xác do lập trình viên chỉ là vô nghĩa, và sẽ chỉ dẫn đến các loại lỗi tôi đã sử dụng để chạy trong các chương trình COBOL. (Ví dụ, khi bạn thay đổi độ chính xác của các biến, nó thực sự dễ dàng để bỏ lỡ một biến chạy giá trị thông qua Đối với người khác, nó sẽ mất rất nhiều suy nghĩ về kích thước kết quả trung gian hơn là tốt..)
David Thornley

4
Một Float(2)như bạn đề nghị không nên được gọi là Float, vì không có gì nổi đây, chắc chắn không phải là "dấu thập phân" này.
Paŭlo Ebermann

1
  • ngôn ngữ có hỗ trợ loại thập phân; tất nhiên điều này không thực sự giải quyết được vấn đề, tuy nhiên bạn vẫn không có đại diện chính xác và hữu hạn ví dụ;
  • một số DB và khung có hỗ trợ loại Tiền, về cơ bản, đây là lưu trữ số xu dưới dạng số nguyên;
  • có một số thư viện hỗ trợ số hữu tỷ; giải quyết vấn đề của, nhưng không giải quyết được vấn đề của ví dụ √2;

Những điều trên có thể áp dụng trong một số trường hợp, nhưng không thực sự là một giải pháp chung để xử lý các giá trị float. Giải pháp thực sự là hiểu vấn đề và học cách đối phó với nó. Nếu bạn đang sử dụng tính toán điểm nổi, bạn phải luôn kiểm tra xem thuật toán của bạn có ổn định về mặt số hay không . Có một lĩnh vực lớn về toán học / khoa học máy tính liên quan đến vấn đề này. Nó được gọi là Phân tích số .


1

Như các câu trả lời khác đã lưu ý, cách thực sự duy nhất để tránh những cạm bẫy nổi trong phần mềm tài chính là không sử dụng nó ở đó. Điều này thực sự có thể khả thi - nếu bạn cung cấp một thư viện được thiết kế tốt dành riêng cho toán tài chính .

Các hàm được thiết kế để nhập ước tính dấu phẩy động phải được dán nhãn rõ ràng như vậy và được cung cấp các tham số phù hợp với thao tác đó, ví dụ:

Finance.importEstimate(float value, Finance roundingStep)

Cách thực sự duy nhất để tránh những cạm bẫy dấu phẩy động nói chung là giáo dục - lập trình viên cần đọc và hiểu một cái gì đó giống như những gì mọi lập trình viên nên biết về số học dấu phẩy động .

Một số điều có thể giúp đỡ, mặc dù:

  • Tôi sẽ thứ hai những người hỏi "tại sao thử nghiệm bình đẳng chính xác cho dấu phẩy động thậm chí là hợp pháp?"
  • Thay vào đó, sử dụng một isNear()chức năng.
  • Cung cấp và khuyến khích sử dụng các đối tượng tích lũy dấu phẩy động (bổ sung các chuỗi các giá trị dấu phẩy động ổn định hơn là chỉ thêm tất cả chúng vào một biến dấu phẩy động thông thường).

-1

Hầu hết các lập trình viên sẽ ngạc nhiên khi COBOL hiểu đúng ... trong phiên bản đầu tiên của COBOL không có dấu phẩy động, chỉ có số thập phân và truyền thống trong COBOL tiếp tục cho đến ngày hôm nay rằng điều đầu tiên bạn nghĩ đến khi khai báo một số là số thập phân. .. điểm nổi sẽ chỉ được sử dụng nếu bạn thực sự cần nó. Khi C xuất hiện, vì một số lý do, không có loại thập phân nguyên thủy, vì vậy theo tôi, đó là nơi tất cả các vấn đề bắt đầu.


1
C không có loại thập phân vì nó không nguyên thủy, rất ít máy tính có bất kỳ loại hướng dẫn thập phân phần cứng nào. Bạn có thể hỏi tại sao BASIC và Pascal không có nó, vì chúng không được thiết kế để phù hợp với kim loại. COBOL và PL / Tôi là những ngôn ngữ duy nhất tôi biết về thời gian có bất cứ thứ gì như thế.
David Thornley

3
@JoelFan: vậy làm thế nào để bạn viết trong COBOL? Thập phân không giải quyết được bất kỳ vấn đề nào, cơ sở 10 cũng không chính xác như cơ sở 2.
vartec

2
Thập phân giải quyết vấn đề đại diện chính xác cho đô la và xu, rất hữu ích cho ngôn ngữ "Định hướng kinh doanh". Nhưng nếu không, thập phân là vô dụng; nó có cùng loại lỗi (ví dụ: 1/3 * 3 = 0.99999999) trong khi chậm hơn nhiều . Đó là lý do tại sao nó không phải là mặc định trong các ngôn ngữ không được thiết kế riêng cho kế toán.
dan04

1
Và FORTRAN, trước C hơn một thập kỷ, cũng không có hỗ trợ thập phân tiêu chuẩn.
dan04

1
@JoelFan: nếu bạn có giá trị hàng quý và bạn cần giá trị mỗi tháng, hãy đoán xem bạn phải nhân nó với ... không, đó không phải là 0,33, đó là.
vartec
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.