Dlaczego potrzebujesz float / double?


29

Oglądałem http://www.joelonsoftware.com/items/2011/06/27.html i śmiałem się z żartu Jona Skeeta o tym, że 0.3 nie jest 0.3. Osobiście nigdy nie miałem problemów z liczbami zmiennoprzecinkowymi / dziesiętnymi / podwójnymi, ale pamiętam, że nauczyłem się 6502 bardzo wcześnie i nigdy nie potrzebowałem liczb zmiennoprzecinkowych w większości moich programów. Jedyny raz użyłem go do grafiki i matematyki, gdzie niepoprawne liczby były w porządku, a dane wyjściowe dotyczyły ekranu i nie były przechowywane (w db, pliku) lub zależne.

Moje pytanie brzmi: gdzie są miejsca, w których zwykle używasz liczb zmiennoprzecinkowych / dziesiętnych / podwójnych? Wiem więc, aby uważać na te głupstwa. Za pieniądze używam długich i przechowuję wartości w centach, dla prędkości obiektu w grze dodaję liczby całkowite i dzielę (lub przesunięcie bitowe) wartość, aby wiedzieć, czy muszę przesunąć piksel, czy nie. (Sprawiłem, że obiekt poruszał się w 6502 dniach, nie mieliśmy podziału ani pływaków, ale mieliśmy zmiany).

Byłem więc głównie ciekawy.


10
ponieważ bardzo ważne jest to, że odsetki, które płacę od mojego kredytu hipotecznego, wynoszą 12,6, a do 13 lat, bo 13 to tak ładna, okrągła liczba.
Chani

1
„Nauczyłem się 6502 bardzo wcześnie i nigdy nie potrzebowałem pływaków w większości moich programów ... dla prędkości obiektu dodaję liczby całkowite i dzielę wartość, aby wiedzieć, czy przesunąć piksel, czy nie”. Są to bardzo nietypowe sposoby realizacji tych zadań we współczesnej praktyce, z wyjątkiem reprezentowania pieniędzy w długich centach.
jprete

Dobrze, że komputer rozumie milicenty.
tylermac

1
Lub dodatkowo, po co używać liczb dziesiętnych, skoro możemy używać ułamków?
tylermac

6
@ Scrooge - jak na ironię, nie można reprezentować 0,6 w liczbach zmiennoprzecinkowych.
Martin Beckett

Odpowiedzi:


28

Ponieważ są one w większości przypadków bardziej dokładne niż liczby całkowite.

Jak to jest? „dla prędkości obiektu w grze ...” jest to dobry przykład na taki przypadek. Powiedz, że potrzebujesz bardzo szybkich przedmiotów, takich jak pociski. Aby móc opisać ich ruch za pomocą liczb całkowitych zmiennych prędkości, musisz upewnić się, że prędkości mieszczą się w zakresie zmiennych liczb całkowitych, co oznacza, że ​​nie możesz mieć arbitralnie dokładnego rastra.

Ale możesz też chcieć opisać niektóre bardzo powolne obiekty, takie jak wskazówka godzinowa zegara. Ponieważ jest to około 6 rzędów wielkości wolniejsze niż obiekty pocisku, pierwsze ld (10⁶) ≈ 20 bitów ma wartość zero, co wyklucza short inttypy od samego początku. Ok, dzisiaj mamy longwszędzie, co daje nam wciąż wygodne 12 bitów. Ale nawet wtedy szybkość zegara będzie dokładnie z dokładnością do czterech miejsc po przecinku. To niezbyt dobry zegar ... ale z pewnością jest w porządku dla gry. Po prostu nie chcesz, aby raster był znacznie grubszy niż jest już.

... co prowadzi do problemów, jeśli któregoś dnia chcesz wprowadzić nowy, jeszcze szybszy typ obiektu. Nie ma już „nadmiaru”.

Co się stanie, jeśli wybierzemy typ pływaka? Ten sam rozmiar 32 bitów, ale teraz masz pełne 24 bity precyzji dla wszystkich obiektów. Oznacza to, że zegar ma wystarczającą precyzję, aby zachować synchronizację co do sekundy przez lata. Pociski nie mają większej precyzji, ale i tak „żyją” przez ułamki sekundy, więc byłyby całkowicie bezużyteczne, gdyby miały. I nie wpadasz w żadne kłopoty, jeśli chcesz opisać nawet znacznie szybsze obiekty (dlaczego nie prędkość światła? Nie ma problemu) lub znacznie wolniejsze. Z pewnością nie potrzebujesz takich rzeczy w grze, ale czasami robisz to w symulacjach fizycznych.

A dzięki liczbom zmiennoprzecinkowym zawsze otrzymujesz tę samą precyzję i bez sprytnego wyboru nieoczywistego rastra. Jest to chyba najważniejsza kwestia, ponieważ konieczność takiego wyboru jest bardzo podatna na błędy.


Liczby całkowite są idealnie dokładne. Niedokładność zależy od niepoprawnego obliczenia.
fjdumont

15
Liczby całkowite są doskonale dokładne tylko wtedy, gdy używasz ich do rzeczywistego przedstawienia liczb całkowitych (ℤ). Reprezentowanie czegokolwiek innego oznacza w rzeczywistości nieprawidłowe obliczenia. W takim przypadku masz dwie możliwości: albo zdefiniuj jakiś typ, który idealnie pasuje do liczb, które faktycznie chcesz reprezentować. jest to możliwe, na przykład Mathematica może to zrobić. Jest to jednak bardzo skomplikowane i czasochłonne i zwykle nie warte wysiłku, ponieważ tak naprawdę nie potrzebujesz doskonałej precyzji. Ale potrzebujesz dobrej precyzji, i tam właśnie liczby zmiennoprzecinkowe wykonują lepszą robotę niż liczby całkowite.
leftaroundabout

53

Używasz ich, gdy opisujesz wartość ciągłą, a nie dyskretną . Opisanie nie jest bardziej skomplikowane. Tylko nie popełniaj błędu zakładając, że jakakolwiek wartość z kropką dziesiętną jest ciągła. Jeśli zmienia się naraz w kawałkach, jak dodanie grosza, jest dyskretny.


28

Naprawdę masz tutaj dwa pytania.

Dlaczego w ogóle ktoś potrzebuje matematyki zmiennoprzecinkowej?

Jak zauważa Karl Bielefeldt, liczby zmiennoprzecinkowe pozwalają modelować ciągłe wielkości - i można je znaleźć wszędzie - nie tylko w świecie fizycznym, ale nawet w miejscach takich jak biznes i finanse.

W mojej karierze programistycznej korzystałem z matematyki zmiennoprzecinkowej: chemii, pracy w AutoCAD, a nawet pisania symulacji Monte Carlo do prognoz finansowych. W rzeczywistości jest facet o imieniu David E. Shaw, który zastosował techniki Walling Point oparte na liczbach zmiennoprzecinkowych, aby zarobić miliardy.

I oczywiście jest grafika komputerowa. Doradzam w opracowywaniu cukierków dla interfejsów użytkownika, a próbowanie tego w dzisiejszych czasach bez solidnego zrozumienia zmiennoprzecinkowego, trygonometrii, rachunku różniczkowego i algebry liniowej byłoby jak pokazanie się w walce na broń za pomocą scyzoryka.

Dlaczego ktoś miałby float kontra double ?

Dzięki standardowym reprezentacjom IEEE 754 32-bitowa liczba zmiennoprzecinkowa daje około 7 cyfr dziesiętnych dokładności i wykładniki w zakresie od 10 do 38 do 10 38 . 64-bitowe podwójne daje około 15 cyfr dziesiętnych dokładności i wykładników w zakresie od 10-307 do 10 307 .

Może się wydawać, że pływak wystarczyłby na to, czego ktokolwiek by potrzebował, ale tak nie jest. Na przykład wiele rzeczywistych wielkości mierzy się więcej niż 7 cyframi dziesiętnymi.

Ale bardziej subtelnie, istnieje problem zwany potocznie „błędem zaokrąglenia”. Binarne reprezentacje zmiennoprzecinkowe są ważne tylko dla wartości, których części ułamkowe mają mianownik o sile 2, np. 1/2, 1/4, 3/4 itd. Aby przedstawić inne ułamki, np. 1/10, „zaokrąglasz” wartość do najbliższej frakcji binarnej, ale to trochę źle - to jest „błąd zaokrąglenia”. Następnie, gdy robisz matematykę na tych niedokładnych liczbach, niedokładności w wynikach mogą być znacznie gorsze niż na początku - czasami procentowe odsetki błędów mnożą się, a nawet kumulują się wykładniczo.

W każdym razie, im więcej cyfr binarnych będziesz musiał pracować, tym bliżej zaokrąglonej reprezentacji binarnej będzie liczba, którą próbujesz reprezentować, więc błąd zaokrąglenia będzie mniejszy. Następnie, gdy wykonujesz na nim matematykę, jeśli masz dużo cyfr do pracy, możesz wykonać o wiele więcej operacji, zanim skumulowany błąd zaokrągleń pojawi się tam, gdzie jest problem.

W rzeczywistości 64-bitowe podwojenia z 15 cyframi dziesiętnymi nie są wystarczające do wielu zastosowań. Użyłem 80-bitowych liczb zmiennoprzecinkowych w 1985 roku, a IEEE definiuje teraz 128-bitowy (16-bajtowy) typ liczb zmiennoprzecinkowych, do którego mogę sobie wyobrazić zastosowania.


2
+1 Bob Moje doświadczenia z systemami sterowania o wysokiej rozdzielczości, takimi jak teleskop dla astronomii, są takie, że podwójny 64-bit nie jest wystarczająco dobry, chyba że posortujesz swoje warunki. To samo dotyczy kontroli ognia i nawigacji dalekiego zasięgu
Tim Williscroft

20

Jest to powszechne nieporozumienie, że wszędzie tam, gdzie masz do czynienia z pieniędzmi, powinieneś przechowywać ich wartość jako liczbę całkowitą (centy). Chociaż w niektórych prostych przypadkach, takich jak sklep internetowy, jest to prawda, jeśli masz coś bardziej zaawansowanego, to niewiele pomaga.

Weźmy przykład: programista zarabia 100 000 USD rocznie. Jaka jest dokładna pensja za jego miesiąc? Używając liczby całkowitej otrzymujesz wynik 8333,33 $ (¢ 833333), który pomnożony przez 12 wynosi 99 999.96 $. Czy utrzymanie go jako liczby całkowitej pomogło? Nie, nie zrobił.

Czy banki zawsze używają wartości dziesiętnych / całkowitych? Cóż, robią to dla części transakcyjnej. Ale na przykład, gdy tylko zaczniesz mówić o bankowości inwestycyjnej, z wyjątkiem śledzenia faktycznych transakcji, wszystko inne jest zmienne. Ponieważ jest to cały wewnętrzny kod, nie zobaczysz go, ale możesz zdobyć szczyt na QuantLib , który jest zasadniczo taki sam (z wyjątkiem znacznie czystszego ;-).

Dlaczego warto korzystać z pływaków? Ponieważ używanie dziesiętnych wcale nie pomaga, gdy używasz funkcji takich jak pierwiastek kwadratowy, logarytmy, potęgi z wykładnikami innymi niż całkowite itp. I oczywiście liczby zmiennoprzecinkowe są znacznie szybsze niż typy dziesiętne.


1
@Job - liczby dziesiętne i zmiennoprzecinkowe są bardzo różne. Państwo może przechowywać 0,1 dokładnie w rodzaju dziesiętnego, ale nie w float lub double.
Scott Whitlock

3
Miałem kolejne pytanie. Jeśli zapłaciłeś $100,000/12i użyłeś float. Dlaczego wynik miałby dokładnie 100 000 $? Dlaczego liczba zmiennoprzecinkowa (lub dziesiętna) nie miałaby zaokrąglać w górę lub w dół za każdym razem, gdy ktoś otrzyma zapłatę? Mówię o tym podczas pisania czeku (nie możesz zrobić 1/2 lub 1/3 centa) lub bezpośredniego depozytu (zakładam, że ma takie same ograniczenia)

@kwas: >>> x = 100000 / 12,0 >>> x * 12
100000.0

ponownie przeczytać mój komentarz? moje pytanie brzmi, kiedy używam oprogramowania do tworzenia czeku co miesiąc. Skoro nie można zapłacić 1/2 centa, w jaki sposób osoba otrzymuje pełną kwotę po roku?

2
@kwas: nie możesz używać dzielenia prostego, niezależnie od tego, czy używasz liczb całkowitych, dziesiętnych czy dzielenia jako liczby zmiennoprzecinkowe, a następnie okrągłe. To jest cały punkt, użycie dziesiętne nie pomaga w tym przypadku.
vartec

4

To, co opisałeś, doskonale sprawdza się w sytuacjach, w których kontrolujesz wszystkie wejścia i wyjścia .

Tak naprawdę nie jest tak. Musisz być w stanie poradzić sobie z systemami, które dostarczają ci ich dane jako pewną rzeczywistą wartość z pewnym stopniem dokładności i będziesz oczekiwać, że zwrócisz dane w tym samym formacie. W takich przypadkach będzie spotkać te problemy.

W rzeczywistości napotkasz te problemy, nawet jeśli użyjesz wymienionych lew. Przy obliczaniu 17,5% podatku od ceny otrzymasz ułamki centów, niezależnie od tego, czy przechowujesz wartość w dolarach, czy centach. Musisz poprawnie zaokrąglić, ponieważ podatnik bardzo się denerwuje, jeśli nie zapłacisz mu wystarczająco dużo. Używanie prawidłowych moneytypów (niezależnie od tego, w jakim języku są używane) uratuje cię przed światem bólu.


Jaki jest rodzaj pieniędzy? (język lub odnośnik) i dlaczego jest to „poprawny” typ? Czy to dlatego, że ... 128 bitów lub więcej? Moje drugie, dlaczego używanie moich „sztuczek” byłoby nieprawidłowe? Masz liczbę całkowitą w centach. Jeśli pomnożysz ją przez .175, otrzymasz liczbę całkowitą i wykorzystasz ją do wszystkiego, czego chcesz. Myśląc o twoim przykładzie, myślę, że float byłby w stanie utrzymać moją wartość z wystarczającą precyzją, ale nie będę musiał się martwić, że 0.3f == 0.3d jest fałszem. -edycja- i +1

1
@ acidzombie24 - Nie miałem na myśli konkretnego typu, ale jakiego typu używasz w swoim języku do reprezentowania wartości pieniężnych. Również jeśli masz 10 centów i pomnożysz przez 0,175, masz 1,75 centów - jak sobie z tym poradzić z arytmetyką liczb całkowitych? Czy to 1 cent czy 2 centy? Pomylisz się, a twój klient może w końcu mieć u podatnika mnóstwo pieniędzy.
ChrisF

Nigdy nie należy pomnożyć 10 (liczba całkowita) przez .175 (liczba rzeczywista / zmiennoprzecinkowa), ponieważ nie należy mieszać liczb dokładnych z liczbami niedokładnymi; wynik będzie niedokładny. Innymi słowy, w systemie dokładnych liczb wartość taka jak .175 nigdy nie istniałaby, a zatem jest to obliczenie niesensowne. Lepszym rozwiązaniem jest pomnożenie 10000 przez 175 i ręczne wstawienie kropki dziesiętnej tam, gdzie jest to właściwe.
Barry Brown

8
@Barry - wiem. Próbowałem zilustrować rodzaj problemu, który masz. Istnieje również wartość taka jak 0,175, jeśli stawka podatku wynosi 17,5% i trzeba obliczyć podatek od towaru, który kosztuje 10 centów.
ChrisF

1
@acidzombie: Prawidłowym typem używanym do pieniędzy jest dziesiętny punkt dziesiętny z wysoką precyzją (co najmniej 4 miejsca dziesiętne). Żadnych jeśli, i albo ale. Przechowywanie wartości pieniężnych jako centów nie jest wystarczające, ponieważ w praktyce daje tylko dwa punkty precyzji.
Aaronaught

3

„Bóg stworzył liczby całkowite, wszystko inne jest dziełem człowieka”. - Leopold Kronecker (1886).

Z definicji nie potrzebujesz żadnych innych rodzajów liczb. Kompletność Turinga dla języka programowania opiera się na prostych relacjach między różnymi rodzajami liczb. Jeśli możesz pracować z liczbami całkowitymi (a / k / a liczby naturalne), możesz zrobić wszystko.

Pytanie jest dość podstępne, ponieważ ich nie potrzebujesz . Może chcesz miejsca, w których jest to wygodne lub optymalne, tańsze czy coś takiego?


7
Możemy również zrezygnować z liczb całkowitych, ponieważ można je również konstruować, używając tylko operacji teorii zbiorów i pustego zbioru. Ale zarówno to, jak i argumentacja z kompletności Turinga, doprowadziły do ​​skrajności akademickiego redukcjonizmu.
Bob Murphy

4
Ponadto kompletność Turinga dotyczy tylko komputerów. Ani liczby całkowite, ani nawet racjonalne nie są matematycznie kompletne, ponieważ żadna z nich nie jest zamknięta na zbieżność sekwencji Cauchy'ego. Więc Kronecker był pełen gorącego powietrza: jeśli chcesz mieć pełną przestrzeń metryczną obejmującą liczby całkowite, musisz uzyskać prawdziwe: xkcd.com/849
Bob Murphy

1
@Bob Murphy: „akademicki redukcjonizm doprowadzony do skrajności”. Dokładnie. Pytanie jest słabe i prowadzi do jak najbardziej możliwej odpowiedzi.
S.Lott

2

W zdaniu, zmiennoprzecinkowe typy dziesiętne hermetyzują konwersję do i od wartości całkowitych (czyli tyle, ile komputer wie, jak sobie radzić na poziomie binarnym; w systemie binarnym nie ma przecinka dziesiętnego), zapewniając logiczną, ogólnie łatwą do zrozumieć interfejs do obliczania liczb dziesiętnych.

Szczerze mówiąc, mówienie, że nie potrzebujesz liczb zmiennoprzecinkowych, ponieważ wiesz, jak robić matematy dziesiętne za pomocą liczb całkowitych, jest jak mówienie, że wiesz, jak robić arytmetykę odręcznie. Więc znasz tę koncepcję; Brawo. Nie oznacza to, że musisz cały czas ćwiczyć tę wiedzę. Często jest to szybsze, tańsze i bardziej zrozumiałe dla non-binarnego śwista, mówiąc po prostu 3,5 + 4,6 = 8,1 zamiast konwertowania liczb sig na liczbę całkowitą.


1

Podstawową zaletą typów zmiennoprzecinkowych jest to, że z perspektywy czasu, dwa lub trzy formaty (chciałbym, żeby więcej obsługiwanych formatów 80-bitowych) wystarczały do ​​szybkiej większości celów obliczeniowych. Jeśli języki programowania mogłyby z łatwością obsługiwać rodzinę typów stałoprzecinkowych, złożoność sprzętowa wymagana dla danego poziomu wydajności byłaby często niższa w przypadku typów stałoprzecinkowych niż zmiennoprzecinkowych. Niestety zapewnienie takiego wsparcia nie jest wcale „łatwe”.

Aby język programowania skutecznie zaspokajał 98% potrzeb numerycznych aplikacji, musiałby zawierać dziesiątki typów i definiować operacje dla setek kombinacji; ponadto, nawet jeśli język programowania miałby wspaniałe wsparcie dla punktu stałego, niektóre aplikacje nadal musiałyby utrzymać z grubsza stałą względną precyzję w wystarczająco dużym zakresie, aby wymagać zmiennoprzecinkowego punktu. Biorąc pod uwagę, że matematyka zmiennoprzecinkowa będzie w niektórych przypadkach niezbędna, dostawcy sprzętu skoncentrują się na wydajności matematycznej z dwoma lub trzema formatami zmiennoprzecinkowymi, a kod wykorzysta te formaty, gdy tylko będą one działać dobrze, na ogół osiągają lepsze „huk za grosze” niż próba zoptymalizowania zachowania matematyki w punkcie stałym.

Nawiasem mówiąc, matematyka stałoprzecinkowa była bardziej korzystna z procesorami 8-bitowymi i 16-bitowymi niż z procesorami 32-bitowymi. Na 8-bitowym procesorze, w sytuacji, gdy 32 bity nie wystarczyłyby, typ 40-bitowy kosztowałby tylko o 25% więcej miejsca i 25-50% więcej czasu niż typ 32-bitowy i wymagałby 37,5% mniej miejsca i 37,5-60% mniej czasu niż typ 64-bitowy. Na platformie 32-bitowej, jeśli typ 32-bitowy nie wystarczy do czegoś, często nie ma powodu, aby używać mniej niż 64 bitów. Jeśli 48-bitowy typ punktu stałego byłby odpowiedni, 64-bitowy „podwójny” będzie działał tak samo dobrze, jak typ punktu stałego.


0

Ogólnie powinieneś bardzo ostrożnie z nich korzystać. Zrozumienie utraty precyzji, która może wynikać nawet z prostych obliczeń, stanowi wyzwanie. Na przykład uśrednianie takiej liczby liczb jest bardzo złym pomysłem:

double average(List<Double> data) {
  double ans = 0;
  for(Double d : data) {
    ans += d;
  }
  return ans / data.size();
}

Powodem jest to, że w przypadku wystarczająco dużych list zasadniczo tracisz wszystkie punkty danych, gdy ansstają się wystarczająco duże (patrz np. To ). Problem z tym kodem polega na tym, że w przypadku małych list prawdopodobnie będzie po prostu działał --- łamie się tylko w skali.

Osobiście uważam, że powinieneś ich używać tylko wtedy, gdy: a) obliczenia naprawdę muszą być szybkie; b) nie obchodzi cię, że wynik będzie prawdopodobnie bardzo odległy (chyba że naprawdę wiesz, co robisz).


-1

Jedną z myśli jest to, że użyłbyś reprezentacji zmiennoprzecinkowych lub podwójnych, gdy musisz poradzić sobie z wartościami spoza zakresu liczb całkowitych.

Dzisiejsze architektury (z grubsza) mają podpisany zakres liczb całkowitych +/- 2 147 483 647 (32 bity) lub +/- 9 223 372,036,854,775,807 (64 bity). Unsigned przedłuża to o współczynnik 2.

Liczba zmiennoprzecinkowa IEEE 754 (z grubsza) wynosi od +/- 1,4 × 10 ^ −45 do 3,4 × 10 ^ 38. Podwójnie rozszerza ten zakres do +/- 5 × 10-324 ± 2.225 × 10 ^ −308 z pominięciem wielu warunków i szczegółów.

Oczywiście najbardziej zadziwiającym powodem jest konieczność przedstawienia -0 ;-)


Liczby pochodzą głównie z artykułów z Wikipedii i mają charakter poglądowy. Z wyjątkiem -0, to tylko dla zabawy.
Stephen

Problem polega na tym, że istnieje wiele liczb całkowitych w tym ogromnym zakresie, które w ogóle nie są reprezentowane.
Barry Brown

@BarryBrown Absolutnie racja. „pominięto wiele warunków i szczegółów”.
Stephen

-1

Zwykle powodem jest to, że są one szybkie, ponieważ JVM zazwyczaj korzysta z podstawowej obsługi sprzętu (chyba że używasz strictfp).

Zobacz https://stackoverflow.com/questions/517915/when-to-use-strictfp-keyword-in-java, aby dowiedzieć się, co implikuje strictfp.


Matematyka zmiennoprzecinkowa jest szybsza niż matematyka liczb całkowitych? Na jakim procesorze obliczenia zmiennoprzecinkowe zajmują mniej cykli niż obliczenia w liczbach całkowitych?
this.josh

1
@ this.josh, silnie zależy od liczby cyfr, które masz w swoich liczbach. Również liczby całkowite nie mogą dzielić dokładnie, co może, ale nie musi być ważne.

-2

Dlatego potrzebujemy 256-bitowych systemów operacyjnych.

Długość deski (najmniejsza odległość, którą można zmierzyć) = 10 ^ -35 m
Obserwowalny wszechświat ma 14 miliardów parsów = 10 ^ 25m,
więc możesz zmierzyć wszystko w jednostkach długości deski jako liczby całkowite, jeśli masz tylko 200 bitów precyzji.


2
-1: co jeśli symulujesz rzeczy w skali większej niż obserwowalny wszechświat?
amara,

2
@sparkleshy, do tego służą wskaźniki FAR!
Martin Beckett,
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.