Java Language
Opérations en virgule flottante Java
Recherche…
Introduction
Les nombres à virgule flottante sont des nombres comportant des parties fractionnaires (généralement exprimées par un point décimal). En Java, il existe deux types de primitives pour les nombres à virgule flottante: float
(utilise 4 octets) et double
(utilise 8 octets). Cette page de documentation permet de détailler avec des exemples des opérations pouvant être effectuées sur des points flottants en Java.
Comparer des valeurs en virgule flottante
Vous devez faire attention lorsque vous comparez des valeurs à virgule flottante ( float
ou double
) en utilisant des opérateurs relationnels: ==
!=
, <
Et ainsi de suite. Ces opérateurs donnent des résultats en fonction des représentations binaires des valeurs à virgule flottante. Par exemple:
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"
}
}
Le calcul oneThird
a introduit une erreur d’arrondi minuscule, et lorsque nous multiplions un oneThird
par 3
nous obtenons un résultat légèrement différent de 1.0
.
Ce problème des représentations inexactes est plus flagrant lorsque nous essayons de mélanger double
et float
dans les calculs. Par exemple:
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
}
}
Les représentations en virgule flottante utilisées en Java pour les types float
et double
ont un nombre limité de chiffres de précision. Pour le type float
, la précision est de 23 chiffres binaires ou d'environ 8 chiffres décimaux. Pour le type double
, il s'agit de 52 bits ou d'environ 15 chiffres décimaux. De plus, certaines opérations arithmétiques introduiront des erreurs d'arrondi. Par conséquent, lorsqu'un programme compare des valeurs à virgule flottante, il est pratique courante de définir un delta acceptable pour la comparaison. Si la différence entre les deux nombres est inférieure au delta, ils sont considérés égaux. Par exemple
if (Math.abs(v1 - v2) < delta)
Delta comparer exemple:
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();
}
}
}
Résultat:
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
Pour la comparaison des types primitifs double
et float
on peut également utiliser une méthode de compare
statique du type boxe correspondant. Par exemple:
double a = 1.0;
double b = 1.0001;
System.out.println(Double.compare(a, b));//-1
System.out.println(Double.compare(b, a));//1
Enfin, il peut être difficile de déterminer quels sont les deltas les plus appropriés pour une comparaison. Une approche couramment utilisée consiste à choisir des valeurs delta qui, selon notre intuition, sont à peu près correctes. Cependant, si vous connaissez l'échelle et la précision des valeurs d'entrée, ainsi que les calculs effectués, il est possible de définir mathématiquement des limites solides sur la précision des résultats et, par conséquent, sur les deltas. (Il existe une branche formelle des mathématiques connue sous le nom d’analyse numérique qui était enseignée à des scientifiques spécialisés dans ce type d’analyse.)
OverFlow et UnderFlow
Type de données flottant
Le type de données float est un virgule flottante IEEE 754 32 bits simple précision.
Float
La valeur maximale possible est 3.4028235e+38
, quand elle dépasse cette valeur, elle produit Infinity
float f = 3.4e38f;
float result = f*2;
System.out.println(result); //Infinity
Float
UNDERFLOW
La valeur minimale est 1.4e-45f, quand est en dessous de cette valeur, elle produit 0.0
float f = 1e-45f;
float result = f/1000;
System.out.println(result);
type de données double
Le type de données double est un virgule flottante IEEE 754 64-bit
double précision.
Double
OverFlow
La valeur maximale possible est 1.7976931348623157e+308
, lorsqu'elle dépasse cette valeur, elle produit Infinity
double d = 1e308;
double result=d*2;
System.out.println(result); //Infinity
Double
UnderFlow
La valeur minimale est 4.9e-324, quand elle est inférieure à cette valeur, elle produit 0.0
double d = 4.8e-323;
double result = d/1000;
System.out.println(result); //0.0
Formatage des valeurs à virgule flottante
Les nombres à virgule flottante peuvent être formatés sous la forme d'un nombre décimal à l'aide de String.format
avec l' 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"
Les nombres à virgule flottante peuvent être formatés en nombre décimal à l'aide de 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
Adhésion stricte à la spécification IEEE
Par défaut, les opérations à virgule flottante sur float
et double
ne respectent pas strictement les règles de la spécification IEEE 754. Une expression est autorisée à utiliser des extensions spécifiques à l'implémentation pour la plage de ces valeurs; leur permettant essentiellement d'être plus précis que nécessaire.
strictfp
désactive ce comportement. Il est appliqué à une classe, une interface ou une méthode et s'applique à tout ce qui y est contenu, comme les classes, interfaces, méthodes, constructeurs, initialiseurs de variables, etc. Avec strictfp
, les valeurs intermédiaires d'une expression à virgule flottante doivent être comprises entre l'ensemble de valeurs flottantes ou l'ensemble de valeurs doubles. Les résultats de ces expressions sont donc exactement ceux prévus par la spécification IEEE 754.
Toutes les expressions constantes sont implicitement strictes, même si elles ne sont pas strictfp
dans une portée strictfp
.
Par conséquent, strictfp
a pour effet net de parfois faire certains calculs coin cas moins précis, et peut également plus lent opérations en virgule flottante (comme la CPU est en train de faire plus de travail pour assurer une précision supplémentaire native ne modifie pas le résultat). Cependant, les résultats sont également identiques sur toutes les plates-formes. Il est donc utile dans des choses comme les programmes scientifiques, où la reproductibilité est plus importante que la vitesse.
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;
}
}
}