logo

Zaokrąglanie błędów w Javie

Kompaktowanie wielu nieskończonych liczb rzeczywistych na skończoną liczbę bitów wymaga przybliżonej reprezentacji. Większość programów przechowuje wyniki obliczeń całkowitych w maksymalnie 32 lub 64 bitach. Przy dowolnej ustalonej liczbie bitów większość obliczeń na liczbach rzeczywistych da ilości, których nie można dokładnie przedstawić przy użyciu tak wielu bitów. Dlatego wynik obliczeń zmiennoprzecinkowych często należy zaokrąglić, aby zmieścił się w jego skończonej reprezentacji. Ten błąd zaokrąglenia jest charakterystyczną cechą obliczeń zmiennoprzecinkowych. Dlatego też, wykonując obliczenia na liczbach zmiennoprzecinkowych (zwłaszcza jeśli obliczenia dotyczą pieniędzy), musimy uważać na błędy zaokrągleń w języku programowania. Zobaczmy przykład:

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);  } } 


przykłady NFA

Wyjście:



x = 0.7999999999999999  
y = 0.8
false

Tutaj odpowiedzią nie jest to, czego oczekiwaliśmy, ponieważ jest to zaokrąglenie wykonane przez kompilator Java.

Przyczyna błędu zaokrąglenia

Typy danych Float i Double implementują specyfikację zmiennoprzecinkową IEEE 754. Oznacza to, że liczby są reprezentowane w postaci takiej jak:

SIGN FRACTION * 2 ^ EXP 

0,15625 = (0,00101)2co w formacie zmiennoprzecinkowym jest reprezentowane jako: 1,01 * 2^-3
Nie wszystkie ułamki można przedstawić dokładnie jako ułamek potęgi dwójki. Jako prosty przykład 0,1 = (0,000110011001100110011001100110011001100110011001100110011001… )2 i dlatego nie można ich przechowywać w zmiennej zmiennoprzecinkowej.

Inny przykład:

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);  } } 

Wyjście:

x = 0.7999999999999999  
y = 0.8
false

Inny przykład:

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);  } } 

Wyjście:

a = 0.09999999999999998

Jak naprawić błędy zaokrągleń?

  • Zaokrąglij wynik: Funkcji Round() można użyć do zminimalizowania wszelkich skutków niedokładności przechowywania arytmetyki zmiennoprzecinkowej. Użytkownik może zaokrąglić liczby do wymaganej liczby miejsc po przecinku. Na przykład podczas pracy z walutą prawdopodobnie zaokrąglisz liczbę do 2 miejsc po przecinku.
  • Algorytmy i funkcje: Użyj numerycznie stabilnych algorytmów lub zaprojektuj własne funkcje do obsługi takich przypadków. Możesz obciąć/zaokrąglić cyfry, co do których nie jesteś pewien, czy są poprawne (możesz również obliczyć numeryczną precyzję operacji)
  • Klasa BigDecimal: Możesz skorzystać z java.math.BigDecimal klasa, która ma zapewnić nam dokładność, szczególnie w przypadku dużych liczb ułamkowych. Poniższy program pokazuje, jak można usunąć błąd:
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);  } } 


Wyjście:

0.1

Tutaj a = a.setScale(1 RoundingMode.HALF_UP);

Rundy ado 1 miejsca po przecinku przy użyciu trybu zaokrąglania HALF_UP. Zatem użycie BigDecimal zapewnia bardziej precyzyjną kontrolę nad operacjami arytmetycznymi i zaokrąglaniami, co może być szczególnie przydatne w obliczeniach finansowych lub w innych przypadkach, w których precyzja ma kluczowe znaczenie.

Ważna uwaga:

Math.round zaokrągla wartość do najbliższej liczby całkowitej. Ponieważ 0,10 jest bliżej 0 niż 1, zostaje zaokrąglone do 0. Po zaokrągleniu i podzieleniu przez 1,0 wynikiem jest 0,0. Można więc zauważyć różnicę między wynikami klasy BigDecimal i funkcją 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);  } } 

Wyjście:

0.0