round - truncar decimales java




Cómo redondear un número a n lugares decimales en Java (20)

@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:

  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 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 ):

  1. Precision de Apache Commons Math

    double rounded = Precision.round(0.912385, 5, BigDecimal.ROUND_HALF_UP);
    
  2. Functions de Colt

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

    double rounded = Utils.roundDouble(0.912385, 5)
    

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), "");
    }

}

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.



Puedes usar la clase DecimalFormat.

double d = 3.76628729;

DecimalFormat newFormat = new DecimalFormat("#.##");
double twoDecimal =  Double.valueOf(newFormat.format(d));

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


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

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

demo en línea.


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();

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;

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




digits