logo

Zaokroževanje napak v Javi

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:

Java
public 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:

java
public 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:

Java
public 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.09999999999999998

Kako 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:
Java
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.1

Tukaj 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.

Java
public 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