Java Language
Operaciones de punto flotante de Java
Buscar..
Introducción
Los números de punto flotante son números que tienen partes fraccionarias (generalmente expresadas con un punto decimal). En Java, hay dos tipos primitivos para números de punto flotante que son float
(usa 4 bytes) y double
(usa 8 bytes). Esta página de documentación es para detallar con operaciones de ejemplos que se pueden hacer en puntos flotantes en Java.
Comparando valores de punto flotante
Debe tener cuidado al comparar valores de punto float
( float
o double
) utilizando operadores relacionales: ==
!=
, <
Y así sucesivamente. Estos operadores dan resultados de acuerdo con las representaciones binarias de los valores de punto flotante. Por ejemplo:
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"
}
}
El cálculo oneThird
ha introducido un pequeño error de redondeo, y cuando multiplicamos oneThird
por 3
obtenemos un resultado que es ligeramente diferente a 1.0
.
Este problema de representaciones inexactas es más grave cuando intentamos mezclar el double
y el float
en los cálculos. Por ejemplo:
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
}
}
Las representaciones de punto flotante utilizadas en Java para los tipos float
y double
tienen un número limitado de dígitos de precisión. Para el tipo float
, la precisión es de 23 dígitos binarios o aproximadamente 8 dígitos decimales. Para el tipo double
, es de 52 bits o alrededor de 15 dígitos decimales. Además de eso, algunas operaciones aritméticas introducirán errores de redondeo. Por lo tanto, cuando un programa compara valores de punto flotante, es una práctica estándar definir un delta aceptable para la comparación. Si la diferencia entre los dos números es menor que el delta, se considera que son iguales. Por ejemplo
if (Math.abs(v1 - v2) < delta)
Ejemplo de comparación de 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();
}
}
}
Resultado:
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
También para la comparación de tipos primitivos double
y float
se puede usar el método de compare
estática del tipo de boxeo correspondiente. Por ejemplo:
double a = 1.0;
double b = 1.0001;
System.out.println(Double.compare(a, b));//-1
System.out.println(Double.compare(b, a));//1
Finalmente, determinar qué deltas son más apropiadas para una comparación puede ser complicado. Un enfoque comúnmente utilizado es seleccionar valores delta que, según nuestra intuición, son correctos. Sin embargo, si conoce la escala y la precisión (verdadera) de los valores de entrada y los cálculos realizados, puede ser posible establecer límites matemáticamente sólidos sobre la precisión de los resultados y, por lo tanto, para los deltas. (Hay una rama formal de Matemáticas conocida como Análisis Numérico que solía enseñarse a científicos computacionales que cubrían este tipo de análisis).
OverFlow y UnderFlow
Tipo de datos flotante
El tipo de datos flotante es un punto flotante IEEE 754 de 32 bits de precisión simple.
Desbordamiento de Float
El valor máximo posible es 3.4028235e+38
, cuando supera este valor produce Infinity
float f = 3.4e38f;
float result = f*2;
System.out.println(result); //Infinity
Float
Bajo Flujo
El valor mínimo es 1.4e-45f, cuando está por debajo de este valor, produce 0.0
float f = 1e-45f;
float result = f/1000;
System.out.println(result);
doble tipo de datos
El tipo de datos doble es un punto flotante IEEE 754 de doble precisión de 64-bit
.
Double
OverFlow
El valor máximo posible es 1.7976931348623157e+308
, cuando supera este valor produce Infinity
double d = 1e308;
double result=d*2;
System.out.println(result); //Infinity
Double
flujo bajo
El valor mínimo es 4.9e-324, cuando está por debajo de este valor, produce 0.0
double d = 4.8e-323;
double result = d/1000;
System.out.println(result); //0.0
Formato de los valores de punto flotante
Los números de punto flotante se pueden formatear como un número decimal utilizando String.format
con el 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"
Los números de punto flotante se pueden formatear como un número decimal usando 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
Adherencia estricta a la especificación IEEE
De forma predeterminada, las operaciones de punto flotante en float
y double
no se ajustan estrictamente a las reglas de la especificación IEEE 754. Se permite que una expresión use extensiones específicas de la implementación para el rango de estos valores; Básicamente permitiendo que sean más precisos de lo requerido.
strictfp
desactiva este comportamiento. Se aplica a una clase, interfaz o método, y se aplica a todo lo que contiene, como clases, interfaces, métodos, constructores, inicializadores de variables, etc. Con strictfp
, los valores intermedios de una expresión de punto flotante deben estar dentro de el conjunto de valores flotantes o el conjunto de valores dobles. Esto hace que los resultados de tales expresiones sean exactamente aquellos que predice la especificación IEEE 754.
Todas las expresiones constantes son implícitamente estrictas, incluso si no están dentro de un alcance strictfp
.
Por lo tanto, strictfp
tiene el efecto neto de que algunas veces los cálculos de casos de esquina sean menos precisos, y también puede hacer que las operaciones de punto flotante sean más lentas (ya que la CPU está haciendo más trabajo para garantizar que cualquier precisión adicional nativa no afecte el resultado). Sin embargo, también hace que los resultados sean exactamente iguales en todas las plataformas. Por lo tanto, es útil en cosas como programas científicos, donde la reproducibilidad es más importante que la velocidad.
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;
}
}
}