Co można zrobić z językami programowania, aby uniknąć pułapek zmiennoprzecinkowych?


28

Nieporozumienie arytmetyki zmiennoprzecinkowej i jej niedociągnięć jest główną przyczyną zaskoczenia i zamieszania w programowaniu (rozważ liczbę pytań na temat przepełnienia stosu dotyczących „nieprawidłowego dodawania liczb”). Biorąc pod uwagę, że wielu programistów jeszcze nie zrozumiało jego konsekwencji, może wprowadzić wiele subtelnych błędów (szczególnie w oprogramowaniu finansowym). Co mogą zrobić języki programowania, aby uniknąć pułapek dla tych, którzy nie znają pojęć, a jednocześnie oferują szybkość, gdy dokładność nie jest krytyczna dla tych, którzy rozumieją pojęcia?


26
Jedyne, co język programowania może zrobić, aby uniknąć pułapek przetwarzania zmiennoprzecinkowego, to jego zakazanie. Zauważ, że obejmuje to również zmiennoprzecinkowe podstawy 10, co jest ogólnie tak samo niedokładne, z tym wyjątkiem, że aplikacje finansowe są do niego wstępnie przystosowane.
David Thornley,

4
Do tego właśnie służy „analiza numeryczna”. Dowiedz się, jak zminimalizować utratę precyzji - inaczej pułapki zmiennoprzecinkowe.

Dobry przykład problemu zmiennoprzecinkowego: stackoverflow.com/questions/10303762/0-0-0-0-0-0
Austin Henley

Odpowiedzi:


47

Mówisz „specjalnie dla oprogramowania finansowego”, co przywołuje jedno z moich ulubionych pomysłów: pieniądze nie są zmienne, to int .

Jasne, wygląda jak pływak. Ma tam przecinek dziesiętny. Ale to tylko dlatego, że jesteś przyzwyczajony do jednostek, które mylą problem. Pieniądze zawsze przychodzą w liczbach całkowitych. W Ameryce to centy. (W niektórych kontekstach myślę, że mogą to być młyny , ale na razie zignoruj ​​to.)

Kiedy powiesz 1,23 USD, to naprawdę 123 centy. Zawsze, zawsze, zawsze wykonuj matematykę w tych kategoriach, a wszystko będzie dobrze. Aby uzyskać więcej informacji, zobacz:

Odpowiadając bezpośrednio na pytanie, języki programowania powinny zawierać typ pieniędzy jako rozsądną prymitywność.

aktualizacja

Ok, powinienem był powiedzieć „zawsze” dwa razy, a nie trzy razy. Pieniądze są rzeczywiście zawsze int; Ci, którzy myślą inaczej, mogą spróbować wysłać mi 0,3 centa i pokazać mi wynik na wyciągu bankowym. Ale jak zauważają komentatorzy, są rzadkie wyjątki, kiedy trzeba wykonywać matematykę zmiennoprzecinkową na liczbach podobnych do pieniędzy. Np. Niektóre rodzaje kalkulacji cen lub odsetek. Nawet wtedy należy je traktować jak wyjątki. Pieniądze przychodzą i wychodzą jako liczby całkowite, więc im bardziej twój system się do tego zbliży, tym będzie zdrowszy.


20
@JoelFan: mylisz koncepcję implementacji specyficznej dla platformy.
whatsisname

12
To nie jest takie proste. Obliczenia odsetek, między innymi, dają centy ułamkowe i muszą zostać zaokrąglone w pewnym momencie zgodnie z określoną metodą.
kevin cline

24
Fikcyjne -1, ponieważ brakuje mi przedstawiciela do przegłosowania :) ... Może to być poprawne dla wszystkiego, co masz w portfelu, ale istnieje wiele sytuacji księgowych, w których możesz dobrze radzić sobie z dziesiątymi częściami centów lub mniejszymi ułamkami. Decimaljest jedynym rozsądnym systemem radzenia sobie z tym, a twój komentarz „zignoruj ​​to na razie” jest zwiastunem zagłady dla programistów na całym świecie: P
detly

9
@kevin cline: W obliczeniach są ułamkowe centy, ale istnieją konwencje dotyczące ich obsługi. Celem obliczeń finansowych nie jest poprawność matematyczna, ale uzyskanie dokładnie takich samych wyników, jakie uzyskałby bankier z kalkulatorem.
David Thornley

6
Wszystko będzie idealnie, zastępując słowo „liczba całkowita” słowem „racjonalny” -
Emilio Garavaglia

15

Zapewnienie obsługi typu dziesiętnego pomaga w wielu przypadkach. Wiele języków ma typ dziesiętny, ale są one w niepełnym użyciu.

Ważne jest zrozumienie przybliżenia występującego podczas pracy z reprezentacją liczb rzeczywistych. Używanie zarówno liczb dziesiętnych, jak i zmiennoprzecinkowych 9 * (1/9) != 1jest poprawną instrukcją. Gdy stałe optymalizator może zoptymalizować obliczenia, aby były poprawne.

Pomocne byłoby podanie przybliżonego operatora. Takie porównania są jednak problematyczne. Pamiętaj, że 0,9999 biliona dolarów to w przybliżeniu 1 bilion dolarów. Czy mógłbyś zdeponować różnicę na moim koncie bankowym?


2
0.9999...bilion dolarów to dokładnie dokładnie 1 bilion dolarów.
PO PROSTU MOJA poprawna OPINIA

5
@JUST: Tak, ale nie spotkałem żadnych komputerów z rejestrami, które będą przechowywać 0.99999.... Wszystkie one w pewnym momencie obcinają się, co powoduje nierówność. 0.9999jest wystarczający dla inżynierii. Dla celów finansowych tak nie jest.
BillThor

2
Ale jaki system wykorzystał biliony dolarów jako podstawę zamiast dolarów?
Brad

@Brad Spróbuj obliczyć (1 bilion / 3) * 3 na swoim kalkulatorze. Jaką masz wartość?
BillThor

8

Powiedziano nam, co robić na pierwszym roku (drugiego) wykładu z informatyki, kiedy poszedłem na uniwersytet (ten kurs był warunkiem koniecznym dla większości kursów informatycznych)

Pamiętam, że wykładowca powiedział: „Liczby zmiennoprzecinkowe są przybliżone. Używaj typów całkowitych dla pieniędzy. Używaj FORTRAN lub innego języka z liczbami BCD, aby uzyskać dokładne obliczenia”. (a następnie wskazał przybliżenie, używając tego klasycznego przykładu 0,2 niemożliwego do dokładnego przedstawienia w binarnym zmiennoprzecinkowym). To również pojawiło się w tym tygodniu w ćwiczeniach laboratoryjnych.

Ten sam wykład: „Jeśli musisz uzyskać większą dokładność od liczb zmiennoprzecinkowych, posortuj terminy. Dodawaj małe liczby razem, a nie duże.” To utkwiło mi w pamięci.

Kilka lat temu miałem pewną sferyczną geometrię, która musiała być bardzo dokładna i wciąż szybka. 80-bitowe podwojenie na komputerach PC nie powodowało cięcia, więc dodałem do programu kilka typów, które posortowały terminy przed wykonaniem operacji przemiennych. Problem rozwiązany.

Zanim narzekasz na jakość gitary, naucz się grać.

Cztery lata temu miałem współpracownika, który pracował dla JPL. Wyraził niedowierzanie, że użyliśmy FORTRAN do niektórych celów. (Potrzebowaliśmy bardzo dokładnych symulacji numerycznych obliczonych offline.) „Zastąpiliśmy cały ten FORTRAN C ++” - powiedział z dumą. Przestałem się zastanawiać, dlaczego przegapili planetę.


2
Daj +1 odpowiednie narzędzie do właściwej pracy. Chociaż tak naprawdę nie używam FORTRAN. Na szczęście nie pracuję też nad naszymi systemami finansowymi w pracy.
James Khoury,

„Jeśli musisz uzyskać większą dokładność od liczb zmiennoprzecinkowych, posortuj terminy. Dodawaj małe liczby razem, a nie duże.” Jakaś próbka na ten temat?
mamcx,

@mamcx Wyobraź sobie dziesiętną liczbę zmiennoprzecinkową mającą tylko jedną cyfrę prefikcji. Obliczenia 1.0 + 0.1 + ... + 0.1(powtarzane 10 razy) powracają, 1.0gdy każdy wynik pośredni zostanie zaokrąglony. Robi to w drugą stronę, można uzyskać wyniki pośrednie 0.2, 0.3..., 1.0a na końcu 2.0. Jest to skrajny przykład, ale przy realistycznych liczbach zmiennoprzecinkowych zdarzają się podobne problemy. Podstawową ideą jest to, że dodanie liczb o podobnej wielkości prowadzi do najmniejszego błędu. Zacznij od najmniejszych liczb, ponieważ ich suma jest większa i dlatego lepiej nadaje się do dodawania do większych.
maaartinus

Elementy zmiennoprzecinkowe w Fortran i C ++ będą jednak w większości identyczne. Oba są dokładne i offline, i jestem pewien, że Fortran nie ma natywnych wersji BCD ...
Mark

8

Ostrzeżenie: System zmiennoprzecinkowy System.Double nie ma precyzji do bezpośredniego testowania równości.

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

Nie wierzę, że cokolwiek można lub należy zrobić na poziomie językowym.


1
Dawno nie używałem float ani double, więc jestem ciekawy. Czy to aktualne ostrzeżenie kompilatora, czy tylko to, które chciałbyś zobaczyć?
Karl Bielefeldt

1
@Karl - Osobiście nie widziałem go ani nie potrzebuję, ale wyobrażam sobie, że może być przydatny dla oddanych, ale zielonych programistów.
ChaosPandion

1
Binarne typy zmiennoprzecinkowe nie są ani lepsze ani gorsze jakościowo niż w Decimalprzypadku testów równości. Różnica między 1.0m/7.0m*7.0mi 1.0mmoże być o wiele rzędów wielkości mniejsza niż różnica między 1.0/7.0*7.0, ale nie jest równa zero.
supercat

1
@Patrick - Nie jestem pewien, do czego zmierzasz. Istnieje ogromna różnica między byciem prawdą w jednym przypadku a byciem prawdą we wszystkich przypadkach.
ChaosPandion

1
@ChaosPandion Problem z przykładem w tym poście nie polega na porównaniu równości, lecz na literach zmiennoprzecinkowych. Nie ma pływaka o dokładnej wartości 1,0 / 10. Obliczenia zmiennoprzecinkowe dają 100% dokładne wyniki przy obliczaniu liczb całkowitych pasujących do mantysy.
Patrick

7

Domyślnie języki powinny używać argumentów o dowolnej dokładności dla liczb niecałkowitych.

Ci, którzy muszą zoptymalizować, zawsze mogą poprosić o pływaki. Używanie ich jako domyślnych miało sens w językach programowania C i innych systemach, ale nie w większości popularnych obecnie języków.


1
Jak zatem radzisz sobie z liczbami nieracjonalnymi?
dsimcha

3
Robisz to tak samo jak w przypadku liczb zmiennoprzecinkowych: przybliżenie.
Waquo

1
Muszę powiedzieć, że myślę, że to ma sens, większość ludzi, którzy potrzebują dokładnych liczb, potrzebuje racjonalności, a nie irracjonalności (nauka i inżynieria mogą używać irracjonalności, ale znów powracasz do przybliżonego królestwa lub robisz jakieś dość wyspecjalizowane matematyki)
jk.

1
Obliczenia z uzasadnieniami o dowolnej dokładności będą często o rząd wielkości wolniejsze (być może WIELU rzędów wielkości wolniejsze) niż obliczenia z obsługą sprzętową double. Jeśli obliczenia muszą być dokładne dla części na milion, lepiej poświęcić mikrosekundę na obliczenie z dokładnością do kilku części na miliard, niż spędzić sekundę na obliczeniu absolutnie dokładnie.
supercat

5
@supercat: Sugerujesz, że jesteś tylko dzieckiem przedwczesnej optymalizacji. Obecna sytuacja jest taka, że ​​ogromna większość programistów nie potrzebuje żadnej potrzeby szybkiej matematyki, a następnie gryzie ją trudne do zrozumienia (zmienne) zachowanie zmiennoprzecinkowe, tak że stosunkowo niewielka liczba programistów, którzy potrzebują szybkiej matematyki, otrzymuje ją bez konieczności wpisać jeden dodatkowy znak. To miało sens w latach siedemdziesiątych, teraz to tylko nonsens. Domyślnie powinno być bezpieczne. Ci, którzy potrzebują szybko, powinni o to poprosić.
Waquo,

4

Dwa największe problemy dotyczące liczb zmiennoprzecinkowych to:

  • niespójne jednostki zastosowane do obliczeń (zauważ, że wpływa to również na arytmetykę liczb całkowitych w ten sam sposób)
  • niezrozumienie, że liczby FP są przybliżeniem i jak inteligentnie radzić sobie z zaokrąglaniem.

Pierwszego rodzaju awarii można zaradzić tylko poprzez podanie typu złożonego, który zawiera informacje o wartości i jednostce. Na przykład a lengthlub areawartość, która zawiera jednostkę (odpowiednio metry lub metry kwadratowe lub stopy i stopy kwadratowe). W przeciwnym razie musisz bardzo uważać, aby zawsze pracować z jednym rodzajem jednostki miary i konwertować na inny tylko wtedy, gdy dzielimy się odpowiedzią z człowiekiem.

Drugi rodzaj niepowodzenia to błąd koncepcyjny. Niepowodzenia objawiają się, gdy ludzie myślą o nich jako o liczbach bezwzględnych . Wpływa na operacje równościowe, skumulowane błędy zaokrąglania itp. Na przykład może być poprawne, że dla jednego systemu dwa pomiary są równoważne w ramach pewnego marginesu błędu. Tj .999 i 1.001 są mniej więcej takie same jak 1.0, gdy nie przejmujesz się różnicami mniejszymi niż +/- .1. Jednak nie wszystkie systemy są tak łagodne.

Jeśli potrzebne jest jakieś narzędzie na poziomie językowym, nazwałbym to precyzją równości . W NUnit, JUnit i podobnie skonstruowanych ramach testowych możesz kontrolować dokładność uważaną za poprawną. Na przykład:

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

Gdyby na przykład C # lub Java zostały zmienione w celu włączenia operatora precyzyjnego, mogłoby to wyglądać mniej więcej tak:

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

Jeśli jednak podasz taką funkcję, musisz również wziąć pod uwagę przypadek, w którym równość jest dobra, jeśli strony +/- nie są takie same. Na przykład + 1 / -10 uważa, że ​​dwie liczby są równoważne, jeśli jedna z nich była w odległości 1 więcej lub 10 mniej niż pierwsza liczba. Aby poradzić sobie z tą sprawą, może być konieczne dodanie rangesłowa kluczowego:

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

2
Zmieniłbym zamówienie. Problem koncepcyjny jest wszechobecny. Dla porównania problem konwersji jednostek jest stosunkowo niewielki.
S.Lott

Podoba mi się koncepcja operatora precyzyjnego, ale jak wspominasz dalej, zdecydowanie trzeba go dobrze przemyśleć. Osobiście byłbym bardziej skłonny postrzegać to jako własną kompletną konstrukcję syntaktyczną.
ChaosPandion

Można to również bardzo łatwo zrobić w bibliotece.
Michael K,

1
@ dan04: Myślałem bardziej w kategoriach „wszystkich obliczeń z dokładnością do jednego procent” lub podobnych. Widziałem tar-pit, który jest jednostką miary, i trzymam się z daleka.
TMN

1
Około 25 lat temu widziałem pakiet numeryczny zawierający typ składający się z pary liczb zmiennoprzecinkowych reprezentujących maksymalne i minimalne możliwe wartości dla ilości. Gdy liczby przechodzą przez obliczenia, rośnie różnica między maksimum a minimum. W efekcie umożliwiło to sprawdzenie, ile rzeczywistej precyzji było w obliczonej wartości.
supercat

3

Co potrafią języki programowania? Nie wiem, czy jest jedna odpowiedź na to pytanie, ponieważ wszystko, co kompilator / tłumacz robi w imieniu programisty, aby jego życie było łatwiejsze, zwykle działa wbrew wydajności, przejrzystości i czytelności. Myślę, że zarówno sposób C ++ (zapłać tylko za to, czego potrzebujesz), jak i sposób Perla (zasada najmniejszego zaskoczenia) są poprawne, ale zależy to od aplikacji.

Programiści wciąż muszą pracować z językiem i rozumieć, w jaki sposób obsługuje zmiennoprzecinkowe, ponieważ jeśli nie, przyjmą założenia, a pewnego dnia określone zachowanie nie będzie zgodne z ich założeniami.

Moje zdanie na temat tego, co powinien wiedzieć programista:

  • Jakie typy zmiennoprzecinkowe są dostępne w systemie i języku
  • Jaki typ jest potrzebny
  • Jak wyrazić zamiary, jaki typ jest potrzebny w kodzie
  • Jak poprawnie skorzystać z dowolnej automatycznej promocji typu, aby zrównoważyć jasność i wydajność przy jednoczesnym zachowaniu poprawności

3

Co mogą zrobić języki programowania, aby uniknąć pułapek [zmiennoprzecinkowych] ...?

Użyj rozsądnych wartości domyślnych, np. Wbudowana obsługa decmials.

Groovy robi to całkiem nieźle, choć przy odrobinie wysiłku możesz nadal pisać kod, aby wprowadzić nieprecyzyjną zmiennoprzecinkową.


3

Zgadzam się, że nie ma nic do zrobienia na poziomie językowym. Programiści muszą zrozumieć, że komputery są dyskretne i ograniczone, a wiele przedstawionych na nich pojęć matematycznych jest jedynie przybliżeniem.

Nie wspominając o zmiennoprzecinkowym. Trzeba zrozumieć, że połowa wzorów bitowych jest używana dla liczb ujemnych i że 2 ^ 64 jest w rzeczywistości dość małe, aby uniknąć typowych problemów z arytmetyką liczb całkowitych.


nie zgadzają się, większość języków zapewnia obecnie zbyt duże wsparcie dla binarnych typów zmiennoprzecinkowych (dlaczego == jest nawet zdefiniowane dla liczb zmiennoprzecinkowych?), a niewystarczająca obsługa wymiernych lub dziesiętnych
jk.

@jk: Nawet jeśli wynik jakiegokolwiek obliczenia nigdy nie byłby gwarantowany jako wynik jakiegokolwiek innego obliczenia, porównanie równości byłoby nadal przydatne w przypadku, gdy ta sama wartość zostanie przypisana do dwóch zmiennych (chociaż powszechnie stosowane reguły równości są prawdopodobnie zbyt luźne, ponieważ x== ynie oznacza, że ​​wykonanie obliczeń na xda taki sam wynik jak wykonanie tego samego obliczenia na y).
supercat

@ superkat wciąż potrzebujesz porównania, ale wolałbym, żeby język wymagał ode mnie określenia tolerancji dla każdego porównania zmiennoprzecinkowego, mogę nadal wrócić do równości, wybierając tolerancję = 0, ale jestem przynajmniej zmuszony to zrobić wybór
jk.

3

Języki mogą zrobić jedną rzecz - usunąć porównanie równości z typów zmiennoprzecinkowych inne niż bezpośrednie porównanie z wartościami NAN.

Testy równości istniałyby tylko jako wywołanie funkcji, które wzięło dwie wartości i deltę, lub dla języków takich jak C #, które pozwalają typom mieć metody EqualsTo, który przyjmuje drugą wartość i deltę.


3

Wydaje mi się dziwne, że nikt nie wskazał racjonalnej sztuczki liczbowej rodziny Lisp.

Poważnie, otwórz sbcl i zrób to: (+ 1 3)a dostaniesz 4. Jeśli *( 3 2)dostaniesz 6. Teraz spróbuj (/ 5 3)i dostaniesz 5/3, czyli 5 trzecich.

To powinno trochę pomóc w niektórych sytuacjach, prawda?


Zastanawiam się, czy to możliwe, aby wiedzieć, czy wynik musi być reprezentowany jako 1/3, czy może być dokładnym ułamkiem dziesiętnym?
mamcx,

dobra sugestia
Peter Porfy,

3

Jedną rzecz chciałbym zobaczyć byłoby uznanie, że doublena floatnależy traktować jako rozszerzającej nawrócenia, podczas gdy floatdo doublezwęża (*). Może się to wydawać sprzeczne z intuicją, ale zastanów się, co faktycznie oznaczają te typy:

  • 0,1f oznacza „13 421 773,5 / 134 217 728, plus lub minus 1 / 268,435,456 lub więcej”.
  • 0,1 naprawdę oznacza 3,602,879,701,896,397 / 36 028,797,018,963,968, plus lub minus 1/72 057 594,037,927,936 lub więcej ”

Jeśli ktoś ma doublenajlepszą reprezentację wielkości „jedna dziesiąta” i konwertuje ją float, wynikiem będzie „13 421 773,5 / 134 217 728, plus lub minus 1 / 268,435,456”, co jest poprawnym opisem wartości.

Dla kontrastu, jeśli ktoś ma floatnajlepszą reprezentację wielkości „jedna dziesiąta” i konwertuje ją double, wynik będzie wynosić „13 421 7733,5 / 134 217 728, plus lub minus 1/72 057 594,037,927,936 lub więcej” - poziom implikowanej dokładności co jest błędne ponad 53 milionami razy.

Chociaż standard IEEE-744 wymaga, aby matematyka zmiennoprzecinkowa była wykonywana tak, jakby każda liczba zmiennoprzecinkowa reprezentowała dokładną liczbę liczbową dokładnie w środku jej zakresu, nie należy zakładać, że wartości zmiennoprzecinkowe faktycznie reprezentują te dokładne wielkości liczbowe. Wymóg, aby przyjąć, że wartości znajdują się w środku ich zakresów, wynika z trzech faktów: (1) obliczenia muszą być wykonane tak, jakby argumenty miały pewne szczególne dokładne wartości; (2) spójne i udokumentowane założenia są bardziej pomocne niż niespójne lub nieudokumentowane; (3) jeśli ktoś zamierza przyjąć spójne założenie, żadne inne spójne założenie nie może być lepsze niż założenie, że ilość reprezentuje środek jego zakresu.

Nawiasem mówiąc, pamiętam jakieś 25 lat temu, ktoś wymyślił pakiet numeryczny dla C, który używał „typów zakresów”, z których każdy składał się z pary 128-bitowych liczb zmiennoprzecinkowych; wszystkie obliczenia zostałyby wykonane w taki sposób, aby obliczyć minimalną i maksymalną możliwą wartość dla każdego wyniku. Jeśli ktoś wykona duże, długie obliczenie iteracyjne i uzyska wartość [12.53401391134 12.53902812673], można mieć pewność, że choć wiele cyfr precyzji zostało utraconych z powodu błędów zaokrąglania, wynik nadal można rozsądnie wyrazić jako 12,54 (i to nie było t naprawdę 12,9 lub 53,2). Dziwię się, że nie widziałem żadnego wsparcia dla takich typów w żadnym z głównych języków, zwłaszcza że wydaje się, że dobrze pasują do jednostek matematycznych, które mogą działać na wielu wartościach równolegle.

(*) W praktyce często pomocne jest stosowanie wartości podwójnej precyzji do przechowywania obliczeń pośrednich podczas pracy z liczbami o pojedynczej precyzji, dlatego użycie wszystkich typów operacji może być denerwujące. Języki mogłyby pomóc, mając typ „rozmytego podwójnego”, który wykonywałby obliczenia jako podwójny i mógłby być swobodnie przesyłany do iz pojedynczego; byłoby to szczególnie pomocne, gdyby funkcje, które pobierają parametry typu doublei powrotu, doublemogły zostać oznaczone, aby automatycznie generowały przeciążenie, które akceptuje i zwraca zamiast tego „rozmyte podwójne”.


2

Jeśli więcej języków programowania pobierze stronę z baz danych i pozwoli programistom określić długość i precyzję liczbowych typów danych, mogą znacznie zmniejszyć prawdopodobieństwo wystąpienia błędów związanych z liczbą zmiennoprzecinkową. Jeśli język pozwolił deweloperowi zadeklarować zmienną jako zmiennoprzecinkową (2), co wskazuje, że potrzebował liczby zmiennoprzecinkowej z dwiema cyframi dokładności dziesiętnej, może wykonywać operacje matematyczne znacznie bezpieczniej. Gdyby to zrobił reprezentując wewnętrznie zmienną jako liczbę całkowitą i dzieląc przez 100 przed ujawnieniem wartości, mógłby poprawić prędkość, stosując szybsze ścieżki arytmetyczne liczb całkowitych. Semantyka Float (2) pozwoliłaby także programistom uniknąć ciągłej potrzeby zaokrąglania danych przed wysłaniem ich, ponieważ Float (2) z natury zaokrągla dane do dwóch miejsc po przecinku.

Oczywiście musisz zezwolić programistom na zapytanie o zmiennoprzecinkową maksymalną precyzję, gdy musi ona mieć tę precyzję. I wprowadzilibyśmy problemy, w których nieco inne wyrażenia tej samej operacji matematycznej dają potencjalnie różne wyniki z powodu pośrednich operacji zaokrąglania, gdy programiści nie mają wystarczającej precyzji w swoich zmiennych. Ale przynajmniej w świecie baz danych nie wydaje się to zbyt dużym problemem. Większość ludzi nie wykonuje takich obliczeń naukowych, które wymagają dużej precyzji w wynikach pośrednich.


Określenie długości i precyzji niewiele by się przydało. Posiadanie bazy 10 w punkcie stałym byłoby przydatne w przetwarzaniu finansowym, co usunęłoby wiele niespodzianek, jakie ludzie otrzymują od zmiennoprzecinkowych.
David Thornley,

@David - Być może coś mi brakuje, ale czym różni się typ danych o stałym punkcie 10 od tego, co tutaj proponuję? Liczba zmiennoprzecinkowa (2) w moim przykładzie miałaby stałe 2 cyfry dziesiętne i automatycznie zaokrąglałaby do najbliższej setnej, co prawdopodobnie można by wykorzystać do prostych obliczeń finansowych. Bardziej złożone obliczenia wymagałyby przydzielenia przez programistę większej liczby cyfr dziesiętnych.
Justin Cave

1
To, co popierasz, to podstawowy typ danych o stałym punkcie 10 z precyzją określoną przez programistę. Mówię, że precyzja określona przez programistę jest w większości bezcelowa i po prostu doprowadzi do różnego rodzaju błędów, które występowały w programach COBOL. (Na przykład, kiedy zmieniasz precyzję zmiennych, naprawdę łatwo przeoczyć jedną zmienną, przez którą przechodzi wartość. W przypadku innej zajmie to znacznie więcej myślenia o pośredniej wielkości wyniku niż jest dobre.)
David Thornley,

4
Tak Float(2)jak proponujesz, nie należy nazywać Float, ponieważ nic tu nie unosi się, z pewnością nie „kropka dziesiętna”.
Paŭlo Ebermann

1
  • języki mają obsługę typu dziesiętnego; oczywiście to nie rozwiązuje problemu, wciąż nie masz dokładnej i skończonej reprezentacji na przykład ⅓;
  • niektóre DB i frameworki mają obsługę typu Money, w zasadzie przechowuje liczbę centów jako liczbę całkowitą;
  • istnieją biblioteki do obsługi liczb wymiernych; rozwiązuje problem ⅓, ale nie rozwiązuje problemu na przykład √2;

Powyższe mają zastosowanie w niektórych przypadkach, ale tak naprawdę nie są ogólnym rozwiązaniem do obsługi wartości zmiennoprzecinkowych. Prawdziwym rozwiązaniem jest zrozumienie problemu i nauczenie się, jak sobie z nim radzić. Jeśli korzystasz z obliczeń zmiennoprzecinkowych, zawsze powinieneś sprawdzić, czy algorytmy są stabilne numerycznie . Istnieje ogromna dziedzina matematyki / informatyki związana z problemem. Nazywa się to analizą numeryczną .


1

Jak zauważyły ​​inne odpowiedzi, jedynym prawdziwym sposobem uniknięcia pułapek zmiennoprzecinkowych w oprogramowaniu finansowym jest nieużywanie go w tym miejscu. Może to być faktycznie wykonalne - jeśli zapewnisz dobrze zaprojektowaną bibliotekę poświęconą matematyce finansowej .

Funkcje zaprojektowane do importowania liczb zmiennoprzecinkowych powinny być wyraźnie oznaczone jako takie i wyposażone w parametry odpowiednie dla tej operacji, np .:

Finance.importEstimate(float value, Finance roundingStep)

Jedynym prawdziwym sposobem uniknięcia pułapek zmiennoprzecinkowych w ogóle jest edukacja - programiści muszą przeczytać i zrozumieć coś takiego, co każdy programista powinien wiedzieć o arytmetyce zmiennoprzecinkowej .

Kilka rzeczy, które mogą pomóc:

  • Poproszę tych, którzy pytają „dlaczego dokładne testowanie równości dla zmiennoprzecinkowego jest nawet legalne?”
  • Zamiast tego użyj isNear()funkcji.
  • Zapewnij i zachęcaj do korzystania ze zmiennoprzecinkowych obiektów akumulatorowych (które dodają sekwencje wartości zmiennoprzecinkowych bardziej stabilnie niż po prostu dodając je wszystkie do zwykłej zmiennej zmiennoprzecinkowej).

-1

Większość programistów zdziwiłaby się, że COBOL ma rację… w pierwszej wersji COBOL nie było zmiennoprzecinkowych, tylko dziesiętne, a tradycja w języku COBOL trwa do dziś, że pierwszą rzeczą, o której myślisz, deklarując liczbę, jest dziesiętna. ... zmiennoprzecinkowy byłby używany tylko wtedy, gdy naprawdę byłby potrzebny. Kiedy pojawił się C, z jakiegoś powodu nie było prymitywnego typu dziesiętnego, więc moim zdaniem, tam zaczęły się wszystkie problemy.


1
C nie miał typu dziesiętnego, ponieważ nie jest prymitywny, bardzo niewiele komputerów ma instrukcje sprzętowe dziesiętne. Możesz zapytać, dlaczego BASIC i Pascal go nie mieli, skoro nie zostały zaprojektowane tak, aby ściśle odpowiadały metalowi. COBOL i PL / I to jedyne znane mi języki, w których było coś takiego.
David Thornley,

3
@JoelFan: więc jak piszesz CO w języku COBOL? Dziesiętny nie rozwiązuje żadnych problemów, podstawa 10 jest tak samo niedokładna jak podstawa 2.
vartec 28.03.11

2
Dziesiętny rozwiązuje problem dokładnego przedstawiania dolarów i centów, co jest przydatne w przypadku języka „zorientowanego na biznes”. Ale poza tym dziesiętne są bezużyteczne; ma te same rodzaje błędów (np. 1/3 * 3 = 0,99999999), a jednocześnie jest znacznie wolniejszy. To dlatego, że to nie domyślny w językach, które nie zostały zaprojektowane specjalnie dla księgowych.
dan04

1
FORTRAN, który wyprzedza C o ponad dekadę, również nie ma standardowej obsługi dziesiętnej.
dan04

1
@JelFan: jeśli masz wartość kwartalną i potrzebujesz wartości miesięcznej, zgadnij, co musisz pomnożyć przez ... nie, to nie jest 0.33, to jest ⅓.
vartec
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.