round - numeros decimales en java
Cómo redondear un número a n lugares decimales en Java (29)
@Milhous: el formato decimal para redondear es excelente:
También puedes usar el
DecimalFormat df = new DecimalFormat("#.00000"); df.format(0.912385);
para asegurarse de que tiene los 0 finales.
Me gustaría agregar que este método es muy bueno para proporcionar un mecanismo de redondeo numérico real, no solo visualmente, sino también cuando se procesa.
Hipotético: tiene que implementar un mecanismo de redondeo en un programa GUI. Para modificar la precisión de un resultado, simplemente cambie el formato de intercalación (es decir, entre corchetes). Así que eso:
DecimalFormat df = new DecimalFormat("#0.######");
df.format(0.912385);
volvería como salida: 0.912385
DecimalFormat df = new DecimalFormat("#0.#####");
df.format(0.912385);
volvería como salida: 0.91239
DecimalFormat df = new DecimalFormat("#0.####");
df.format(0.912385);
volvería como salida: 0.9124
[EDITAR: también si el formato de intercalación es similar ("# 0. ############" y usted ingresa un decimal, por ejemplo, 3.1415926, por el bien del argumento, DecimalFormat no produce ninguna basura ( por ejemplo, ceros finales) y devolverá: 3.1415926
.. si así lo desea. Por supuesto, es un poco detallado para el gusto de algunos desarrolladores, pero bueno, tiene una huella de memoria baja durante el procesamiento y es muy fácil de implementar.]
Esencialmente, la belleza de DecimalFormat es que maneja simultáneamente el aspecto de la cuerda, así como el nivel de precisión de redondeo establecido. Ergo: obtienes dos beneficios por el precio de una implementación de código. ;)
Lo que me gustaría es un método para convertir un doble en una cadena que se redondea utilizando el método de media altura, es decir, si el decimal a redondear es 5, siempre se redondea al número anterior. Este es el método estándar de redondeo que la mayoría de las personas espera en la mayoría de las situaciones.
También me gustaría que solo se mostraran dígitos significativos, es decir, no debería haber ningún cero final.
Sé que un método para hacer esto es usar el método String.format
:
String.format("%.5g%n", 0.912385);
devoluciones:
0.91239
lo que es genial, sin embargo, siempre muestra números con 5 lugares decimales, incluso si no son significativos:
String.format("%.5g%n", 0.912300);
devoluciones:
0.91230
Otro método es usar el DecimalFormatter
:
DecimalFormat df = new DecimalFormat("#.#####");
df.format(0.912385);
devoluciones:
0.91238
Sin embargo, como se puede ver, esto utiliza el redondeo a medias Es decir, se redondeará hacia abajo si el dígito anterior es par. Lo que me gustaría es esto:
0.912385 -> 0.91239
0.912300 -> 0.9123
¿Cuál es la mejor manera de lograr esto en Java?
Aquí hay un resumen de lo que puede usar si desea el resultado como String:
DecimalFormat#setRoundingMode() :
DecimalFormat df = new DecimalFormat("#.#####"); df.setRoundingMode(RoundingMode.HALF_UP); String str1 = df.format(0.912385)); // 0.91239
String str2 = new BigDecimal(0.912385) .setScale(5, BigDecimal.ROUND_HALF_UP) .toString();
Aquí hay una sugerencia de qué bibliotecas puede usar si quiere double
como resultado. Sin embargo, no lo recomendaría para la conversión de cadenas, ya que es posible que el doble no pueda representar exactamente lo que quiere (vea, por ejemplo, here ):
Asumiendo que el value
es un double
, puedes hacer:
(double)Math.round(value * 100000d) / 100000d
Eso es para 5 dígitos de precisión. El número de ceros indica el número de decimales.
Como han señalado otros, la respuesta correcta es usar DecimalFormat
o BigDecimal
. El punto flotante no tiene decimales, por lo que no es posible redondear / truncar a un número específico de ellos en primer lugar. Tienes que trabajar en una base decimal, y eso es lo que hacen esas dos clases.
Estoy publicando el siguiente código como un ejemplo contrario a todas las respuestas en este hilo y, de hecho, en todo (y en otros lugares) que recomiendan la multiplicación seguida de un truncamiento seguido de una división. Corresponde a los defensores de esta técnica explicar por qué el siguiente código produce un resultado incorrecto en más del 92% de los casos.
public class RoundingCounterExample
{
static float roundOff(float x, int position)
{
float a = x;
double temp = Math.pow(10.0, position);
a *= temp;
a = Math.round(a);
return (a / (float)temp);
}
public static void main(String[] args)
{
float a = roundOff(0.0009434f,3);
System.out.println("a="+a+" (a % .001)="+(a % 0.001));
int count = 0, errors = 0;
for (double x = 0.0; x < 1; x += 0.0001)
{
count++;
double d = x;
int scale = 2;
double factor = Math.pow(10, scale);
d = Math.round(d * factor) / factor;
if ((d % 0.01) != 0.0)
{
System.out.println(d + " " + (d % 0.01));
errors++;
}
}
System.out.println(count + " trials " + errors + " errors");
}
}
Salida de este programa:
10001 trials 9251 errors
EDITAR: Para abordar algunos comentarios a continuación, rehago la parte del módulo del bucle de prueba usando BigDecimal
y el new MathContext(16)
para la operación del módulo de la siguiente manera:
public static void main(String[] args)
{
int count = 0, errors = 0;
int scale = 2;
double factor = Math.pow(10, scale);
MathContext mc = new MathContext(16, RoundingMode.DOWN);
for (double x = 0.0; x < 1; x += 0.0001)
{
count++;
double d = x;
d = Math.round(d * factor) / factor;
BigDecimal bd = new BigDecimal(d, mc);
bd = bd.remainder(new BigDecimal("0.01"), mc);
if (bd.multiply(BigDecimal.valueOf(100)).remainder(BigDecimal.ONE, mc).compareTo(BigDecimal.ZERO) != 0)
{
System.out.println(d + " " + bd);
errors++;
}
}
System.out.println(count + " trials " + errors + " errors");
}
Resultado:
10001 trials 4401 errors
Como no encontré una respuesta completa sobre este tema, he reunido una clase que debería manejar esto correctamente, con soporte para:
- Formato : formatee fácilmente una cadena doble con un cierto número de decimales
- Análisis : Analice el valor formateado de nuevo al doble
- Configuración regional : formatee y analice utilizando la configuración regional predeterminada
- Notación exponencial : comience a usar la notación exponencial después de un cierto umbral
El uso es bastante simple :
(Por el bien de este ejemplo, estoy usando una configuración regional personalizada)
public static final int DECIMAL_PLACES = 2;
NumberFormatter formatter = new NumberFormatter(DECIMAL_PLACES);
String value = formatter.format(9.319); // "9,32"
String value2 = formatter.format(0.0000005); // "5,00E-7"
String value3 = formatter.format(1324134123); // "1,32E9"
double parsedValue1 = formatter.parse("0,4E-2", 0); // 0.004
double parsedValue2 = formatter.parse("0,002", 0); // 0.002
double parsedValue3 = formatter.parse("3423,12345", 0); // 3423.12345
Aquí está la clase :
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
import java.util.Locale;
public class NumberFormatter {
private static final String SYMBOL_INFINITE = "/u221e";
private static final char SYMBOL_MINUS = ''-'';
private static final char SYMBOL_ZERO = ''0'';
private static final int DECIMAL_LEADING_GROUPS = 10;
private static final int EXPONENTIAL_INT_THRESHOLD = 1000000000; // After this value switch to exponential notation
private static final double EXPONENTIAL_DEC_THRESHOLD = 0.0001; // Below this value switch to exponential notation
private DecimalFormat decimalFormat;
private DecimalFormat decimalFormatLong;
private DecimalFormat exponentialFormat;
private char groupSeparator;
public NumberFormatter(int decimalPlaces) {
configureDecimalPlaces(decimalPlaces);
}
public void configureDecimalPlaces(int decimalPlaces) {
if (decimalPlaces <= 0) {
throw new IllegalArgumentException("Invalid decimal places");
}
DecimalFormatSymbols separators = new DecimalFormatSymbols(Locale.getDefault());
separators.setMinusSign(SYMBOL_MINUS);
separators.setZeroDigit(SYMBOL_ZERO);
groupSeparator = separators.getGroupingSeparator();
StringBuilder decimal = new StringBuilder();
StringBuilder exponential = new StringBuilder("0.");
for (int i = 0; i < DECIMAL_LEADING_GROUPS; i++) {
decimal.append("###").append(i == DECIMAL_LEADING_GROUPS - 1 ? "." : ",");
}
for (int i = 0; i < decimalPlaces; i++) {
decimal.append("#");
exponential.append("0");
}
exponential.append("E0");
decimalFormat = new DecimalFormat(decimal.toString(), separators);
decimalFormatLong = new DecimalFormat(decimal.append("####").toString(), separators);
exponentialFormat = new DecimalFormat(exponential.toString(), separators);
decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
decimalFormatLong.setRoundingMode(RoundingMode.HALF_UP);
exponentialFormat.setRoundingMode(RoundingMode.HALF_UP);
}
public String format(double value) {
String result;
if (Double.isNaN(value)) {
result = "";
} else if (Double.isInfinite(value)) {
result = String.valueOf(SYMBOL_INFINITE);
} else {
double absValue = Math.abs(value);
if (absValue >= 1) {
if (absValue >= EXPONENTIAL_INT_THRESHOLD) {
value = Math.floor(value);
result = exponentialFormat.format(value);
} else {
result = decimalFormat.format(value);
}
} else if (absValue < 1 && absValue > 0) {
if (absValue >= EXPONENTIAL_DEC_THRESHOLD) {
result = decimalFormat.format(value);
if (result.equalsIgnoreCase("0")) {
result = decimalFormatLong.format(value);
}
} else {
result = exponentialFormat.format(value);
}
} else {
result = "0";
}
}
return result;
}
public String formatWithoutGroupSeparators(double value) {
return removeGroupSeparators(format(value));
}
public double parse(String value, double defValue) {
try {
return decimalFormat.parse(value).doubleValue();
} catch (ParseException e) {
e.printStackTrace();
}
return defValue;
}
private String removeGroupSeparators(String number) {
return number.replace(String.valueOf(groupSeparator), "");
}
}
El manual de instrucciones de Java de Real posts esta solución, que también es compatible con versiones anteriores a Java 1.6.
BigDecimal bd = new BigDecimal(Double.toString(d));
bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP);
return bd.doubleValue();
Podrías usar el siguiente método de utilidad
public static double round(double valueToRound, int numberOfDecimalPlaces)
{
double multipicationFactor = Math.pow(10, numberOfDecimalPlaces);
double interestedInZeroDPs = valueToRound * multipicationFactor;
return Math.round(interestedInZeroDPs) / multipicationFactor;
}
Por si acaso alguien todavía necesita ayuda con esto. Esta solución funciona perfectamente para mí.
private String withNoTrailingZeros(final double value, final int nrOfDecimals) {
return new BigDecimal(String.valueOf(value)).setScale(nrOfDecimals, BigDecimal.ROUND_HALF_UP).stripTrailingZeros().toPlainString();
}
devuelve una String
con la salida deseada.
Pruebe esto: org.apache.commons.math3.util.Precision.round (doble x, escala int)
Consulte: http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math3/util/Precision.html
La página de inicio de Apache Commons Mathematics Library es: http://commons.apache.org/proper/commons-math/index.html
La implementación interna de este método es:
public static double round(double x, int scale) {
return round(x, scale, BigDecimal.ROUND_HALF_UP);
}
public static double round(double x, int scale, int roundingMethod) {
try {
return (new BigDecimal
(Double.toString(x))
.setScale(scale, roundingMethod))
.doubleValue();
} catch (NumberFormatException ex) {
if (Double.isInfinite(x)) {
return x;
} else {
return Double.NaN;
}
}
}
Puedes usar BigDecimal
BigDecimal value = new BigDecimal("2.3");
value = value.setScale(0, RoundingMode.UP);
BigDecimal value1 = new BigDecimal("-2.3");
value1 = value1.setScale(0, RoundingMode.UP);
System.out.println(value + "n" + value1);
Consulte: http://www.javabeat.net/precise-rounding-of-decimals-using-rounding-mode-enumeration/
Puedes usar la clase DecimalFormat.
double d = 3.76628729;
DecimalFormat newFormat = new DecimalFormat("#.##");
double twoDecimal = Double.valueOf(newFormat.format(d));
Si está utilizando DecimalFormat
para convertir double
a String
, es muy sencillo:
DecimalFormat formatter = new DecimalFormat("0.0##");
formatter.setRoundingMode(RoundingMode.HALF_UP);
double num = 1.234567;
return formatter.format(num);
Hay varios valores de enumeración de RoundingMode
para seleccionar, dependiendo del comportamiento que requiera.
Si realmente quiere números decimales para el cálculo (y no solo para la salida), no use un formato de punto flotante basado en binario como el doble.
Use BigDecimal or any other decimal-based format.
Utilizo BigDecimal para los cálculos, pero tenga en cuenta que depende del tamaño de los números con los que está tratando. En la mayoría de mis implementaciones, encuentro que el análisis de doble o entero a largo es suficiente para cálculos con números muy grandes.
De hecho, recientemente he usado Parsed-to-Long para obtener representaciones precisas (en lugar de resultados hexadecimales) en una GUI para números tan grandes como ################## ############### caracteres (como ejemplo).
Supongamos que tienes
double d = 9232.129394d;
puedes usar BigDecimal
BigDecimal bd = new BigDecimal(d).setScale(2, RoundingMode.HALF_EVEN);
d = bd.doubleValue();
o sin BigDecimal
d = Math.round(d*100)/100.0d;
con ambas soluciones d == 9232.13
También puedes usar el
DecimalFormat df = new DecimalFormat("#.00000");
df.format(0.912385);
para asegurarse de que tiene los 0 finales.
Una solución sucinta:
public static double round(double value, int precision) {
int scale = (int) Math.pow(10, precision);
return (double) Math.round(value * scale) / scale;
}
Véase también https://.com/a/22186845/212950 Gracias a jpdymond por ofrecer esto.
Use setRoundingMode
, configure el RoundingMode
explícita para manejar su problema con la ronda a medias, luego use el patrón de formato para su salida requerida.
Ejemplo:
DecimalFormat df = new DecimalFormat("#.####");
df.setRoundingMode(RoundingMode.CEILING);
for (Number n : Arrays.asList(12, 123.12345, 0.23, 0.1, 2341234.212431324)) {
Double d = n.doubleValue();
System.out.println(df.format(d));
}
da la salida:
12
123.1235
0.23
0.1
2341234.2125
Vine aquí solo por una respuesta simple sobre cómo redondear un número. Esta es una respuesta complementaria para proporcionar eso.
Cómo redondear un número en Java
El caso más común es usar Math.round()
.
Math.round(3.7) // 4
Los números se redondean al número entero más cercano. Se redondea un valor de .5
. Si necesita un comportamiento de redondeo diferente al de ese, puede usar una de las otras funciones Math . Vea la comparación a continuación.
round
Como se indicó anteriormente, esto se redondea al número entero más cercano. .5
decimales se redondean. Este método devuelve un int
.
Math.round(3.0); // 3
Math.round(3.1); // 3
Math.round(3.5); // 4
Math.round(3.9); // 4
Math.round(-3.0); // -3
Math.round(-3.1); // -3
Math.round(-3.5); // -3 *** careful here ***
Math.round(-3.9); // -4
ceil
Cualquier valor decimal se redondea al siguiente entero. Va hasta el techo . Este método devuelve a double
.
Math.ceil(3.0); // 3.0
Math.ceil(3.1); // 4.0
Math.ceil(3.5); // 4.0
Math.ceil(3.9); // 4.0
Math.ceil(-3.0); // -3.0
Math.ceil(-3.1); // -3.0
Math.ceil(-3.5); // -3.0
Math.ceil(-3.9); // -3.0
floor
Cualquier valor decimal se redondea al siguiente entero. Este método devuelve a double
.
Math.floor(3.0); // 3.0
Math.floor(3.1); // 3.0
Math.floor(3.5); // 3.0
Math.floor(3.9); // 3.0
Math.floor(-3.0); // -3.0
Math.floor(-3.1); // -4.0
Math.floor(-3.5); // -4.0
Math.floor(-3.9); // -4.0
rint
Esto es similar a redondear en esos valores decimales redondear al entero más cercano. Sin embargo, a diferencia round
, los .5
valores se redondean al entero par. Este método devuelve a double
.
Math.rint(3.0); // 3.0
Math.rint(3.1); // 3.0
Math.rint(3.5); // 4.0 ***
Math.rint(3.9); // 4.0
Math.rint(4.5); // 4.0 ***
Math.rint(5.5); // 6.0 ***
Math.rint(-3.0); // -3.0
Math.rint(-3.1); // -3.0
Math.rint(-3.5); // -4.0 ***
Math.rint(-3.9); // -4.0
Math.rint(-4.5); // -4.0 ***
Math.rint(-5.5); // -6.0 ***
En general, el redondeo se realiza mediante escalado: round(num / p) * p
/**
* MidpointRounding away from zero (''arithmetic'' rounding)
* Uses a half-epsilon for correction. (This offsets IEEE-754
* half-to-even rounding that was applied at the edge cases).
*/
double RoundCorrect(double num, int precision) {
double c = 0.5 * EPSILON * num;
// double p = Math.pow(10, precision); //slow
double p = 1; while (precision--> 0) p *= 10;
if (num < 0)
p *= -1;
return Math.round((num + c) * p) / p;
}
// testing edge cases
RoundCorrect(1.005, 2); // 1.01 correct
RoundCorrect(2.175, 2); // 2.18 correct
RoundCorrect(5.015, 2); // 5.02 correct
RoundCorrect(-1.005, 2); // -1.01 correct
RoundCorrect(-2.175, 2); // -2.18 correct
RoundCorrect(-5.015, 2); // -5.02 correct
Para lograr esto podemos usar este formateador:
DecimalFormat df = new DecimalFormat("#.00");
String resultado = df.format(valor)
o:
DecimalFormat df = new DecimalFormat("0.00"); :
Usa este método para obtener siempre dos decimales:
private static String getTwoDecimals(double value){
DecimalFormat df = new DecimalFormat("0.00");
return df.format(value);
}
Definiendo estos valores:
91.32
5.22
11.5
1.2
2.6
Usando el método podemos obtener estos resultados:
91.32
5.22
11.50
1.20
2.60
DecimalFormat es la mejor forma de generar, pero no lo prefiero. Siempre hago esto todo el tiempo, porque devuelve el valor doble. Así que puedo usarlo más que solo salida.
Math.round(selfEvaluate*100000d.0)/100000d.0;
O
Math.round(selfEvaluate*100000d.0)*0.00000d1;
Si necesita un valor de posiciones decimales grandes, puede usar BigDecimal en su lugar. De todos modos .0
es importante. Sin él, el redondeo de 0.33333d5 devuelve 0.33333 y solo se permiten 9 dígitos. La segunda función sin .0
problemas con 0.30000 devuelve 0.30000000000000004.
Donde dp = lugar decimal que desea, y el valor es un doble.
double p = Math.pow(10d, dp);
double result = Math.round(value * p)/p;
El fragmento de código a continuación muestra cómo mostrar n dígitos. El truco es establecer la variable pp a 1 seguido de n ceros. En el siguiente ejemplo, el valor de la variable pp tiene 5 ceros, por lo que se mostrarán 5 dígitos.
double pp = 10000;
double myVal = 22.268699999999967;
String needVal = "22.2687";
double i = (5.0/pp);
String format = "%10.4f";
String getVal = String.format(format,(Math.round((myVal +i)*pp)/pp)-i).trim();
Estoy de acuerdo con la respuesta elegida para utilizar DecimalFormat
--- o alternativamente BigDecimal
.
Por favor, lea la actualización a continuación primero!
Sin embargo, si no desea redondear el valor doble y obtener un double
resultado de valor, se puede utilizar org.apache.commons.math3.util.Precision.round(..)
como se mencionó anteriormente. La implementación usa BigDecimal
, es lenta y crea basura.
La DoubleRounder
utilidad en la biblioteca decimal4j proporciona un método similar pero rápido y sin basura :
double a = DoubleRounder.round(2.0/3.0, 3);
double b = DoubleRounder.round(2.0/3.0, 3, RoundingMode.DOWN);
double c = DoubleRounder.round(1000.0d, 17);
double d = DoubleRounder.round(90080070060.1d, 9);
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
Saldrá
0.667
0.666
1000.0
9.00800700601E10
Ver https://github.com/tools4j/decimal4j/wiki/DoubleRounder-Utility
Descargo de responsabilidad: estoy involucrado en el proyecto decimal4j.
Actualización: como @iaforek señaló que DoubleRounder a veces devuelve resultados contraintuitivos. La razón es que realiza redondeo matemáticamente correcto. Por ejemplo DoubleRounder.round(256.025d, 2)
, se redondeará a 256.02 porque el valor doble representado como 256.025d es algo más pequeño que el valor racional de 256.025 y, por lo tanto, se redondeará hacia abajo.
Notas:
- Este comportamiento es muy similar al del
BigDecimal(double)
constructor (pero no alvalueOf(double)
que usa el constructor de cadenas). - El problema puede ser evitado con un paso de doble redondeo a una precisión más alta primero, pero es complicado y no voy a entrar en detalles aquí
Por esas razones y todo lo mencionado anteriormente en esta publicación, no puedo recomendar el uso de DoubleRounder .
Si consideras 5 o n número de decimal. Puede ser esta respuesta resolver su problema.
double a = 123.00449;
double roundOff1 = Math.round(a*10000)/10000.00;
double roundOff2 = Math.round(roundOff1*1000)/1000.00;
double roundOff = Math.round(roundOff2*100)/100.00;
System.out.println("result:"+roundOff);
La salida será: 123.0 1
esto se puede resolver con bucle y función recursiva.
Si está utilizando una tecnología que tiene un mínimo de JDK. Aquí hay una manera sin ningún libs de Java:
double scale = 100000;
double myVal = 0.912385;
double rounded = (int)((myVal * scale) + 0.5d) / scale;
Tenga en cuenta que String.format () y DecimalFormat producen cadenas usando la configuración regional predeterminada. Por lo tanto, pueden escribir un número formateado con un punto o una coma como separador entre las partes enteras y decimales. Para asegurarse de que la Cadena redondeada está en el formato que desea, use java.text.NumberFormat así:
Locale locale = Locale.ENGLISH;
NumberFormat nf = NumberFormat.getNumberInstance(locale);
// for trailing zeros:
nf.setMinimumFractionDigits(2);
// round to 2 digits:
nf.setMaximumFractionDigits(2);
System.out.println(nf.format(.99));
System.out.println(nf.format(123.567));
System.out.println(nf.format(123.0));
Se imprimirá en la configuración regional en inglés (sin importar cuál sea su ubicación): 0.99 123.57 123.00
El ejemplo está tomado de Farenda: cómo convertir el doble en una cadena correctamente .
double myNum = .912385;
int precision = 10000; //keep 4 digits
myNum= Math.floor(myNum * precision +.5)/precision;
new BigDecimal(String.valueOf(double)).setScale(yourScale, BigDecimal.ROUND_HALF_UP);
te conseguirá un BigDecimal
. Para eliminar la cadena, simplemente llame al método toString
toPlainString
o al método toPlainString
para Java 5+ para una cadena de formato plano.
Programa de muestra:
package trials;
import java.math.BigDecimal;
public class Trials {
public static void main(String[] args) {
int yourScale = 10;
System.out.println(BigDecimal.valueOf(0.42344534534553453453-0.42324534524553453453).setScale(yourScale, BigDecimal.ROUND_HALF_UP));
}