Formatee óptimamente un BigDecimal de acuerdo con un límite máximo de caracteres

Me gustaría convertir un valor BigDecimal en una cadena que no tenga más de un cierto número de caracteres, cambiando a notación científica si es necesario. ¿Cómo puedo hacer esto?

En otras palabras, ¿cómo puedo escribir la formatDecimal(BigDecimal number, int maxChars)función que pasaría las siguientes pruebas:

    final int maxChars = 6;

    assertEquals(
            "0.3333",
            formatDecimal(new BigDecimal(0.3333333333333), maxChars)
            );

    assertEquals(
            "1.6E+6", 
            formatDecimal(new BigDecimal(1555555), maxChars)
            );

    assertEquals(
            "1.6E-5", 
            formatDecimal(new BigDecimal(0.0000155), maxChars)
            );

    assertEquals(
            "1234.6", 
            formatDecimal(new BigDecimal(1234.56789), maxChars)
            );

    assertEquals(
            "123", 
            formatDecimal(new BigDecimal(123), maxChars)
            );

    assertEquals(
            "0", 
            formatDecimal(new BigDecimal(0), maxChars)
            );

En mi aplicación, maxCharsrealmente no necesita ser un parámetro: se solucionará en el momento de la compilación (a 18), por lo que una solución que use, por ejemplo, DecimalFormat("#.####")podría funcionar, siempre que el cambio a la notación científica se pueda administrar correctamente.

Respuesta 1

Una solución trivial si desea limitar solo el número de decimales sería

public static String formatDecimal(BigDecimal b, int max) {
    return b.setScale(max, RoundingMode.HALF_EVEN).stripTrailingZeros().toEngineeringString();
}

Ahora, también desea limitar el número de dígitos de fracción dependiendo de los dígitos en la parte entera. Creo que es posible jugar mejor con las clases DecimalFormat y MessageFormat, esto es lo que puedo ofrecer, pasa los casos de prueba, pero no creo que sea tan robusto. Puede intentar crear un mejor algoritmo para manejar todos los casos diferentes.

public static String formatDecimal(BigDecimal b, int max) {
    // trivial case
    String bs = b.stripTrailingZeros().toPlainString();
    if (bs.length() <= max) {
        return bs;
    }
    // determine the max integer = 1.0Emax
    String maxInteger = "1" + StringUtils.repeat("0", max - 1);
    // determine the min fraction = 1.0E-max
    String minFraction = "0." + StringUtils.repeat("0", max - 2) + "1";
    // get the integer part
    String integerPart = String.valueOf(b.intValue());
    // make the pattern like ###.### with the correct repetition
    String pattern = StringUtils.repeat("#", max - integerPart.length()) + "." + StringUtils.repeat("#", max - 1 - integerPart.length());
    // play with Message format, using a choice to determine when to use the exponential format
    MessageFormat fmt = new MessageFormat( //
            "{0,choice," + minFraction + "<{0,number,'0.#E0'}|0.1#{0,number,'" + pattern + "'}|" + maxInteger + "<{0,number,'0.#E0'}}" //
    );
    // time to format the number
    return fmt.format(new Object[] {b});
}

Otra solución usando if / else podría iniciarse así:

public static String formatDecimal(BigDecimal b, int max) {
    // trivial case
    if (b.toPlainString().length() <= max)
        return b.toPlainString();
    else {
        // between 0 and 1, or superior than 1*10^max, better use the exponential notation
        if ((b.compareTo(BigDecimal.ZERO) > 0 && b.compareTo(new BigDecimal("0.01")) < 0) //
                || (b.compareTo(new BigDecimal("1" + StringUtils.repeat("0", max - 1))) > 0)) {
            return new DecimalFormat("#.#E0").format(b);
        } else { // set Scale for fraction, better keep integer part safe
            String sb = b.toPlainString();
            return b.setScale(max - sb.indexOf(".") - 1, RoundingMode.HALF_EVEN).stripTrailingZeros().toPlainString();
        }
    }
}
Respuesta: 2
public static String formatDecimal(BigDecimal bd, int maxChars)
{
 String convert = bd.toString();
 String result=" ";
 char[] cArray = convert.toCharArray();
 int decPos = -1;
 int chrsBefore = 0;
 int chrsAfter = 0;
 int zeroesAfter = 0;
 int currentPos = 0;
 boolean zeroStreakAlive = true;

 if (cArray.length>maxChars){
    for (char c : cArray){
        if (c=='.')
            decPos = currentPos;
        else if (decPos == -1)
            chrsBefore++;
        else if (zeroStreakAlive && c=='0'){
            zeroesAfter++;
            chrsAfter++;   
        }
        else
            chrsAfter++;
        currentPos++;
    }

    if (chrsBefore>maxChars)
        result = cArray[0] + "." + cArray[1] + "E+" + (chrsBefore-1);
    else if (zeroesAfter >= maxChars-2)
        result = cArray[zeroesAfter+2] + "." + cArray[zeroesAfter+3] + "E-" + (chrsAfter-2);
    else 
        //your logic here probably using DecimalFormat for a normally rounded number to 6 places(including decimal point if one exists).
 }
 return result;
}
}

Esto es lo que se me ocurrió. No todo es abarcativo, ya que hay muchos casos que abordar. No sé exactamente cómo quieres que se desarrollen todos, así que no puedo hacerlos todos, pero deberías ser capaz de tener la idea y comenzar desde aquí.

Respuesta: 3

Instalé QGIS a través de ppa: ubuntugis / ubuntugis-inestable. Mi Ubuntu 12.04 viene con OpenJDK 1.7.0. Cuando inicio qgis desde la línea de comando, se devuelve el siguiente mensaje de error. /usr/bin/qgis.bin: ...

Estoy tratando de descubrir cómo hacer texto no editable (no un JTextField) cuyo color de fondo cambia cuando el mouse pasa sobre él. Intenté usar JButton implementando ActionListener y ocultando ...

Yo uso Apache POI en un proyecto Java. He trabajado en una página horizontal, con el siguiente código: private void changeOrientation (documento XWPFDocument, orientación de cadena) {CTDocument1 doc = ...

Tengo una aplicación web basada en Java EE que se ejecuta en Tomcat y Spring 3.0. Mi página web envía una solicitud para eliminar un gran conjunto de registros. Mientras la solicitud se ejecuta en segundo plano, se agota el tiempo de espera ...