Cómo redondear un número a n decimales en Java

1259

Lo que me gustaría es un método para convertir un doble en una cadena que se redondea utilizando el método de la mitad hacia arriba, es decir, si el decimal a redondear es 5, siempre se redondea al siguiente número. Este es el método estándar de redondeo que la mayoría de la gente espera en la mayoría de las situaciones.

También me gustaría que solo se muestren dígitos significativos, es decir, no debería haber ceros a la izquierda.

Sé que un método para hacer esto es usar el String.formatmétodo:

String.format("%.5g%n", 0.912385);

devoluciones:

0.91239

lo cual es genial, sin embargo, siempre muestra números con 5 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 puede ver, esto usa 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?

Alex Spurling
fuente

Respuestas:

751

Use setRoundingMode, configure RoundingModeexplícitamente para manejar su problema con la media ronda, 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

EDITAR : La respuesta original no aborda la precisión de los valores dobles. Eso está bien si no te importa mucho si se redondea hacia arriba o hacia abajo. Pero si desea un redondeo preciso, debe tener en cuenta la precisión esperada de los valores. Los valores de coma flotante tienen una representación binaria internamente. Eso significa que un valor como 2.7735 en realidad no tiene ese valor exacto internamente. Puede ser un poco más grande o un poco más pequeño. Si el valor interno es ligeramente menor, entonces no se redondeará a 2.7740. Para remediar esa situación, debe ser consciente de la precisión de los valores con los que está trabajando y sumar o restar ese valor antes de redondear. Por ejemplo, cuando sabe que sus valores son precisos hasta 6 dígitos, luego, para redondear los valores a la mitad, agregue esa precisión al valor:

Double d = n.doubleValue() + 1e-6;

Para redondear hacia abajo, reste la precisión.

curtisk
fuente
99
Esta es probablemente la mejor solución presentada hasta ahora. La razón por la que no vi esta instalación cuando vi por primera vez la clase DecimalFormat es que solo se introdujo en Java 1.6. Desafortunadamente, estoy restringido a usar 1.5, pero será útil saberlo en el futuro.
Alex Spurling
1
Intenté esto con: "#.##"redondeo HALF_UP. 256.335f-> "256.33"... (el ejemplo proviene de los comentarios a la respuesta de @ asterite).
piedras grandes el
66
Tenga cuidado ya que DecimalFormat depende de su configuración local actual, es posible que no obtenga un punto como separador. Personalmente prefiero la respuesta de Asterite a continuación
Gomino
1
También tenga en cuenta que no debe esperar que DecimalFormat sea seguro para subprocesos. Según los documentos de Java : los formatos decimales generalmente no están sincronizados. Se recomienda crear instancias de formato separadas para cada hilo. Si varios subprocesos acceden a un formato simultáneamente, debe sincronizarse externamente.
CGK
1
¿Cómo lo hago para que haga un redondeo adecuado para que no redondee 0.0004 a 0.001
471

Suponiendo que valuees un double, puede hacer:

(double)Math.round(value * 100000d) / 100000d

Eso es para una precisión de 5 dígitos. El número de ceros indica el número de decimales.

asterita
fuente
71
ACTUALIZACIÓN: Acabo de confirmar que hacer esto ES MUCHO más rápido que usar DecimalFormat. Enganché usando DecimalFormat 200 veces, y este método. DecimalFormat tardó 14 ms en completar los 200 bucles, este método tardó menos de 1 ms. Como sospechaba, esto es más rápido. Si le pagan por el ciclo del reloj, esto es lo que debería estar haciendo. Me sorprende que Chris Cudmore incluso dijera lo que dijo para ser honesto. asignar objetos siempre es más costoso que lanzar primitivas y usar métodos estáticos (Math.round () en lugar de decimalFormat.format ()).
Andi Jay
99
Esta técnica falla en más del 90% de los casos. -1.
Marqués de Lorne
25
De hecho, esto no funciona: Math.round(0.1 * Math.pow(10,20))/Math.pow(10,20) == 0.09223372036854775.
Robert Tupelo-Schneck
54
Tenga mucho cuidado al usar este método (o cualquier redondeo de puntos flotantes). Falla por algo tan simple como 265.335. El resultado intermedio de 265.335 * 100 (precisión de 2 dígitos) es 26533.499999999996. Esto significa que se redondea a 265.33. Simplemente hay problemas inherentes al convertir de números de coma flotante a números decimales reales. Vea la respuesta de EJP aquí en stackoverflow.com/a/12684082/144578
Sebastiaan van den Broek el
66
@SebastiaanvandenBroek: Wow, nunca supe que era tan fácil obtener una respuesta incorrecta. Sin embargo, si se trabaja con números no exactos, se debe reconocer que cualquier valor no es exacto . 265.335realmente significa 265.335 += tolerance, donde la tolerancia depende de las operaciones anteriores y el rango de valores de entrada. No sabemos el verdadero valor exacto. En los valores límite, cualquier respuesta es posiblemente correcta. Si necesitamos ser exactos, no deberíamos trabajar en doble. El failaquí no está en volver a convertir al doble. Está en OP pensando que puede confiar en que la entrada 265.335sea ​​exactamente eso.
ToolmakerSteve
191
new BigDecimal(String.valueOf(double)).setScale(yourScale, BigDecimal.ROUND_HALF_UP);

te conseguirá a BigDecimal. Para obtener la cadena fuera de él, simplemente llamar a eso BigDecimal's toStringmétodo o latoPlainString método para Java 5+ para una cadena sin formato.

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));
    }
MetroidFan2002
fuente
38
Esa es mi solución preferida. Aún más corto: BigDecimal.valueOf (doubleVar) .setScale (yourScaleHere, BigDecimal.ROUND_HALF_UP); BigDecimal.valueOf (double val) en realidad llama a Double.toString () debajo del capó;)
Etienne Neveu
44
Agradable. No corte esquinas y use, new BigDecimal(doubleVar)ya que puede encontrarse con problemas con el redondeo de puntos flotantes
Edd
8
@Edd, curiosamente, el problema de redondeo ocurre en el caso que SebastiaanvandenBroek menciona en comentarios a la respuesta de asterite. double val = 265.335;, BigDecimal.valueOf(val).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString();=> 265.34, pero (new BigDecimal(val)).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString();=> 265.33.
ToolmakerSteve
77
@ToolmakerSteve Eso se debe a que el uso de un nuevo BigDecimal con el doble toma el valor doble directamente e intenta usarlo para crear el BigDecimal, mientras que cuando se usa BigDecimal.valueOf o el formulario de cadena se analiza primero en una cadena (una representación más exacta) antes de la conversión .
MetroidFan2002
116

También puedes usar el

DecimalFormat df = new DecimalFormat("#.00000");
df.format(0.912385);

para asegurarte de que tienes los 0 finales.

Milhous
fuente
25
Creo que uno de los objetivos de la cuestión era que "no debería no haber ningún ceros".
Lunchbox
77
Para esta pregunta, el operador no quería ceros, pero esto es exactamente lo que quería. Si tiene una lista de números con 3 decimales, desea que todos tengan los mismos dígitos, incluso si es 0.
Tom Kincaid
Olvidó especificarRoundingMode.
IgorGanapolsky
1
@IgorGanapolsky por defecto Decimal modeutilizaRoundingMode.HALF_EVEN.
EndermanAPM
87

Como otros han notado, la respuesta correcta es usar cualquiera DecimalFormato BigDecimal. El punto flotante no tiene lugares decimales, por lo que no puede redondear / truncar a un número específico de ellos en primer lugar. Tienes que trabajar en una raíz decimal, y eso es lo que hacen esas dos clases.

Estoy publicando el siguiente código como contraejemplo de todas las respuestas en este hilo y, de hecho, en todo StackOverflow (y en otros lugares) que recomiendan la multiplicación seguida del truncamiento seguido de la 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, rehice la parte del módulo del bucle de prueba usando BigDecimaly 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
Marqués de Lorne
fuente
8
El truco es que en todos sus errores 9251, el resultado impreso sigue siendo correcto.
Didier L
77
@DidierL No me sorprende. Tuve la muy buena fortuna de hacer 'Métodos numéricos' como mi primer curso de computación y de ser introducido desde el principio a lo que el punto flotante puede y no puede hacer. La mayoría de los programadores son bastante vagos al respecto.
Marqués de Lorne
15
Todo lo que está haciendo es refutar que la flotación no representa muchos valores decimales exactamente, lo que espero que todos comprendamos. No es que el redondeo cause un problema. Como admite, los números todavía se imprimen como se esperaba.
Peter Lawrey
8
Su prueba está rota, tome round () y la prueba falla el 94% del tiempo. ideone.com/1y62CY imprime 100 trials 94 errorsDebería comenzar con una prueba que pase y demostrar que la introducción del redondeo interrumpe la prueba.
Peter Lawrey
66
Refutación, refutada aquí. Usando Math.round para este rango de doublesin errores ideone.com/BVCHh3
Peter Lawrey
83

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

usuario593581
fuente
2
Creo que esta es la mejor solución para usuarios de Java 1.5 (y a continuación). Un comentario, no utilices el modo de redondeo HALF_EVEN ya que tiene un comportamiento diferencial para números pares e impares (2.5 vueltas a 2 mientras 5.5 vueltas a 6, por ejemplo), a menos que esto sea lo que quieres.
IcedDante
44
La primera solución es correcta: la segunda no funciona. Ver aquí para la prueba.
Marqués de Lorne
1
@EJP: Incluso la primera solución con RoundingMode.HALF_UPestá mal. Prueba con 1.505. La forma correcta es usar BigDecimal.valueOf(d).
Matthias Braun
Matthias Braun, la solución está bien, por lo tanto, 31 ups. 1.505 decimal se almacena en coma flotante doble como 1.50499998 si desea tomar 1.505 y convertir de doble a decimal, primero debe convertirlo a Double.toString (x) luego póngalo en un BigDecimal (), pero eso es extremadamente lento y, en primer lugar, anula el propósito de usar el doble de velocidad.
hamish
1
Corrió un bucle de 100k con BigDecimal (tomó 225 ms) y Math.round (2 ms) y aquí está el tiempo ... Tiempo tomado: 225 mili segundos para convertir usando a: 9232.13 Tiempo tomado: 2 mili segundos para convertir a : 9232.13 techiesinfo.com
usuario1114134
59

Puede usar la clase DecimalFormat.

double d = 3.76628729;

DecimalFormat newFormat = new DecimalFormat("#.##");
double twoDecimal =  Double.valueOf(newFormat.format(d));
JibW
fuente
¿Alguna razón por la que Double.valueOf()fue elegido Double.parseDouble()? El valueOf()método devuelve un Doubleobjeto, mientras parseDouble()que devolverá un doubleprimitivo. Con la forma en que se escribe el código actual, también aplica el desempaquetado automático al retorno para convertirlo a la primitiva que su twoDoublevariable espera, una operación adicional de código de bytes. Cambiaría la respuesta para usar parseDouble()en su lugar.
ecbrodie
Double.parseDouble()necesita Stringentrada
Ryde
38

Real How-to de Java publica 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();
Ovesh
fuente
33
double myNum = .912385;
int precision = 10000; //keep 4 digits
myNum= Math.floor(myNum * precision +.5)/precision;
Chris Cudmore
fuente
2
sí, esto es exactamente lo que hace math.round para números positivos, pero ¿lo has intentado con números negativos? la gente está usando math.round en las otras soluciones para cubrir también el caso de números negativos.
hamish
Nota: Math.floor(x + 0.5)yMath.round(x)
Peter Lawrey
30

@Milhous: el formato decimal para redondear es excelente:

También puedes usar el

DecimalFormat df = new DecimalFormat("#.00000");
df.format(0.912385);

para asegurarte de que tienes los 0 finales.

Agregaría que este método es muy bueno para proporcionar un mecanismo numérico real de redondeo, no solo visualmente, sino también durante el procesamiento.

Hipotético: debe implementar un mecanismo de redondeo en un programa GUI. Para alterar la precisión / precisión de un resultado, simplemente cambie el formato de intercalación (es decir, entre paréntesis). 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 así ("# 0. ############") e ingresa un decimal, por ejemplo, 3.1415926, por el argumento, DecimalFormat no produce basura ( p. ej. ceros finales) y devolverá: 3.1415926.. si está inclinado de esa manera. De acuerdo, es un poco detallado para el gusto de algunos desarrolladores, pero bueno, tiene poca huella de memoria durante el procesamiento y es muy fácil de implementar.]

Entonces, esencialmente, la belleza de DecimalFormat es que maneja simultáneamente la apariencia de la cuerda, así como el nivel de precisión de redondeo establecido. Ergo: obtienes dos beneficios por el precio de la implementación de un código. ;)

Ivan
fuente
2
Si realmente desea números decimales para el cálculo (y no solo para la salida), no use un formato de punto flotante basado en binario como double. Utilice BigDecimal o cualquier otro formato basado en decimales.
Paŭlo Ebermann
20

Aquí hay un resumen de lo que puede usar si desea el resultado como Cadena:

  1. DecimalFormat # setRoundingMode () :

    DecimalFormat df = new DecimalFormat("#.#####");
    df.setRoundingMode(RoundingMode.HALF_UP);
    String str1 = df.format(0.912385)); // 0.91239
  2. BigDecimal # setScale ()

    String str2 = new BigDecimal(0.912385)
        .setScale(5, BigDecimal.ROUND_HALF_UP)
        .toString();

Aquí hay una sugerencia de qué bibliotecas puede usar si lo desea doublecomo resultado. Sin embargo, no lo recomendaría para la conversión de cadenas, ya que double puede no ser capaz de representar exactamente lo que quieres (ver, por ejemplo, aquí )

  1. Precisión de Apache Commons Math

    double rounded = Precision.round(0.912385, 5, BigDecimal.ROUND_HALF_UP);
  2. Funciones del potro

    double rounded = Functions.round(0.00001).apply(0.912385)
  3. Utilidades de Weka

    double rounded = Utils.roundDouble(0.912385, 5)
Mifeet
fuente
18

Puede 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;
}
Amit
fuente
@mariolpantunes: fallará. Prueba esto: round(1.005,2);oround(0.50594724957626620092, 20);
Matthias Braun
Funciona. Pero, de manera poco informativa, flotar y los dobles son aproximaciones. Consideremos su primer ejemplo. Si imprime la salida de PDsInZeroDP interesados ​​antes de Math.round, imprimirá 100.49999999999999. Perdiste precisión como tal Math.round alrededor de 100. Debido a la naturaleza o flotadores y dobles hay casos límite cuando no funciona correctamente (más información aquí en.wikipedia.org/wiki/Floating_point#Accuracy_problems )
mariolpantunes
¡el doble es un ayuno! el decimal es lento Las computadoras no se molestan en procesar su pensamiento en notación decimal. tienes que renunciar a cierta precisión decimal para mantener el punto flotante doblemente rápido.
hamish
@hamish La pregunta es sobre precisión, no sobre velocidad.
Marqués de Lorne
13

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

Ver también, https://stackoverflow.com/a/22186845/212950 Gracias a jpdymond por ofrecer esto.

MAbraham1
fuente
7

Pruebe esto: org.apache.commons.math3.util.Precision.round (doble x, escala int)

Ver: http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math3/util/Precision.html

La página de inicio de la Biblioteca de Matemáticas Apache Commons 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;
        }
    }
}
Li Ying
fuente
7

Como no encontré una respuesta completa sobre este tema, reuní una clase que debería manejar esto correctamente, con soporte para:

  • Formateo : formatee fácilmente un doble en una cadena con un cierto número de decimales
  • Análisis : analiza 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), "");
    }

}
Andrei Lupsa
fuente
7

Si realmente desea números decimales para el cálculo (y no solo para la salida), no use un formato de punto flotante basado en binario como double.

Use BigDecimal or any other decimal-based format.

Sí uso 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 de números muy grandes.

De hecho, recientemente utilicé analizado a largo para obtener representaciones precisas (en lugar de resultados hexadecimales) en una GUI para números tan grandes como ################## ############### caracteres (como ejemplo).

Ivan
fuente
6

Para lograr esto, podemos usar este formateador:

 DecimalFormat df = new DecimalFormat("#.00");
 String resultado = df.format(valor)

o:

DecimalFormat df = new DecimalFormat("0.00"); :

Use 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

demostración en línea.

Jorgesys
fuente
5

En caso de que alguien todavía necesite 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 a String con la salida deseada.

Asher M. O
fuente
Incluya su razón para votar en el comentario, de lo contrario, eso es lo que llamamos intimidación.
Asher M. O
4

Estoy de acuerdo con la respuesta elegida para usar DecimalFormat--- o alternativamente BigDecimal.

Por favor lea Actualización primero la continuación!

Sin embargo, si no desea redondear el valor doble y obtener un doubleresultado 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 DoubleRounderutilidad proporciona un método similar pero rápido y libre de basura en la biblioteca decimal4j:

 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 señaló @iaforek, DoubleRounder a veces devuelve resultados contraintuitivos. La razón es que realiza redondeos matemáticamente correctos. Por ejemploDoubleRounder.round(256.025d, 2) , se redondeará hacia abajo a 256.02 porque el valor doble representado como 256.025d es algo más pequeño que el valor racional 256.025 y, por lo tanto, se redondeará hacia abajo.

Notas:

  • Este comportamiento es muy similar al del BigDecimal(double)constructor (pero no al valueOf(double)que usa el constructor de cadenas).
  • El problema se puede eludir con un paso de doble redondeo a una mayor precisión 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 .

marco
fuente
¿Tiene métricas que muestran qué tan eficiente es su solución en comparación con las otras?
iaforek
No lo he comparado con otras soluciones, pero hay un punto de referencia jmh disponible en el código fuente: github.com/tools4j/decimal4j/blob/master/src/jmh/java/org/… Ejecuté el punto de referencia en una VM , los resultados están disponibles como archivo csv aquí: github.com/tools4j/decimal4j/wiki/Performance
marco
1
DoubleRounder falla en los siguientes casos: DoubleRounder.round (256.025d, 2) - esperado: 256.03, real: 256.02 o para DoubleRounder.round (260.775d, 2) - esperado: 260.78, real: 260.77.
iaforek
@iaforek: esto es correcto, porque DoubleRounder realiza un redondeo matemáticamente correcto. Sin embargo, admito que esto es algo contradictorio y, por lo tanto, actualizaré mi respuesta en consecuencia.
marco
3

El fragmento de código a continuación muestra cómo mostrar n dígitos. El truco es establecer la variable pp en 1 seguido de n ceros. En el siguiente ejemplo, el valor de pp variable 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();
Jasdeep Singh
fuente
3

Si está usando DecimalFormatpara convertir doublea 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 RoundingModevalores de enumeración para seleccionar, dependiendo del comportamiento que requiera.

Drew Noakes
fuente
3

Vine aquí solo queriendo 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. Un .5valor se redondea. Si necesita un comportamiento de redondeo diferente al anterior, puede usar uno de los otros Math funciones . Vea la comparación a continuación.

redondo

Como se indicó anteriormente, esto se redondea al número entero más cercano. .5decimales redondeados. 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

fortificar techo

Cualquier valor decimal se redondea al siguiente entero. Se va al 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

suelo

Cualquier valor decimal se redondea al siguiente número 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 que los valores decimales redondean al entero más cercano. Sin embargo, a diferencia de round, los .5valores 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 ***
Suragch
fuente
1
solo está resolviendo el caso particular de redondeo a 0 decimales. La pregunta original es más genérica.
lukas84
3

Si está utilizando una tecnología que tiene un JDK mínimo. Aquí hay una manera sin ninguna biblioteca de Java:

double scale = 100000;    
double myVal = 0.912385;
double rounded = (int)((myVal * scale) + 0.5d) / scale;
Craigo
fuente
Esto fallaría en casos donde myVal no sea menor que 1 y con ceros después del valor decimal más allá de la escala. Digamos que tiene myVal = 9.00000000912385; Lo anterior devolverá 9.0. Creo que deberíamos proporcionar una solución que funcione en todos los casos de myVal. No específicamente para el valor que declaró.
tavalendo
@ user102859 En su ejemplo, 9.0 es el resultado correcto. No entiendo cómo fallaría esto.
Craigo
2

DecimalFormat es la mejor forma de salida, pero no lo prefiero. Siempre hago esto todo el tiempo, porque devuelve el doble valor. Entonces puedo usarlo más que solo la salida.

Math.round(selfEvaluate*100000d.0)/100000d.0;

O

Math.round(selfEvaluate*100000d.0)*0.00000d1;

Si necesita un valor de lugares decimales grande, puede usar BigDecimal en su lugar. De todos modos .0es importante. Sin ella, el redondeo de 0.33333d5 devuelve 0.33333 y solo se permiten 9 dígitos. La segunda función sin .0tiene problemas con 0.30000 return 0.30000000000000004.

Se Song
fuente
2

Aquí está mi respuesta:

double num = 4.898979485566356;
DecimalFormat df = new DecimalFormat("#.##");      
time = Double.valueOf(df.format(num));

System.out.println(num); // 4.89
MD Jamal Uddin
fuente
1

Entonces, después de leer la mayoría de las respuestas, me di cuenta de que la mayoría de ellas no serán precisas, de hecho, usarlas BigDecimalparece ser la mejor opción, pero si no comprende cómo RoundingModefunciona, inevitablemente perderá precisión. Me di cuenta de esto cuando trabajaba con grandes números en un proyecto y pensé que podría ayudar a otros a tener problemas para redondear números. Por ejemplo.

BigDecimal bd = new BigDecimal("1363.2749");
bd = bd.setScale(2, RoundingMode.HALF_UP);
System.out.println(bd.doubleValue());

Esperaría obtener 1363.28como salida, pero terminará con lo 1363.27que no se espera si no sabe lo que RoundingModeestá haciendo. Entonces, mirando los documentos de Oracle , encontrará la siguiente descripción RoundingMode.HALF_UP.

Modo de redondeo para redondear hacia el "vecino más cercano" a menos que ambos vecinos sean equidistantes, en cuyo caso redondear hacia arriba.

Entonces, sabiendo esto, nos dimos cuenta de que no obtendremos un redondeo exacto, a menos que queramos redondear hacia el vecino más cercano . Entonces, para lograr una ronda adecuada, tendríamos que recorrer desde el n-1decimal hacia los decimales deseados. Por ejemplo.

private double round(double value, int places) throws IllegalArgumentException {

    if (places < 0) throw new IllegalArgumentException();

    // Cast the number to a String and then separate the decimals.
    String stringValue = Double.toString(value);
    String decimals = stringValue.split("\\.")[1];

    // Round all the way to the desired number.
    BigDecimal bd = new BigDecimal(stringValue);
    for (int i = decimals.length()-1; i >= places; i--) {
        bd = bd.setScale(i, RoundingMode.HALF_UP);
    }

    return bd.doubleValue();
}

Esto terminará dándonos el resultado esperado, que sería 1363.28.

Alain Cruz
fuente
0

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;
objetivo
fuente
1
Produce 1.0para value = 1.005y dp = 2. Use esto en su lugar.
Matthias Braun
está bien Matt, tu ejemplo no es válido. porque 1.005 no puede representarse en coma flotante doble de todos modos. tiene que almacenarse realmente por encima o por debajo de 1.005, es decir, se almacena como el doble cuando compila: 1.0049998 (no se almacena como decimal en su código compilado como diría que creen los lectores) el objetivo es correcto, está almacenando valores como punto flotante doble, donde los casos marginales como el tuyo son insignificantes de todos modos. si lo fuera, estaría usando 3dp y luego lo convertiría a decimal, luego haría una función de redondeo decimal, al igual que el enlace que publicó.
hamish
1
@hamish No veo dónde Matthias 'haría que los lectores creyeran' algo así como que el valor se compila como decimal. No ponga palabras en la boca de otras personas.
Marqués de Lorne
0

Tenga en cuenta que String.format () y DecimalFormat producen cadenas usando la configuración regional predeterminada. Por lo tanto, pueden escribir números formateados con punto o coma como separador entre las partes enteras y decimales. Para asegurarse de que la cadena redondeada esté en el formato que desee, use java.text.NumberFormat de esta manera:

  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 (no importa cuál sea su configuración regional): 0.99 123.57 123.00

El ejemplo está tomado de Farenda: cómo convertir el doble a la cadena correctamente .

pwojnowski
fuente
0

En general, el redondeo se realiza escalando: 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
Amr Ali
fuente