Zaokrąglanie waluty w aplikacjach finansowych: przechowuj pieniądze bezpiecznie
Zaokrąglanie waluty w aplikacjach finansowych może powodować błędy o jeden cent. Dowiedz się o przechowywaniu w centach, regułach zaokrąglania podatków i spójnym wyświetlaniu na web i mobile.

Dlaczego pojawiają się błędy o jeden cent
Błąd o jeden cent to taki, który użytkownicy zauważają od razu. Suma w koszyku pokazuje $19.99 na liście produktów, a przy kasie robi się $20.00. Zwrot za $14.38 trafia jako $14.37. Na fakturze pojawia się „Podatek: $1.45”, a jednak suma końcowa wygląda, jakby zastosowano inny sposób liczenia podatku.
Takie problemy zwykle wynikają z małych różnic w zaokrąglaniu, które się kumulują. Pieniądze to nie tylko „liczba”. Mają reguły: ile miejsc dziesiętnych ma waluta, kiedy zaokrąglać i czy zaokrąglać per pozycja czy dopiero dla sumy końcowej. Jeśli w różnych miejscach aplikacji przyjmiesz inną konwencję, pojedynczy cent może zniknąć lub się pojawić.
Te błędy pojawiają się często tylko czasami, co utrudnia debugowanie. Te same dane wejściowe mogą dać różne centy w zależności od urządzenia lub ustawień lokalnych, kolejności operacji albo sposobu konwersji typów.
Częste przyczyny to obliczenia na float i zaokrąglanie „na końcu” (ale „koniec” nie znaczy to samo wszędzie), naliczanie podatku per pozycja na jednym ekranie, a od podsumowania na innym, mieszanie walut lub kursów i zaokrąglanie w niespójnych krokach, albo formatowanie wartości do wyświetlenia i przypadkowe ponowne parsowanie ich jako liczb.
Największe szkody występują tam, gdzie zaufanie jest kruche i kwoty są audytowane: sumy przy kasie, zwroty, faktury, subskrypcje, napiwki, wypłaty i raporty wydatków. Różnica jednego centa może wywołać problemy płatnicze, utrudnienia w rozliczeniach i zgłoszenia do supportu "wasza aplikacja mnie okrada".
Cel jest prosty: te same wejścia powinny dawać te same centy wszędzie. Te same pozycje, ten sam podatek, te same rabaty, ta sama reguła zaokrąglania — niezależnie od ekranu, urządzenia, języka czy eksportu.
Przykład: jeśli dwa produkty po $9.99 mają 7.25% podatku, zdecyduj, czy zaokrąglasz podatek per pozycja, czy od sumy, a potem stosuj to samo w backendzie, UI web i aplikacji mobilnej. Konsekwencja zapobiega pytaniu „dlaczego tu jest inaczej?”.
Dlaczego float jest ryzykowny dla pieniędzy
Większość języków przechowuje float i double w postaci binarnej. Wiele dziesiętnych cen nie da się dokładnie zapisać w binarnym float, więc liczba, którą myślisz, że zapisałeś, jest często minimalnie wyższa lub niższa.
Klasyczny przykład to 0.1 + 0.2. W wielu systemach daje 0.30000000000000004. To wygląda niewinnie, ale logika finansowa to zwykle łańcuch: ceny pozycji, rabaty, podatek, opłaty, a potem ostateczne zaokrąglenie. Małe błędy mogą zmienić decyzję o zaokrągleniu i stworzyć różnicę jednego centa.
Objawy, które ludzie zauważają, gdy zaokrąglanie pieniędzy idzie źle:
- W logach lub odpowiedziach API pojawiają się wartości typu 9.989999 lub 19.9000001.
- Summy dryfują po dodaniu wielu pozycji, nawet jeśli każda pozycja wygląda poprawnie.
- Kwota zwrotu nie zgadza się z oryginalnym obciążeniem o $0.01.
- Ta sama suma koszyka różni się między webem, mobile i backendem.
Formatowanie często ukrywa problem. Jeśli wydrukujesz 9.989999 z dwoma miejscami, pokaże 9.99, więc wszystko wydaje się poprawne. Błąd pojawia się później, gdy sumujesz wiele wartości, porównujesz sumy lub zaokrąglasz po podatku. Dlatego zespoły czasem wypuszczają taką funkcję i odkrywają problem dopiero przy uzgadnianiu z operatorami płatności lub eksportach księgowych.
Prosta zasada: nie przechowuj ani nie sumuj pieniędzy jako liczb zmiennoprzecinkowych. Traktuj pieniądze jako całkowitą liczbę jednostek mniejszych waluty (np. centy) albo użyj typu dziesiętnego gwarantującego dokładną arytmetykę dziesiętną.
Jeśli budujesz backend, aplikację web lub mobilną (w tym za pomocą platformy no-code jak AppMaster), stosuj tę samą zasadę wszędzie: przechowuj dokładne wartości, licz na dokładnych wartościach i formatuj dopiero na końcu.
Wybierz model pieniędzy dopasowany do rzeczywistych walut
Większość błędów zaczyna się zanim cokolwiek policzysz: model danych nie odpowiada rzeczywistemu działaniu waluty. Zrób model poprawnie od początku, a zaokrąglanie stanie się problemem reguł, nie zgadywaniem.
Najbezpieczniejszym domyślnym podejściem jest przechowywać pieniądze jako integer w jednostce mniejszej waluty. Dla USD to centy; dla EUR centy euro. Baza i kod radzą sobie z dokładnymi integerami, a „dodajesz przecinki” tylko przy formatowaniu dla ludzi.
Nie każda waluta ma 2 miejsca dziesiętne, więc twój model musi być świadomy waluty. JPY ma 0 miejsc (1 yen to najmniejsza jednostka). BHD często używa 3 miejsc (1 dinar = 1000 fils). Jeśli na stałe ustawisz „dwa miejsca wszędzie”, będziesz cicho zbyt dużo lub za mało obciążać.
Praktyczny rekord pieniędzy zwykle potrzebuje:
amount_minor(integer, np. 1999 dla $19.99)currency_code(string jak USD, EUR, JPY)- opcjonalnie
minor_unitlubscale(0, 2, 3), jeśli system nie może tego niezawodnie pobrać
Przechowuj kod waluty przy każdej kwocie, nawet w tej samej tabeli. Zapobiega to pomyłkom, gdy później dodasz ceny w wielu walutach, zwroty czy raporty.
Zdecyduj też, gdzie zaokrąglanie jest dozwolone, a gdzie zabronione. Dobra polityka to: nie zaokrąglaj wewnątrz wewnętrznych sum, przydziałów, ksiąg czy konwersji w toku; zaokrąglaj tylko na określonych granicach (np. krok podatku, krok rabatu, wiersz faktury); i zawsze zapisuj używany tryb zaokrąglania (half up, half even, zaokrąglanie w dół), aby wyniki były odtwarzalne.
Krok po kroku: implementacja pieniędzy jako jednostek mniejszych
Jeśli chcesz mniej niespodzianek, wybierz jedną wewnętrzną reprezentację i jej nie łam: przechowuj kwoty jako integer w jednostce mniejszej waluty (często centy).
To oznacza, że $10.99 staje się 1099 z walutą USD. Dla walut bez jednostki mniejszej jak JPY, 1 500 yen to 1500.
Prosta ścieżka implementacji, która skaluje wraz z rozwojem aplikacji:
- Baza danych: przechowaj
amount_minorjako 64-bitowy integer plus kod waluty (np.USD,EUR,JPY). Nazwij kolumnę jasno, żeby nikt nie pomylił jej z dziesiętnym polem. - Kontrakt API: wysyłaj i odbieraj
{ amount_minor: 1099, currency: "USD" }. Unikaj sformatowanych łańcuchów jak "$10.99" i unikaj floatów w JSON. - Wejście w UI: traktuj to, co wpisuje użytkownik, jako tekst, nie liczbę. Normalizuj (usuń spacje, zaakceptuj jeden separator dziesiętny), a potem konwertuj używając liczby miejsc jednostki mniejszej waluty.
- Wszystkie obliczenia na integerach: sumy, składniki pozycji, rabaty, opłaty i podatki powinny działać tylko na integerach. Zdefiniuj reguły, np. „rabat procentowy oblicza się, a następnie zaokrągla do jednostek mniejszych” i stosuj je zawsze tak samo.
- Formatowanie tylko na końcu: gdy pokazujesz pieniądze, konwertuj
amount_minordo łańcucha wyświetlającego zgodnie z lokalizacją i regułami waluty. Nigdy nie parsuj własnego sformatowanego wyjścia z powrotem do liczb.
Praktyczny przykład parsowania: dla USD weź "12.3" i potraktuj jako "12.30" przed konwersją do 1230. Dla JPY odrzuć miejsca dziesiętne z góry.
Reguły zaokrąglania podatków, rabatów i opłat
Większość sporów o jeden cent to nie błędy matematyczne, a błędy polityki. Dwa systemy mogą być poprawne i nadal się nie zgadzać, jeśli zaokrąglają w różnych momentach.
Spisz swoją politykę zaokrąglania i używaj jej wszędzie: w obliczeniach, paragonach, eksportach i zwrotach. Powszechne wybory to zaokrąglanie half-up (0.5 w górę) i half-even (0.5 do najbliższego parzystego centa). Niektóre opłaty wymagają zawsze w górę (ceiling), żeby nigdy nie zaniżać kwoty.
Summy zwykle zmieniają się w zależności od decyzji: czy zaokrąglasz per pozycja czy per faktura, czy mieszane reguły (np. podatek per pozycja, ale opłaty od faktury), oraz czy ceny zawierają podatek (odwrotne obliczenie netto i podatku) czy są bez podatku (liczysz podatek z netto).
Rabat to kolejny rozwidlenie. „10% zniżki” zastosowana przed podatkiem zmniejsza podstawę opodatkowania, podczas gdy rabat po podatku zmniejsza to, co klient płaci, ale może nie zmienić wykazanego podatku, w zależności od jurysdykcji i umowy.
Mały przykład pokazuje, dlaczego surowe reguły są ważne. Dwie pozycje po $9.99, podatek 7.5%. Jeśli zaokrąglasz podatek per pozycja, każdy wiersz ma $0.75 (9.99 x 0.075 = 0.74925). Podatek łącznie = $1.50. Jeśli liczysz podatek od sumy faktury, podatek też wynosi $1.50 dla tych cen, ale zmień trochę ceny i zobaczysz różnicę 1 cent.
Napisz regułę prostym językiem, żeby support i księgowość mogli ją wytłumaczyć. Potem użyj tego samego helpera dla podatków, opłat, rabatów i zwrotów.
Konwersja walut bez dryftu sum
Matematyka wielowalutowa to miejsce, gdzie małe decyzje zaokrągleniowe potrafią powoli zmieniać sumy. Cel jest prosty: konwertuj raz, zaokrąglaj celowo i zachowaj pierwotne dane na później.
Przechowuj kursy wymiany z wyraźną precyzją. Częsty wzorzec to skalar integer, np. "rate_micro", gdzie 1.234567 przechowywane jest jako 1234567 ze skale 1_000_000. Inną opcją jest fixed decimal, ale i tak zapisz scale w polach, żeby nie dało się go zgadnąć.
Wybierz walutę bazową do raportów i księgowości (często waluta firmy). Konwertuj przychodzące kwoty na walutę bazową dla ksiąg i analiz, ale zachowaj obok oryginalną walutę i kwotę. W ten sposób zawsze wyjaśnisz każdą liczbę później.
Reguły zapobiegające dryftowi:
- Konwertuj jednokierunkowo dla księgowości (z obcej do bazowej) i unikaj konwersji tam i z powrotem.
- Zdecyduj kiedy zaokrąglać: per pozycja gdy musisz pokazać sumy pozycji, albo na końcu gdy pokazujesz tylko sumę końcową.
- Używaj jednego trybu zaokrąglania konsekwentnie i udokumentuj go.
- Przechowuj oryginalną kwotę, walutę i dokładny kurs użyty w transakcji.
Przykład: klient płaci 19.99 EUR i przechowujesz to jako 1999 w jednostkach mniejszych z currency=EUR. Przechowujesz też kurs użyty przy kasie (np. EUR na USD w micro jednostkach). Księga przechowuje przekonwertowaną kwotę w USD (zaokrągloną według wybranej reguły), ale zwroty używają zachowanej oryginalnej kwoty EUR, a nie ponownej konwersji z USD. To zapobiega zgłoszeniom typu „dlaczego dostałem z powrotem 19.98 EUR?”.
Formatowanie i wyświetlanie na urządzeniach
Ostatni odcinek to ekran. Wartość może być poprawna w magazynie, a mimo to wyglądać źle, jeśli formatowanie różni się między web a mobile.
Różne lokalizacje oczekują różnych separatorów i pozycji symbolu. Na przykład w USA użytkownicy czytają $1,234.50, podczas gdy w wielu krajach europejskich spodziewają się 1.234,50 € (ta sama wartość, inne separatory i pozycja symbolu). Jeśli na stałe narzucisz format, zdezorientujesz ludzi i stworzysz pracę dla supportu.
Zasada: formatuj na brzegu, nie w rdzeniu. Źródłem prawdy powinien być (currency code, integer amount_minor). Tylko konwertuj do łańcucha przy wyświetlaniu. Nigdy nie parsuj sformatowanego ciągu z powrotem na pieniądze — tam wkradają się niespodzianki związane z zaokrąglaniem i separatorami.
Dla wartości ujemnych jak zwroty wybierz spójny styl i używaj go wszędzie. Niektóre systemy pokazują -$12.34, inne ($12.34). Oba są w porządku. Przełączanie między nimi na różnych ekranach wygląda jak błąd.
Prosty kontrakt cross-device, który działa dobrze:
- Przechowuj walutę jako kod ISO (np. USD, EUR), nie tylko symbol.
- Formatuj domyślnie zgodnie z lokalizacją urządzenia, ale pozwól na nadpisanie w aplikacji.
- Pokaż kod waluty przy kwocie na ekranach multi-walut (np. 12.34 USD).
- Traktuj formatowanie wejścia oddzielnie od formatowania wyświetlania.
- Zaokrąglaj raz, zgodnie z regułami pieniężnymi, przed formatowaniem.
Przykład: klient widzi zwrot 10,00 EUR na mobile, a potem otwiera to samo zamówienie na desktop i widzi -€10. Jeśli pokażesz też kod (10,00 EUR) i zachowasz spójny styl ujemny, nie będą się zastanawiać, czy wartość się zmieniła.
Przykład: checkout, podatek i zwrot bez niespodzianek
Prosty koszyk:
- Pozycja A: $4.99 (499 centów)
- Pozycja B: $2.50 (250 centów)
- Pozycja C: $1.20 (120 centów)
Suma = 869 centów ($8.69). Zastosuj 10% rabatu najpierw: 869 x 10% = 86.9 centa, zaokrąglij do 87 centów. Rabatowana suma = 782 centów ($7.82). Teraz nalicz podatek 8.875%.
Tutaj reguły zaokrąglania mogą zmienić końcowy grosz.
Jeśli policzysz podatek od sumy faktury: 782 x 8.875% = 69.4025 centa, zaokrąglij do 69 centów.
Jeśli liczysz podatek per pozycję (po rabacie) i zaokrąglasz każdą pozycję:
- Pozycja A: podatek z $4.49 = 39.84875 centa, zaokrąglij do 40
- Pozycja B: podatek z $2.25 = 19.96875 centa, zaokrąglij do 20
- Pozycja C: podatek z $1.08 = 9.585 centa, zaokrąglij do 10
Suma podatku per pozycję = 70 centów. Ten sam koszyk, ta sama stawka, różna poprawna reguła — różnica 1 cent.
Dodaj opłatę za wysyłkę po podatku, powiedzmy 399 centów ($3.99). Suma wyniesie $12.50 (podatek od faktury) lub $12.51 (podatek per pozycję). Wybierz jedną regułę, udokumentuj ją i trzymaj się jej.
Teraz zwróć tylko Pozycję B. Zwróć jej rabatowaną cenę (225 centów) plus właściwy podatek. Przy podatku per pozycję to 225 + 20 = 245 centów ($2.45). Pozostałe sumy nadal się pogodzą.
Aby wyjaśnić później jakąkolwiek rozbieżność, loguj te wartości dla każdej opłaty i zwrotu:
- netto per wiersz w centach, podatek per wiersz w centach i użyty tryb zaokrąglania
- rabat na fakturze w centach i sposób jego alokacji
- stawka podatku i użyta podstawa opodatkowania w centach
- wysyłka/opłaty w centach i informacja, czy są opodatkowane
- sumaryczna kwota w centach i kwota zwrotu w centach
Jak testować obliczenia pieniędzy
Większość błędów pieniężnych to nie „błędy matematyczne”. To błędy zaokrąglania, kolejności i formatowania, które pojawiają się tylko dla konkretnych koszyków lub dat. Dobre testy czynią te przypadki nudnymi.
Zacznij od testów golden: stałe wejścia z dokładnymi oczekiwanymi wyjściami w jednostkach mniejszych (np. centach). Trzymaj asercje ścisłe. Jeśli pozycja to 199 centów i podatek to 15 centów, test powinien sprawdzać wartości integerowe, a nie sformatowane łańcuchy.
Mały zestaw goldenów pokryje wiele przypadków:
- Jedna pozycja z podatkiem, potem rabatem, potem opłatą (sprawdź każde pośrednie zaokrąglenie)
- Wiele pozycji, gdzie podatek zaokrąglany per pozycję kontra od sumy (zweryfikuj wybraną regułę)
- Zwroty i częściowe zwroty (zweryfikuj znaki i kierunek zaokrąglenia)
- Konwersja round-trip (A do B do A) z określoną polityką, gdzie zaokrąglanie występuje
- Przypadki brzegowe (pozycje 1 cent, duże ilości, bardzo duże sumy)
Potem dodaj testy oparte na własnościach (lub proste testy losowe), żeby złapać niespodzianki. Zamiast jednej oczekiwanej liczby, asercje sprawdzają inwarianty: sumy równe sumie części, nigdy nie ma ułamkowych jednostek mniejszych i „total = subtotal + tax + fees - discounts” zawsze obowiązuje.
Testy cross-platform są ważne, bo wyniki mogą dryfować między backendem a klientami. Jeśli masz backend w Go, web w Vue i mobile w Kotlin/SwiftUI, uruchamiaj te same wektory testowe w każdej warstwie i porównuj integerowe wyjścia, nie łańcuchy UI.
Na koniec testuj przypadki zależne od czasu. Przechowaj stawkę podatku używaną na fakturze i zweryfikuj, że stare faktury przeliczają się na ten sam wynik nawet po zmianie stawek. Tu rodzą się błędy typu „kiedyś się zgadzało”.
Typowe pułapki do uniknięcia
Większość błędów o jeden cent to nie błędy w liczeniu. To błędy polityki: kod robi dokładnie to, co mu polecono, ale nie to, czego oczekuje dział finansowy.
Pułapki, przed którymi warto się ustrzec:
- Za wczesne zaokrąglanie: jeśli zaokrąglasz każdą pozycję, potem zaokrąglasz subtotal, potem podatek, sumy mogą dryfować. Zdecyduj regułę (np. podatek per pozycja vs. od sumy) i zaokrąglaj tylko tam, gdzie polityka na to pozwala.
- Mieszanie walut w jednej sumie: dodawanie USD i EUR w tym samym polu „total” wygląda niewinnie, aż do zwrotów, raportów czy rozliczeń. Trzymaj kwoty oznaczone walutą i konwertuj według uzgodnionego kursu przed sumowaniem międzywalutowym.
- Błędne parsowanie wejścia użytkownika: użytkownicy wpisują „1,000.50”, „1 000,50” lub „10.0”. Jeśli parser zakłada jeden format, możesz cicho obciążyć 100050 zamiast 1000.50, albo zgubić końcowe zera. Normalizuj wejście, waliduj i zapisuj jako jednostki mniejsze.
- Używanie sformatowanych łańcuchów w API lub DB: "$1,234.56" jest tylko do wyświetlania. Jeśli twoje API akceptuje taki format, inny system może sparsować to inaczej. Przekazuj integer (amount_minor) plus kod waluty i pozwól klientom formatować lokalnie.
- Nie wersjonowanie reguł podatkowych lub tabel kursów: stawki podatku się zmieniają, zwolnienia się zmieniają, reguły zaokrąglania się zmieniają. Jeśli nadpiszesz stary kurs, przeszłe faktury nie będą dały się odtworzyć. Przechowuj wersję lub datę wejścia w życie przy każdym obliczeniu.
Szybkie sprawdzenie rzeczywistości: checkout utworzony w poniedziałek używa zeszłomiesięcznej stawki podatku; użytkownik jest zwracany w piątek po zmianie stawki. Jeśli nie przechowałeś wersji reguły podatkowej i pierwotnej polityki zaokrąglania, zwrot nie będzie się zgadzał z oryginalnym paragonem.
Szybka lista kontrolna i dalsze kroki
Jeśli chcesz mniej niespodzianek, traktuj pieniądze jak mały system z jasnymi wejściami, regułami i wyjściami. Większość błędów o cent przetrwa, bo nikt nie spisał, gdzie zaokrąglanie jest dozwolone.
Lista przed wdrożeniem:
- Przechowuj kwoty w jednostkach mniejszych (np. integer centów) wszędzie: DB, logika biznesowa i API.
- Wykonuj wszystkie obliczenia na integerach i konwertuj do formatu wyświetlanego dopiero na końcu.
- Wybierz jedną lokalizację zaokrąglenia dla każdego typu obliczenia (podatek, rabat, opłata, FX) i egzekwuj ją w jednym miejscu.
- Formatuj używając właściwych reguł waluty (miejsca dziesiętne, separatory, wartości ujemne) zgodnie na web i mobile.
- Dodaj testy dla przypadków brzegowych: 0.01, powtarzające się rozwinięcia dziesiętne w konwersjach, zwroty, częściowe autoryzacje i duże koszyki.
Spisz jedną politykę zaokrąglania dla każdego typu obliczenia. Na przykład: „Rabat zaokrąglany jest per pozycję do najbliższego centa; podatek zaokrąglany jest od sumy faktury; zwroty powtarzają pierwotną ścieżkę zaokrąglania.” Umieść te polityki obok kodu i w dokumentacji zespołowej, żeby nie dryfowały.
Dodaj lekkie logi dla każdego kroku pieniężnego, który ma znaczenie. Zapisuj wartości wejściowe, nazwę polityki i wyjście w jednostkach mniejszych. Kiedy klient zgłasza „zostałem obciążony o cent więcej”, chcesz jednego wiersza, który wyjaśni dlaczego.
Zaplanuj mawy audyt przed zmianą logiki w produkcji. Przelicz sumy na próbce rzeczywistych zamówień, porównaj stare i nowe wyniki i policz rozbieżności. Ręcznie przejrzyj kilka rozbieżności, żeby potwierdzić, że pasują do nowej polityki.
Jeśli chcesz zbudować taki end-to-end flow bez przepisywania tych samych reguł trzy razy, AppMaster (appmaster.io) jest zaprojektowany do kompletnych aplikacji ze współdzieloną logiką backendu. Możesz modelować kwoty jako integer w minor-unit w PostgreSQL przez Data Designer, zaimplementować kroki zaokrąglania i podatku raz w Business Process, a potem ponownie wykorzystać tę samą logikę w web i natywnych UI.
FAQ
Zazwyczaj dzieje się tak, gdy różne części aplikacji zaokrąglają w różnych momentach lub różnymi metodami. Jeśli lista produktów zaokrągla jedno miejsce, a checkout robi to inaczej, ten sam koszyk może trafnie zakończyć się na innych centach.
Ponieważ większość float nie potrafi dokładnie przedstawić typowych cen dziesiętnych, ukryte drobne błędy narastają. Te niewielkie różnice mogą później zmienić decyzję o zaokrągleniu i stworzyć różnicę jednego centa.
Przechowuj pieniądze jako całkowitą liczbę w jednostce mniejszej waluty — na przykład centy dla USD (1999 dla $19.99) — oraz kod waluty. Wykonuj obliczenia na liczbach całkowitych i formatuj do postaci dziesiętnej tylko przy wyświetlaniu użytkownikowi.
Twarde kodowanie dwóch miejsc po przecinku zawiedzie dla walut takich jak JPY (0 miejsc) czy BHD (3 miejsca). Zawsze przechowuj kod waluty przy kwocie i stosuj właściwy scale jednostki mniejszej przy parsowaniu i formatowaniu.
Wybierz jasną regułę i stosuj ją wszędzie, na przykład: zaokrąglanie podatku per pozycja lub zaokrąglanie podatku od sumy faktury. Klucz to konsekwencja między backendem, webem, mobilnymi, eksportami i zwrotami, używając tej samej metody zaokrąglania.
Ustal kolejność z góry i traktuj ją jako politykę, a nie szczegół implementacyjny. Częstym domyślnym podejściem jest najpierw rabat (zmniejsza podstawę opodatkowania), a potem podatek, ale stosuj reguły wymagane przez biznes i jurysdykcję i zachowaj je identycznie w każdym miejscu.
Konwertuj raz używając przechowanego kursu o jawnej precyzji, zaokrąglaj celowo w określonym kroku i przechowuj oryginalną kwotę i walutę dla zwrotów. Unikaj konwertowania tam i z powrotem — wielokrotne zaokrąglenia powodują dryft.
Nigdy nie parsuj sformatowanych łańcuchów wyświetlanych z powrotem na liczby — separatory lokalne i reguły zaokrąglania mogą zmienić wartość. Przekazuj strukturalne wartości jak (amount_minor, currency_code) i formatuj dopiero na warstwie UI zgodnie z regułami lokalnymi.
Testuj stałymi „golden” przypadkami z dokładnymi oczekiwanymi wynikami w jednostkach mniejszych (np. centach). Potem dodaj sprawdzenia inwariantów: suma równa się sumie pozycji, nigdy nie pojawiają się ułamkowe jednostki mniejsze, a relacja „total = subtotal + tax + fees - discounts” zawsze obowiązuje.
Centralizuj matematykę pieniężną w jednym wspólnym miejscu i ponownie jej używaj, aby te same wejścia dawały te same centy. W AppMaster praktyczne podejście to modelowanie amount_minor jako integer w PostgreSQL i umieszczenie logiki zaokrąglania i podatków w jednym Business Process, którego używają zarówno web, jak i mobile.


