Java Language
Operacje zmiennoprzecinkowe Java
Szukaj…
Wprowadzenie
Liczby zmiennoprzecinkowe to liczby, które mają części ułamkowe (zwykle wyrażone kropką dziesiętną). W Javie istnieją dwa podstawowe typy liczb zmiennoprzecinkowych, które są float
(używa 4 bajtów) i double
(używa 8 bajtów). Ta strona dokumentacji służy do wyszczególnienia przykładów operacji, które można wykonać na liczbach zmiennoprzecinkowych w Javie.
Porównywanie wartości zmiennoprzecinkowych
Należy zachować ostrożność porównując wartości float
( float
lub double
) za pomocą operatorów relacyjnych: ==
!=
, <
Itd. Operatory te dają wyniki zgodnie z binarnymi reprezentacjami wartości zmiennoprzecinkowych. Na przykład:
public class CompareTest {
public static void main(String[] args) {
double oneThird = 1.0 / 3.0;
double one = oneThird * 3;
System.out.println(one == 1.0); // prints "false"
}
}
Obliczenia oneThird
wprowadziły drobny błąd zaokrąglania, a gdy pomnożymy oneThird
przez 3
, otrzymujemy wynik nieco inny niż 1.0
.
Problem niedokładnych reprezentacji jest bardziej dotkliwy, gdy próbujemy mieszać double
i float
w obliczeniach. Na przykład:
public class CompareTest2 {
public static void main(String[] args) {
float floatVal = 0.1f;
double doubleVal = 0.1;
double doubleValCopy = floatVal;
System.out.println(floatVal); // 0.1
System.out.println(doubleVal); // 0.1
System.out.println(doubleValCopy); // 0.10000000149011612
System.out.println(floatVal == doubleVal); // false
System.out.println(doubleVal == doubleValCopy); // false
}
}
Reprezentacje zmiennoprzecinkowe używane w Javie dla typów float
i double
mają ograniczoną liczbę cyfr precyzji. Dla typu float
dokładność wynosi 23 cyfry binarne lub około 8 cyfr dziesiętnych. W przypadku typu double
jest to 52 bity lub około 15 cyfr dziesiętnych. Ponadto niektóre operacje arytmetyczne wprowadzą błędy zaokrąglania. Dlatego, gdy program porównuje wartości zmiennoprzecinkowe, standardową praktyką jest zdefiniowanie dopuszczalnej delty do porównania. Jeśli różnica między dwiema liczbami jest mniejsza niż delta, są one uważane za równe. Na przykład
if (Math.abs(v1 - v2) < delta)
Przykład porównania Delta:
public class DeltaCompareExample {
private static boolean deltaCompare(double v1, double v2, double delta) {
// return true iff the difference between v1 and v2 is less than delta
return Math.abs(v1 - v2) < delta;
}
public static void main(String[] args) {
double[] doubles = {1.0, 1.0001, 1.0000001, 1.000000001, 1.0000000000001};
double[] deltas = {0.01, 0.00001, 0.0000001, 0.0000000001, 0};
// loop through all of deltas initialized above
for (int j = 0; j < deltas.length; j++) {
double delta = deltas[j];
System.out.println("delta: " + delta);
// loop through all of the doubles initialized above
for (int i = 0; i < doubles.length - 1; i++) {
double d1 = doubles[i];
double d2 = doubles[i + 1];
boolean result = deltaCompare(d1, d2, delta);
System.out.println("" + d1 + " == " + d2 + " ? " + result);
}
System.out.println();
}
}
}
Wynik:
delta: 0.01
1.0 == 1.0001 ? true
1.0001 == 1.0000001 ? true
1.0000001 == 1.000000001 ? true
1.000000001 == 1.0000000000001 ? true
delta: 1.0E-5
1.0 == 1.0001 ? false
1.0001 == 1.0000001 ? false
1.0000001 == 1.000000001 ? true
1.000000001 == 1.0000000000001 ? true
delta: 1.0E-7
1.0 == 1.0001 ? false
1.0001 == 1.0000001 ? false
1.0000001 == 1.000000001 ? true
1.000000001 == 1.0000000000001 ? true
delta: 1.0E-10
1.0 == 1.0001 ? false
1.0001 == 1.0000001 ? false
1.0000001 == 1.000000001 ? false
1.000000001 == 1.0000000000001 ? false
delta: 0.0
1.0 == 1.0001 ? false
1.0001 == 1.0000001 ? false
1.0000001 == 1.000000001 ? false
1.000000001 == 1.0000000000001 ? false
Do porównania double
i float
typów pierwotnych można zastosować metodę compare
statycznego odpowiedniego typu boksu. Na przykład:
double a = 1.0;
double b = 1.0001;
System.out.println(Double.compare(a, b));//-1
System.out.println(Double.compare(b, a));//1
Wreszcie ustalenie, które delty są najbardziej odpowiednie do porównania, może być trudne. Powszechnie stosowanym podejściem jest wybieranie wartości delta, które według naszej intuicji są prawidłowe. Jednakże, jeśli znasz skalę i (prawdziwą) dokładność wartości wejściowych oraz wykonane obliczenia, możliwe jest uzyskanie matematycznie rozsądnych granic dokładności wyników, a zatem i delt. (Istnieje formalna gałąź matematyki znana jako Analiza numeryczna, której uczono informatyków zajmujących się tego rodzaju analizą).
OverFlow i UnderFlow
Zmienny typ danych
Typ danych zmiennoprzecinkowych to 32-bitowy zmiennoprzecinkowy IEEE 754 o pojedynczej precyzji.
Przepełnienie Float
Maksymalna możliwa wartość to 3.4028235e+38
, gdy przekracza tę wartość, wytwarza Infinity
float f = 3.4e38f;
float result = f*2;
System.out.println(result); //Infinity
Float
UnderFlow
Minimalna wartość to 1,4e-45f, gdy zejdzie poniżej tej wartości, wytworzy 0.0
float f = 1e-45f;
float result = f/1000;
System.out.println(result);
podwójny typ danych
Podwójny typ danych to zmiennoprzecinkowa 64-bit
precyzja IEEE 754 o podwójnej precyzji.
Double
OverFlow
Maksymalna możliwa wartość to 1.7976931348623157e+308
, gdy przekracza tę wartość, wytwarza Infinity
double d = 1e308;
double result=d*2;
System.out.println(result); //Infinity
Double
UnderFlow
Minimalna wartość to 4,9e-324, gdy spadnie poniżej tej wartości, wytworzy 0.0
double d = 4.8e-323;
double result = d/1000;
System.out.println(result); //0.0
Formatowanie wartości zmiennoprzecinkowych
Liczby zmiennoprzecinkowe Liczby można sformatować jako liczbę dziesiętną za pomocą String.format
z String.format
'f'
//Two digits in fracttional part are rounded
String format1 = String.format("%.2f", 1.2399);
System.out.println(format1); // "1.24"
// three digits in fractional part are rounded
String format2 = String.format("%.3f", 1.2399);
System.out.println(format2); // "1.240"
//rounded to two digits, filled with zero
String format3 = String.format("%.2f", 1.2);
System.out.println(format3); // returns "1.20"
//rounder to two digits
String format4 = String.format("%.2f", 3.19999);
System.out.println(format4); // "3.20"
Liczby zmiennoprzecinkowe Liczby można sformatować jako liczbę dziesiętną za pomocą DecimalFormat
// rounded with one digit fractional part
String format = new DecimalFormat("0.#").format(4.3200);
System.out.println(format); // 4.3
// rounded with two digit fractional part
String format = new DecimalFormat("0.##").format(1.2323000);
System.out.println(format); //1.23
// formatting floating numbers to decimal number
double dv = 123456789;
System.out.println(dv); // 1.23456789E8
String format = new DecimalFormat("0").format(dv);
System.out.println(format); //123456789
Ścisłe przestrzeganie specyfikacji IEEE
Domyślnie operacje float
na liczbach float
i double
nie są ściśle zgodne z regułami specyfikacji IEEE 754. Wyrażenie może używać rozszerzeń specyficznych dla implementacji w zakresie tych wartości; zasadniczo pozwala im być bardziej dokładne niż wymagane.
strictfp
wyłącza to zachowanie. Jest stosowany do klasy, interfejsu lub metody i ma zastosowanie do wszystkich zawartych w nim elementów, takich jak klasy, interfejsy, metody, konstruktory, inicjatory zmiennych itp. W przypadku strictfp
wartości pośrednie wyrażenia zmiennoprzecinkowego muszą zawierać się w zakresie zestaw wartości zmiennoprzecinkowych lub zestaw wartości podwójnych. Powoduje to, że wyniki takich wyrażeń są dokładnie takie, jak przewiduje specyfikacja IEEE 754.
Wszystkie stałe wyrażenia są domyślnie surowe, nawet jeśli nie znajdują się w zakresie strictfp
.
W związku z tym strictfp
ma czasami wpływ na to, że niektóre obliczenia przypadków narożnych są mniej dokładne, a także może spowolnić operacje zmiennoprzecinkowe (ponieważ CPU wykonuje teraz więcej pracy, aby zapewnić, że żadna natywna dodatkowa precyzja nie wpłynie na wynik). Jednak powoduje również, że wyniki są dokładnie takie same na wszystkich platformach. Jest zatem przydatny w takich programach naukowych, w których odtwarzalność jest ważniejsza niż szybkość.
public class StrictFP { // No strictfp -> default lenient
public strictfp float strict(float input) {
return input * input / 3.4f; // Strictly adheres to the spec.
// May be less accurate and may be slower.
}
public float lenient(float input) {
return input * input / 3.4f; // Can sometimes be more accurate and faster,
// but results may not be reproducable.
}
public static final strictfp class Ops { // strictfp affects all enclosed entities
private StrictOps() {}
public static div(double dividend, double divisor) { // implicitly strictfp
return dividend / divisor;
}
}
}