Mam następujący fałszywy skrypt testowy:
function test() {
var x = 0.1 * 0.2;
document.write(x);
}
test();
Spowoduje to wydrukowanie wyniku, 0.020000000000000004
podczas gdy powinien po prostu wydrukować 0.02
(jeśli korzystasz z kalkulatora). O ile rozumiem, wynika to z błędów w precyzji mnożenia zmiennoprzecinkowego.
Czy ktoś ma dobre rozwiązanie, aby w takim przypadku uzyskać prawidłowy wynik 0.02
? Wiem, że istnieją funkcje takie jak toFixed
lub zaokrąglanie byłoby inną możliwością, ale chciałbym naprawdę wydrukować cały numer bez żadnego cięcia i zaokrąglania. Chciałem tylko wiedzieć, czy któryś z was ma jakieś fajne, eleganckie rozwiązanie.
Oczywiście, w przeciwnym razie zaokrąglę do około 10 cyfr.
0.1
na skończoną binarną liczbę zmiennoprzecinkową.Odpowiedzi:
Z przewodnika zmiennoprzecinkowego :
Zauważ, że pierwszy punkt ma zastosowanie tylko wtedy, gdy naprawdę potrzebujesz określonego precyzyjnego zachowania po przecinku . Większość ludzi tego nie potrzebuje, są po prostu zirytowani, że ich programy nie działają poprawnie z liczbami takimi jak 1/10, nie zdając sobie sprawy, że nawet nie mrugną z powodu tego samego błędu, jeśli wystąpiłby z 1/3.
Jeśli pierwszy punkt naprawdę dotyczy Ciebie, użyj BigDecimal dla JavaScript , który wcale nie jest elegancki, ale faktycznie rozwiązuje problem, a nie zapewnia niedoskonałego obejścia.
źródło
console.log(9332654729891549)
faktycznie drukuje9332654729891548
(tzn. Wyłącza jeden!);P
... Pomiędzy2⁵²
=4,503,599,627,370,496
i2⁵³
=9,007,199,254,740,992
reprezentowalne liczby są dokładnie liczbami całkowitymi . Na następnym przedziale od2⁵³
do2⁵⁴
, wszystko jest mnożona przez2
, więc zakodowania numery są nawet te , itd. Z drugiej strony, na poprzednim przedziale od2⁵¹
do2⁵²
, odstęp jest0.5
, itd. Wynika to po prostu zwiększając | zmniejszenie podstawy | radix 2 | wykładnik binarny w / z 64-bitowej wartości zmiennoprzecinkowej (co z kolei wyjaśnia rzadko udokumentowane „nieoczekiwane” zachowanietoPrecision()
wartości pomiędzy0
i1
).Lubię rozwiązanie Pedro Ladarii i używam czegoś podobnego.
W przeciwieństwie do rozwiązania Pedros zaokrągli to w górę 0,999 ... powtarzanie i jest dokładne do plus / minus jeden na najmniej znaczącej cyfrze.
Uwaga: W przypadku 32- lub 64-bitowych liczb zmiennoprzecinkowych należy użyć toPrecision (7) i toPrecision (15), aby uzyskać najlepsze wyniki. Zobacz to pytanie, aby dowiedzieć się, dlaczego.
źródło
toPrecision
zwraca ciąg zamiast liczby. Nie zawsze może to być pożądane.(9.99*5).toPrecision(2)
= 50 zamiast 49,95, ponieważ toPrecision liczy liczbę całkowitą, a nie tylko liczby dziesiętne. Możesz wtedy użyćtoPrecision(4)
, ale jeśli wynik wynosi> 100, znów nie masz szczęścia, ponieważ pozwoli to na trzy pierwsze cyfry i jedną dziesiętną, przesuwając kropkę i czyniąc to mniej lub bardziej bezużytecznym. Skończyło się stosująctoFixed(2)
zamiastDla matematycznie skłonnych: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
Zalecanym podejściem jest zastosowanie współczynników korekcyjnych (pomnóż przez odpowiednią moc 10, aby arytmetyka zachodzi między liczbami całkowitymi). Na przykład w przypadku
0.1 * 0.2
współczynnika korekcji jest10
i wykonujesz obliczenia:(Bardzo szybkie) rozwiązanie wygląda mniej więcej tak:
W tym przypadku:
Zdecydowanie polecam użycie przetestowanej biblioteki, takiej jak SinfulJS
źródło
Czy wykonujesz tylko mnożenie? Jeśli tak, to możesz wykorzystać na swoją korzyść czysty sekret dotyczący arytmetyki dziesiętnej. To jest to
NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals
. To znaczy, że jeśli tak,0.123 * 0.12
to wiemy, że będzie 5 miejsc po przecinku, ponieważ0.123
ma 3 miejsca po przecinku i0.12
ma dwa. Zatem jeśli JavaScript dał nam liczbę taką,0.014760000002
że możemy bezpiecznie zaokrąglić do 5. miejsca po przecinku bez obawy o utratę precyzji.źródło
Szukasz
sprintf
implementacji dla JavaScript, abyś mógł wypisywać zmiennoprzecinkowe z niewielkimi błędami (ponieważ są przechowywane w formacie binarnym) w oczekiwanym formacie.Spróbuj javascript-sprintf , nazwałbyś to tak:
wydrukować liczbę jako liczbę zmiennoprzecinkową z dwoma miejscami po przecinku.
Możesz także użyć Number.toFixed () do celów wyświetlania, jeśli wolisz nie dołączać więcej plików tylko do zaokrąglania zmiennoprzecinkowego z określoną precyzją.
źródło
Uważam, że plik BigNumber.js spełnia moje potrzeby.
Ma dobrą dokumentację, a autor bardzo sumiennie odpowiada na opinie.
Ten sam autor ma 2 inne podobne biblioteki:
Big.js
i Decimal.js
Oto kod za pomocą BigNumber:
źródło
---lub---
---również---
--- jak w ---
źródło
Ta funkcja określi potrzebną precyzję na podstawie mnożenia dwóch liczb zmiennoprzecinkowych i zwróci wynik z odpowiednią precyzją. Elegancki nie jest.
źródło
Co zaskakujące, ta funkcja nie została jeszcze opublikowana, chociaż inni mają podobne odmiany. Pochodzi z dokumentów internetowych MDN dla Math.round (). Jest zwięzły i pozwala na różną precyzję.
console.log (precisionRound (1234.5678, 1)); // oczekiwany wynik: 1234,6
console.log (precisionRound (1234.5678, -1)); // oczekiwany wynik: 1230
AKTUALIZACJA: 20 sierpnia 2019 Właśnie zauważyłem ten błąd. Uważam, że jest to spowodowane błędem precyzji zmiennoprzecinkowej w Math.round ().
Te warunki działają poprawnie:
Naprawić:
To dodaje cyfrę po prawej stronie podczas zaokrąglania miejsc po przecinku. MDN zaktualizował stronę Math.round, więc może ktoś mógłby zaoferować lepsze rozwiązanie.
źródło
Musisz tylko zdecydować, ile cyfr dziesiętnych rzeczywiście chcesz - nie możesz mieć ciasta i też go zjeść :-)
Błędy numeryczne kumulują się z każdą kolejną operacją, a jeśli nie odetniesz jej wcześnie, po prostu będzie rosła. Biblioteki numeryczne, które prezentują wyniki, które wyglądają na czyste, po prostu odcinają ostatnie 2 cyfry na każdym kroku, koprocesory numeryczne mają również „normalną” i „pełną” długość z tego samego powodu. Cuf-offy są tanie dla procesora, ale bardzo drogie dla ciebie w skrypcie (mnożenie, dzielenie i używanie pov (...)). Dobra biblioteka matematyczna zapewniłaby podłogę (x, n) do wykonania odcięcia dla ciebie.
Przynajmniej powinieneś zrobić globalną zmienną / stałą za pomocą pov (10, n) - co oznacza, że zdecydowałeś się na potrzebną precyzję :-) Następnie wykonaj:
Możesz także dalej robić matematykę i tylko odcinać na końcu - zakładając, że wyświetlasz tylko i nie robisz if-ów z wynikami. Jeśli możesz to zrobić, to .toFixed (...) może być bardziej wydajny.
Jeśli robisz porównania if-s / i nie chcesz wycinać, potrzebujesz również małej stałej, zwykle nazywanej eps, która jest o jedno miejsce po przecinku wyższa niż maksymalny oczekiwany błąd. Powiedz, że wartość odcięcia wynosi ostatnie dwa miejsca po przecinku - wtedy twój eps ma 1 na 3 miejscu od ostatniego (3 najmniej znaczący) i możesz go użyć do porównania, czy wynik mieści się w oczekiwanym zakresie eps (0,02 -eps <0,1 * 0,2 <0,02 + eps).
źródło
Math.floor(-2.1)
jest-3
. Więc może użyj np.Math[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
floor
zamiastround
?Możesz użyć,
parseFloat()
atoFixed()
jeśli chcesz ominąć ten problem w przypadku małej operacji:źródło
Funkcja round () w phpjs.org działa dobrze: http://phpjs.org/functions/round
źródło
0.6 * 3 to niesamowite!)) Dla mnie to działa dobrze:
Bardzo, bardzo proste))
źródło
8.22e-8 * 1.3
?Zauważ, że w przypadku ogólnego zastosowania takie zachowanie może być do zaakceptowania.
Problem pojawia się podczas porównywania wartości zmiennoprzecinkowych w celu ustalenia odpowiedniego działania.
Wraz z pojawieniem się ES6
Number.EPSILON
definiowana jest nowa stała w celu ustalenia dopuszczalnego marginesu błędu:Zamiast przeprowadzać takie porównanie
możesz zdefiniować niestandardową funkcję porównywania, taką jak ta:
Źródło: http://2ality.com/2015/04/numbers-math-es6.html#numberepsilon
źródło
0.9 !== 0.8999999761581421
Otrzymany wynik jest poprawny i dość spójny we wszystkich implementacjach zmiennoprzecinkowych w różnych językach, procesorach i systemach operacyjnych - jedyną rzeczą, która się zmienia, jest poziom niedokładności, gdy liczba zmiennoprzecinkowa jest w rzeczywistości podwójna (lub wyższa).
0,1 w binarnych zmiennoprzecinkowych jest jak 1/3 w systemie dziesiętnym (tj. 0,33333333333333 ... na zawsze), po prostu nie ma dokładnego sposobu na to.
Jeśli masz do czynienia z liczbami zmiennoprzecinkowymi, zawsze oczekuj małych błędów zaokrąglania, więc zawsze będziesz musiał zaokrąglić wyświetlany wynik do czegoś sensownego. W zamian otrzymujesz bardzo szybką i wydajną arytmetykę, ponieważ wszystkie obliczenia są w natywnym pliku binarnym procesora.
W większości przypadków rozwiązaniem nie jest przejście na arytmetykę w punktach stałych, głównie dlatego, że jest znacznie wolniejsza i 99% czasu po prostu nie potrzebujesz dokładności. Jeśli masz do czynienia z rzeczami, które wymagają takiego poziomu dokładności (na przykład transakcje finansowe), JavaScript prawdopodobnie nie jest najlepszym narzędziem do użycia (ponieważ chcesz wymusić typy stałoprzecinkowe, język statyczny jest prawdopodobnie lepszy ).
Szukasz eleganckiego rozwiązania, ale obawiam się, że to jest to: liczby zmiennoprzecinkowe są szybkie, ale mają małe błędy zaokrąglania - zawsze wyświetlają wyniki zaokrąglone do rozsądnych wyników.
źródło
Aby tego uniknąć, powinieneś pracować z wartościami całkowitymi zamiast zmiennoprzecinkowymi. Więc jeśli chcesz mieć 2 pozycje precyzji, pracuj z wartościami * 100, dla 3 pozycji użyj 1000. Podczas wyświetlania użyj formatyzatora, aby wstawić separator.
Wiele systemów pomija pracę z miejscami po przecinku w ten sposób. To jest powód, dla którego wiele systemów pracuje z centami (jako liczbami całkowitymi) zamiast dolarów / euro (jako zmiennoprzecinkowe).
źródło
Problem
Zmienny punkt nie może dokładnie przechowywać wszystkich wartości dziesiętnych. Tak więc przy stosowaniu formatów zmiennoprzecinkowych zawsze wystąpią błędy zaokrąglania wartości wejściowych. Błędy na wejściach oczywiście skutkują błędami na wyjściu. W przypadku funkcji dyskretnej lub operatora mogą występować duże różnice na wyjściu wokół punktu, w którym funkcja lub operator są dyskretne.
Dane wejściowe i wyjściowe dla wartości zmiennoprzecinkowych
Tak więc, używając zmiennych zmiennoprzecinkowych, zawsze powinieneś być tego świadomy. I cokolwiek chcesz uzyskać z obliczeń z zmiennoprzecinkowymi, zawsze należy sformatować / uwarunkować przed wyświetleniem z tego względu.
Gdy używane są tylko funkcje ciągłe i operatory, często wystarczą zaokrąglenia do pożądanej precyzji (nie obcinaj). Standardowe funkcje formatowania używane do konwertowania liczb zmiennoprzecinkowych na ciąg zwykle to robią za Ciebie.
Ponieważ zaokrąglanie dodaje błąd, który może spowodować, że całkowity błąd będzie większy niż połowa pożądanej precyzji, dane wyjściowe należy skorygować na podstawie oczekiwanej dokładności danych wejściowych i pożądanej dokładności danych wyjściowych. Powinieneś
Te dwie rzeczy zwykle nie są wykonywane, a w większości przypadków różnice spowodowane ich nie wykonaniem są zbyt małe, aby były ważne dla większości użytkowników, ale miałem już projekt, w którym wyniki nie byłyby akceptowane przez użytkowników bez tych poprawek.
Funkcje dyskretne lub operatory (jak moduł)
Gdy zaangażowane są dyskretne operatory lub funkcje, mogą być wymagane dodatkowe poprawki, aby upewnić się, że wynik jest zgodny z oczekiwaniami. Zaokrąglanie i dodawanie drobnych poprawek przed zaokrąglaniem nie może rozwiązać problemu.
Może być wymagana specjalna kontrola / korekta wyników obliczeń pośrednich, natychmiast po zastosowaniu funkcji dyskretnej lub operatora. W przypadku konkretnego przypadku (operator modułu) zobacz moją odpowiedź na pytanie: Dlaczego operator modułu zwraca liczbę ułamkową w javascript?
Lepiej unikaj problemu
Często bardziej efektywnym jest uniknięcie tych problemów poprzez zastosowanie typów danych (format całkowity lub stały punkt) do obliczeń takich jak ten, które mogą przechowywać oczekiwane dane wejściowe bez błędów zaokrąglania. Przykładem tego jest to, że nigdy nie należy używać wartości zmiennoprzecinkowych do obliczeń finansowych.
źródło
Spójrz na arytmetykę punktów stałych . Prawdopodobnie rozwiąże Twój problem, jeśli zakres liczb, na których chcesz operować, jest niewielki (np. Waluta). Zaokrąglibym to do kilku wartości dziesiętnych, co jest najprostszym rozwiązaniem.
źródło
Wypróbuj moją chiliadyczną bibliotekę arytmetyczną, którą możesz zobaczyć tutaj . Jeśli chcesz późniejszą wersję, mogę ci ją zdobyć.
źródło
Nie można dokładnie reprezentować ułamków dziesiętnych dokładnie za pomocą binarnych typów zmiennoprzecinkowych (takiego właśnie używa ECMAScript do reprezentowania wartości zmiennoprzecinkowych). Nie ma więc eleganckiego rozwiązania, chyba że użyjesz dowolnych typów arytmetycznych o precyzji lub typu zmiennoprzecinkowego opartego na liczbach dziesiętnych. Na przykład aplikacja Kalkulator dostarczana z systemem Windows wykorzystuje teraz arytmetykę o dowolnej precyzji do rozwiązania tego problemu .
źródło
źródło
Masz rację, przyczyną tego jest ograniczona precyzja liczb zmiennoprzecinkowych. Przechowuj swoje liczby wymierne jako podział na dwie liczby całkowite, aw większości sytuacji będziesz mógł przechowywać liczby bez utraty precyzji. Jeśli chodzi o drukowanie, możesz chcieć wyświetlić wynik jako ułamek. Dzięki reprezentacji, którą zaproponowałem, staje się to banalne.
Oczywiście nie pomoże to w przypadku liczb nieracjonalnych. Ale możesz zoptymalizować swoje obliczenia w taki sposób, aby powodowały najmniejszy problem (np. Wykrywanie takich sytuacji
sqrt(3)^2)
.źródło
<pedant>
rzeczywistości OP przypisuje to nieprecyzyjnym operacjom liczb zmiennoprzecinkowych, co jest błędne</pedant>
Miałem paskudny problem z błędem zaokrąglenia w przypadku mod 3. Czasami, gdy powinienem dostać 0, dostaję .000 ... 01. To dość łatwe w obsłudze, wystarczy przetestować dla <= .01. Ale czasami dostawałem 2.9999999999999998. AUĆ!
BigNumbers rozwiązał problem, ale wprowadził inny, nieco ironiczny problem. Próbując załadować 8,5 do BigNumbers, zostałem poinformowany, że to naprawdę 8.4999… i miał więcej niż 15 cyfr znaczących. Oznaczało to, że BigNumbers nie mógł tego zaakceptować (myślę, że wspominałem, że ten problem był nieco ironiczny).
Proste rozwiązanie ironicznego problemu:
źródło
Użyj numeru (1.234443) .to Naprawiono (2); wydrukuje 1,23
źródło
decimal.js , big.js lub bignumber.js mogą być używane w celu uniknięcia problemów z obsługą zmiennoprzecinkowych w Javascript:
link do szczegółowych porównań
źródło
Elegancki, przewidywalny i wielokrotnego użytku
Zajmijmy się tym problemem w elegancki sposób, który można ponownie wykorzystać. Poniższe siedem wierszy pozwoli ci uzyskać dostęp do żądanej precyzji zmiennoprzecinkowej na dowolnej liczbie, po prostu dołączając
.decimal
na końcu liczby, formuły lub wbudowanejMath
funkcji.Twoje zdrowie!
źródło
Posługiwać się
źródło
Math.round(x*Math.pow(10,4))/Math.pow(10,4);
. Zaokrąglanie jest zawsze opcją, ale chciałem tylko wiedzieć, czy istnieje jakieś lepsze rozwiązanienie elegancki, ale spełnia swoje zadanie (usuwa końcowe zera)
źródło
To działa dla mnie:
źródło
Dane wyjściowe przy użyciu następującej funkcji:
Zwróć uwagę na wynik
toFixedCurrency(x)
.źródło