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


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow