BigDecimal, veoma veliki visokoprecizni broj sa pokretnim zarezom
BigDecimal je klasa u Javi koja se koristi za numeričke proračune sa brojevima sa pokretnim zarezom, prvenstveno pogodna za obradu scenarija koji zahtevaju preciznu reprezentaciju i izračunavanje. „BigDecimal ne samo da može precizno da predstavi veoma velike ili veoma male brojeve, već pruža i aritmetičke operacije proizvoljne preciznosti. Na efikasan način rešava problem greške zaokruživanja koji se može javiti pri tačnim proračunima sa brojevima sa pokretnim zarezom (float i double).“
Uvod u BigDecimal
Pri proračunima u oblastima kao što su finansije i nauka, BigDecimal je nastao kako bi rešio problem gubitka preciznosti koji postoji kod double ili float vrednosti pri izračunavanju. Prilikom dizajniranja BigDecimal, „cilj je bio da se obezbedi veća preciznost i tačnost, kako bi se osigurala tačnost operacija sa brojevima sa pokretnim zarezom.“ Zato ima sledeće karakteristike:
- „Visoka preciznost“:
BigDecimalmože precizno da predstavi veoma velike ili veoma male brojeve i pruža aritmetičke operacije proizvoljne preciznosti. - „Nepromenljivost“: Objekti klase
BigDecimalsu nepromenljivi. Jednom kreiran, broj se više ne menja. Sve aritmetičke operacije vraćaju novi objekat klaseBigDecimal, bez modifikacije originalnog objekta. Ovakav dizajn činiBigDecimalbezbednim za upotrebu u višenitnom okruženju (thread-safe). - „Bogati skup metoda za izračunavanje“:
BigDecimalpruža bogat skup metoda za aritmetičke operacije, kao što suadd(sabiranje),subtract(oduzimanje),multiply(množenje) idivide(deljenje), kao i metode za zaokruživanje, celobrojno zaokruživanje i poređenje. - „Fleksibilni režimi zaokruživanja“: Pruža više režima zaokruživanja (kao što su zaokruživanje na najbliži ceo broj, zaokruživanje na više itd.), čime se osigurava da su preciznost rezultata i ponašanje zaokruživanja pod kontrolom.
Ukratko, „BigDecimal svojom nepromenljivošću objekata osigurava bezbednost u višenitnom okruženju; istovremeno pruža bogat skup aritmetičkih metoda (sabiranje, oduzimanje, množenje, deljenje) i više režima zaokruživanja (zaokruživanje na najbliži ceo broj, zaokruživanje na više itd.), čime ispunjava zahteve preciznih numeričkih proračuna.“
Tajna skladištenja podataka u BigDecimal
Nakon što smo stekli osnovno znanje o BigDecimal, zatim ćemo preko Debug režima pogledati kako BigDecimal interno zaista implementira visokoprecizno skladištenje podataka. U tu svrhu, prvo ćemo konstruisati jedan objekat klase BigDecimal pomoću sledeće naredbe:
BigDecimal bigDecimal = new BigDecimal("3.1415926");Nakon pokretanja koda i ulaska u Debug režim IDEA-e, možete videti sledeći sadržaj:

Nije teško primetiti da BigDecimal objekat interno ima „pet važnih svojstava: intVal, scal, precision, stringCache, initCompact.“ Dalje, otvaranjem izvornog koda BigDecimal-a, možemo videti tipove ovih pet svojstava:
public class BigDecimal extends Number implements Comparable<BigDecimal> {
private final BigInteger intVal;
private final int scale;
private transient int precision;
private transient String stringCache;
private final transient long intCompact;Konkretno, „intVal je objekat klase BigInteger koji se uglavnom koristi za čuvanje vrednosti koje prelaze okvire primitivnih tipova.“ Na primer, za tip podataka Long, njegova maksimalna vrednost je 0x7fffffffffffffff, odnosno 9223372036854775807. Dakle, dodela BigDecimal bigDecimal = new BigDecimal("9223372036854775808") već prevazilazi opseg koji primitivni tipovi u Javi mogu da predstave, a u tom trenutku se u objektu bigDecimal njegovo interno intVal prikazuje kao što je prikazano — nije teško primetiti da je 9223372036854775808 dodeljeno intVal-u.

Nakon što smo razumeli pravila skladištenja svojstva intVal u BigDecimal-u, pogledajmo značenje scale i precision. „scale predstavlja broj decimala, dok precision predstavlja ukupan broj cifara u podacima BigDecimal-a, uključujući celobrojni i decimalni deo.“
Dalje, „svojstvo stringCache klase BigDecimal se uglavnom koristi za čuvanje informacija o stringu u koji su pretvoreni podaci klase BigDecimal, dok se intCompact koristi za pretvaranje podataka unutar opsega long u primitivni tip long radi skladištenja.“
(Napomena: Ako opseg tipa podataka prelazi onaj koji long može da predstavi, podaci se čuvaju u intVal-u)
Takođe treba imati na umu da, ako podaci sadrže decimalni zarez, on se uklanja, a zatim se čuvaju podaci bez decimalnog zareza. Na primer, kod new BigDecimal("3.1415926"), u ovom BigDecimal objektu intCompact = 31415926.
Najbolje prakse za BigDecimal
Nakon što smo saznali princip skladištenja BigDecimal-a za podatke sa pokretnim zarezom, zatim ćemo govoriti o nekoliko najboljih praksi u vezi sa BigDecimal-om, kako biste izbegli zamke pri korišćenju BigDecimal-a.
❝
- Da biste izbegli gubitak preciznosti, koristite konstruktor
BigDecimal(String val)ili metodBigDecimal.valueOf(double val)❞
Kada se koristi podatak tipa double za konstrukciju objekta klase BigDecimal, javlja se problem gubitka preciznosti. To je uglavnom zato što tip double sam po sebi ima ograničenja u preciznosti pri predstavljanju brojeva sa pokretnim zarezom.
Konkretno, tip double koristi format dvostrouke preciznosti brojeva sa pokretnim zarezom prema standardu IEEE 754, koji u binarnoj reprezentaciji ne može precizno da predstavi sve decimalne brojeve. Na primer, decimalni broj 0.1 je u binarnom zapisu beskonačna periodična decimala i može se samo aproksimirati kao 0.1000000000000000055511151231257827021181583404541015625. Kada se koristi konstruktor new BigDecimal(double), vrednost tipa double će proslediti svoju aproksimaciju BigDecimal-u, što dovodi do gubitka preciznosti. Na primer:
double value = 0.1;
BigDecimal bd = new BigDecimal(value);
System.out.println(bd);Gorenji kod će konačno ispisati: 0.1000000000000000055511151231257827021181583404541015625, dok očekivani BigDecimal zapravo iznosi 0.1. Da biste izbegli problem gubitka preciznosti pri konstrukciji BigDecimal-a, „preporučuje se korišćenje konstruktora BigDecimal(String val) ili statičkog metoda BigDecimal.valueOf(double val) za kreiranje objekata.“
❝
- Prilikom deljenja pomoću
BigDecimal, navedite preciznost rezultata❞
Pri izvođenju operacije deljenja, BigDecimal „ako se ne navede preciznost i režim zaokruživanja, kada rezultat nije celobrojan, biće bačen izuzetak ArithmeticException“. Na primer, kod 1 / 3 dobija se beskonačna periodična decimala. U tom slučaju, ako preciznost i režim zaokruživanja nisu jasno navedeni, BigDecimal ne može dovršiti operaciju deljenja i baca izuzetak.
public class BigDecimalDivisionExample {
public static void main(String[] args) {
BigDecimal num1 = new BigDecimal("1");
BigDecimal num2 = new BigDecimal("3");
BigDecimal result = num1.divide(num2);
}U gorenavedenom kodu, rezultat operacije num1 / num2 je beskonačna periodična decimala 0.333.... „Pošto u kodu nismo naveli preciznost i režim zaokruživanja, pri izvršavanju gorenavedenog koda će se javiti sledeći izuzetak“: Exception: Non-terminating decimal expansion; no exact representable decimal result.
Da biste izbegli gorenavedeni izuzetak, možete eksplicitno navesti preciznost i način zaokruživanja pri pozivu divide. Konkretno, ovako: num1.divide(num2,2, RoundingMode.HALF_UP); U ovom primeru se zadržavaju dve decimala, a koristi se režim RoundingMode.HALF_UP — zaokruživanje na najbliži ceo broj.
Zapravo, pored režima zaokruživanja RoundingMode.HALF_UP, BigDecimal ima i sledeće režime zaokruživanja:
RoundingMode.HALF_UP: zaokruživanje na najbliži ceo broj, na više.RoundingMode.HALF_DOWN: zaokruživanje na najbliži ceo broj, na niže.RoundingMode.HALF_EVEN: zaokruživanje na najbliži ceo broj; ako je odbijeni deo jednak 0.5, zaokružuje se na najbliži paran broj.
❝
- Prema poslovnim potrebama, razumno koristite
compareToiequals❞
Pošto je BigDecimal interno izmenio logiku metoda equals, to čini da metod equals ne poredi samo numerički deo, već i skalu (broj decimala). Dakle, metod equals će vratiti true samo kada su i vrednost i skala jednaki. Na primer:
public class BigDecimalComparison {
public static void main(String[] args) {
BigDecimal bd1 = new BigDecimal("1.0");
BigDecimal bd2 = new BigDecimal("1.00");
System.out.println(bd1.equals(bd2)); // ispisuje false
}
}U ovom primeru, bd1 = 1.0 i bd2 = 1.00 — numeričko značenje ova dva broja je potpuno isto, ali im je preciznost različita. Ukoliko se za poređenje koristi metod equals, vratiće false. Lakim korišćenjem metode equals se lako mogu dobiti neočekivani rezultati.
Da bi se osiguralo poređenje vrednosti, BigDecimal je interno izmenio i metod compareTo, tako da metod compareTo poredi samo numerički deo BigDecimal-a bez uzimanja u obzir skalu. „Prema tome, ako su numeričke vrednosti dva objekta klase BigDecimal jednake, čak i ako im je skala različita, metod compareTo će ih smatrati jednakim.“
public class BigDecimalComparison {
public static void main(String[] args) {
BigDecimal bd1 = new BigDecimal("1.0");
BigDecimal bd2 = new BigDecimal("1.00");
System.out.println(bd1.compareTo(bd2)); //
}
}U ovom primeru konačni rezultat ispisa je 0, što znači da su bd1 i bd2 jednaki. To je uglavnom zato što su numeričke vrednosti bd1 i bd2 jednake, pa metod compareTo vraća 0.
Dakle, da biste izbegli nepotrebnu zabunu i greške, pokušajte da pratite sledeće najbolje prakse:
- „Jasno odredite svrhu poređenja“: Prilikom poređenja pomoću
BigDecimal-a, prvo jasno odredite da li je vaša svrha provera numeričke jednakosti ili potpune jednakosti (uključujući skalu). Ako upoređujete samo vrednosti, koristite metodcompareTo. Ako vam je potrebna potpuna jednakost, koristite metodequals. - „Izbegavajte zabunu“: Razumite da metod
equalsklaseBigDecimaluzima u obzir skalu, dok metodcompareToporedi samo vrednosti. U uobičajenim poređenjima brojeva se preporučuje korišćenje metodacompareTo.
❝
- Oprezno koristite metod
toStringklaseBigDecimal❞
BigDecimal je interno izmenio metod toString, što čini da metod toString klase BigDecimal automatski uklanja krajnje nule i koristi naučnu notaciju za predstavljanje veoma velikih ili veoma malih brojeva. Na primer:
public class BigDecimalToStringExample {
public static void main(String[] args) {
BigDecimal bd1 = new BigDecimal("123.4500");
BigDecimal bd2 = new BigDecimal("0.00012345");
System.out.println(bd1.toString());
System.out.println(bd2.toString());
}
}Gorenji kod će redom ispisati 123.45 i 1.2345E-4. Kod bd1 su krajnje nule uklonjene, dok bd2 koristi naučnu notaciju za predstavljanje podataka. Da biste izbegli ovakve probleme, možete koristiti metod toPlainString klase BigDecimal. Ovaj metod ne uklanja krajnje nule i ne koristi naučnu notaciju.
import java.math.BigDecimal;
public class BigDecimalToPlainStringExample {
public static void main(String[] args) {
BigDecimal bd1 = new BigDecimal("123.4500");
BigDecimal bd2 = new BigDecimal("0.00012345");
System.out.println(bd1.toPlainString()); // ispisuje 123.4500
System.out.println(bd2.toPlainString()); // ispisuje 0.00012345
}
}Nije teško primetiti da u ovom primeru metod toPlainString zadržava krajnje nule i ne koristi naučnu notaciju, pa je format ispisa intuitivniji.
- „Metod
toString“: Automatski uklanja krajnje nule i koristi naučnu notaciju za predstavljanje veoma velikih ili veoma malih brojeva. Može dovesti do toga da format ne odgovara očekivanjima. - „Metod
toPlainString“: Ne uklanja krajnje nule i ne koristi naučnu notaciju; pogodan za scenarije u kojima je potrebno zadržati originalni format broja.
Rezime
Ovaj članak se uglavnom bavi analizom pravila skladištenja brojeva sa pokretnim zarezom unutar BigDecimal-a, kako bi se produbilo razumevanje čitaoca o BigDecimal-u. Ujedno su sažete sledeće najbolje prakse za korišćenje BigDecimal-a:
- Da biste izbegli gubitak preciznosti, koristite konstruktor
BigDecimal(String val)ili metodBigDecimal.valueOf(double val); - Prilikom izvođenja operacije deljenja pomoću
BigDecimal, navedite preciznost rezultata; - Prema poslovnim potrebama, razumno koristite
compareToiequals; - Oprezno koristite metod
toStringklaseBigDecimal.
