Strnjenje številnih neskončnih realnih števil v končno število bitov zahteva približno predstavitev. Večina programov shrani rezultat celoštevilskih izračunov pri največ 32 ali 64 bitih. Glede na poljubno fiksno število bitov bo večina izračunov z realnimi števili proizvedla količine, ki jih ni mogoče natančno predstaviti s toliko bitov. Zato je treba rezultat izračuna s plavajočo vejico pogosto zaokrožiti, da se prilega nazaj v njegovo končno predstavitev. Ta napaka zaokroževanja je značilna lastnost računanja s plavajočo vejico. Zato moramo pri izračunih v številih s plavajočo vejico (še posebej, če so izračuni v denarju) paziti na napake pri zaokroževanju v programskem jeziku. Poglejmo primer:
Javapublic class Main { public static void main(String[] args) { double a = 0.7; double b = 0.9; double x = a + 0.1; double y = b - 0.1; System.out.println('x = ' + x); System.out.println('y = ' + y ); System.out.println(x == y); } }
testni primeri junit
Izhod:
x = 0.7999999999999999
y = 0.8
false
Tukaj odgovor ni tisto, kar smo pričakovali, saj je zaokroževanje opravil prevajalnik java.
Razlog za napako pri zaokroževanju
Podatkovna tipa Float in Double izvajata specifikacijo IEEE s plavajočo vejico 754. To pomeni, da so številke predstavljene v obliki, kot je:
SIGN FRACTION * 2 ^ EXP 0,15625 = (0,00101)2ki je v formatu s plavajočo vejico predstavljen kot: 1,01 * 2^-3
Vseh ulomkov ni mogoče natančno predstaviti kot ulomek potence dvojke. Kot preprost primer 0,1 = (0,000110011001100110011001100110011001100110011001100110011001…)2 in ga zato ni mogoče shraniti znotraj spremenljivke s plavajočo vejico.
Drug primer:
javapublic class Main { public static void main(String[] args) { double a = 0.7; double b = 0.9; double x = a + 0.1; double y = b - 0.1; System.out.println('x = ' + x); System.out.println('y = ' + y ); System.out.println(x == y); } }
Izhod:
x = 0.7999999999999999
y = 0.8
false
Še en primer:
Javapublic class Main { public static void main(String args[]) { double a = 1.0; double b = 0.10; double x = 9 * b; a = a - (x); // Value of a is expected as 0.1 System.out.println('a = ' + a); } }
Izhod:
a = 0.09999999999999998Kako popraviti zaokrožene napake?
- Zaokrožite rezultat: Funkcijo Round() lahko uporabite za zmanjšanje morebitnih učinkov netočnosti aritmetičnega shranjevanja s plavajočo vejico. Uporabnik lahko števila zaokroži na število decimalnih mest, ki jih zahteva izračun. Na primer, ko delate z valuto, bi verjetno zaokrožili na 2 decimalni mesti.
- Algoritmi in funkcije: Uporabite numerično stabilne algoritme ali oblikujte lastne funkcije za obravnavo takih primerov. Številke, za katere niste prepričani, da so pravilne, lahko skrajšate/zaokrožite (lahko izračunate tudi številsko natančnost operacij)
- Razred BigDecimal: Lahko uporabite java.math.BigDecimal razred, ki je zasnovan tako, da nam daje natančnost, zlasti v primeru velikih delnih števil. Naslednji program prikazuje, kako je mogoče napako odstraniti:
import java.math.BigDecimal; import java.math.RoundingMode; public class Main { public static void main(String args[]) { BigDecimal a = new BigDecimal('1.0'); BigDecimal b = new BigDecimal('0.10'); BigDecimal x = b.multiply(new BigDecimal('9')); a = a.subtract(x); // Rounding to 1 decimal place a = a.setScale(1 RoundingMode.HALF_UP); System.out.println('a = ' + a); } }
Izhod:
0.1Tukaj a = a.setScale(1 RoundingMode.HALF_UP);
krogi ana 1 decimalno mesto z uporabo načina zaokroževanja HALF_UP. Uporaba BigDecimala torej zagotavlja natančnejši nadzor nad aritmetičnimi operacijami in operacijami zaokroževanja, kar je lahko še posebej uporabno za finančne izračune ali druge primere, kjer je natančnost ključnega pomena.
Pomembna opomba:
Math.round zaokroži vrednost na najbližje celo število. Ker je 0,10 bližje 0 kot 1, se zaokroži na 0. Po zaokroževanju in deljenju z 1,0 je rezultat 0,0. Tako lahko opazite razliko med rezultati z razredom BigDecimal in funkcijo Maths.round.
Javapublic class Main { public static void main(String args[]) { double a = 1.0; double b = 0.10; double x = 9 * b; a = a - (x); /* We use Math.round() function to round the answer to closest long then we multiply and divide by 1.0 to to set the decimal places to 1 place (this can be done according to the requirements.*/ System.out.println('a = ' + Math.round(a*1.0)/1.0); } }
Izhod:
0.0