Conversión de cadena segura a BigDecimal

120

Estoy tratando de leer algunos valores BigDecimal de la cadena. Digamos que tengo esta cadena: "1,000,000,000.999999999999999" y quiero obtener un BigDecimal de ella. ¿Cuál es la forma de hacerlo?

En primer lugar, no me gustan las soluciones que usan reemplazos de cadenas (reemplazando comas, etc.). Creo que debería haber algún formateador ordenado para hacer ese trabajo por mí.

Encontré una clase DecimalFormatter, sin embargo, como funciona a través del doble, se pierden grandes cantidades de precisión.

Entonces, ¿cómo puedo hacerlo?

bezmax
fuente
Porque dado un formato personalizado, es complicado convertir su formato en un formato compatible con BigDecimal.
bezmax
2
"Porque dado un formato personalizado es un fastidio ..." No sé, separa los dominios problemáticos. Primero, limpia las cosas legibles por humanos de la cadena, luego pasa a algo que sepa cómo convertir el resultado en un archivo BigDecimal.
TJ Crowder

Respuestas:

90

Compruébalo setParseBigDecimalen DecimalFormat. Con este setter, parsete devolverá un BigDecimal.

Jeroen Rosenberg
fuente
1
El constructor de la clase BigDecimal no admite formatos personalizados. El ejemplo que di en la pregunta usa el formato personalizado (comas). No puede analizar eso en BigDecimal usando su constructor.
bezmax
24
Si va a cambiar completamente su respuesta, le sugiero que la mencione en la respuesta. De lo contrario, parece muy extraño cuando la gente ha señalado por qué su respuesta original no tenía ningún sentido. :-)
TJ Crowder
1
Sí, no me di cuenta de ese método. Gracias, esa parece ser la mejor manera de hacerlo. Lo probaré ahora y si funciona correctamente, acepte la respuesta.
bezmax
15
¿Puede dar un ejemplo?
J Woodchuck
61
String value = "1,000,000,000.999999999999999";
BigDecimal money = new BigDecimal(value.replaceAll(",", ""));
System.out.println(money);

Código completo para demostrar que no NumberFormatExceptionse lanza:

import java.math.BigDecimal;

public class Tester {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String value = "1,000,000,000.999999999999999";
        BigDecimal money = new BigDecimal(value.replaceAll(",", ""));
        System.out.println(money);
    }
}

Salida

1000000000.999999999999999

Buhake Sindi
fuente
@Steve McLeod ... no, acabo de probarlo ... mi valor es1000000000.999999999999999
Buhake Sindi
10
Pruebe con la configuración regional ALEMÁN y 1.000.000.000,999999999999
TofuBeer
@ # TofuBeer .... el ejemplo fue 1,000,000,000.999999999999999. Si tuviera que hacer su configuración regional, tendría que reemplazar todos los puntos en el espacio ...
Buhake Sindi
14
Entonces, no es seguro. No conoces todos los lugares.
ymajoros
3
Está bien si solo usa, por ejemplo, en DecimalFormatSymbols.getInstance().getGroupingSeparator()lugar de una coma, no es necesario que se asuste por las diferentes configuraciones regionales.
Jason C
22

El siguiente código de muestra funciona bien (la configuración regional debe obtenerse dinámicamente)

import java.math.BigDecimal;
import java.text.NumberFormat;
import java.text.DecimalFormat;
import java.text.ParsePosition;
import java.util.Locale;

class TestBigDecimal {
    public static void main(String[] args) {

        String str = "0,00";
        Locale in_ID = new Locale("in","ID");
        //Locale in_ID = new Locale("en","US");

        DecimalFormat nf = (DecimalFormat)NumberFormat.getInstance(in_ID);
        nf.setParseBigDecimal(true);

        BigDecimal bd = (BigDecimal)nf.parse(str, new ParsePosition(0));

        System.out.println("bd value : " + bd);
    }
}
Ebith
fuente
6

El código podría ser más limpio, pero parece funcionar para diferentes configuraciones regionales.

import java.math.BigDecimal;
import java.text.DecimalFormatSymbols;
import java.util.Locale;


public class Main
{
    public static void main(String[] args)
    {
        final BigDecimal numberA;
        final BigDecimal numberB;

        numberA = stringToBigDecimal("1,000,000,000.999999999999999", Locale.CANADA);
        numberB = stringToBigDecimal("1.000.000.000,999999999999999", Locale.GERMANY);
        System.out.println(numberA);
        System.out.println(numberB);
    }

    private static BigDecimal stringToBigDecimal(final String formattedString,
                                                 final Locale locale)
    {
        final DecimalFormatSymbols symbols;
        final char                 groupSeparatorChar;
        final String               groupSeparator;
        final char                 decimalSeparatorChar;
        final String               decimalSeparator;
        String                     fixedString;
        final BigDecimal           number;

        symbols              = new DecimalFormatSymbols(locale);
        groupSeparatorChar   = symbols.getGroupingSeparator();
        decimalSeparatorChar = symbols.getDecimalSeparator();

        if(groupSeparatorChar == '.')
        {
            groupSeparator = "\\" + groupSeparatorChar;
        }
        else
        {
            groupSeparator = Character.toString(groupSeparatorChar);
        }

        if(decimalSeparatorChar == '.')
        {
            decimalSeparator = "\\" + decimalSeparatorChar;
        }
        else
        {
            decimalSeparator = Character.toString(decimalSeparatorChar);
        }

        fixedString = formattedString.replaceAll(groupSeparator , "");
        fixedString = fixedString.replaceAll(decimalSeparator , ".");
        number      = new BigDecimal(fixedString);

        return (number);
    }
}
Tofu, cerveza
fuente
3

Así es como lo haría:

public String cleanDecimalString(String input, boolean americanFormat) {
    if (americanFormat)
        return input.replaceAll(",", "");
    else
        return input.replaceAll(".", "");
}

Obviamente, si esto fuera en código de producción, no sería tan simple.

No veo ningún problema con simplemente eliminar las comas de la cadena.

jjnguy
fuente
¿Y si la cadena no está en formato americano? En Alemania, sería 1.000.000.000,999999999999999
Steve McLeod
1
El principal problema aquí es que los números podrían obtenerse de diferentes fuentes, cada una con su propio formato. Entonces, la mejor manera de hacerlo en esta situación sería definir algún "formato numérico" para cada una de las fuentes. No puedo hacer eso con reemplazos simples ya que una fuente puede tener el formato "123 456 789" y la otra "123.456.789" uno.
bezmax
Veo que está redefiniendo silenciosamente el término "método de una línea" ... ;-)
Péter Török
@Steve: esa es una diferencia bastante fundamental que requiere un manejo explícito, no un enfoque de "expresión regular para todos".
Michael Borgwardt
2
@Max: "El principal problema aquí es que los números pueden obtenerse de diferentes fuentes, cada una con su propio formato". Lo que es un argumento a favor , más que en contra, de limpiar la entrada antes de pasarla al código que no controla.
TJ Crowder
3

Necesitaba una solución para convertir un String en un BigDecimal sin conocer la configuración regional y ser independiente de la configuración regional. No pude encontrar ninguna solución estándar para este problema, así que escribí mi propio método de ayuda. Puede que también ayude a alguien más:

Actualización: ¡Advertencia! Este método auxiliar funciona solo para números decimales, por lo que los números que siempre tienen un punto decimal. De lo contrario, el método auxiliar podría generar un resultado incorrecto para números entre 1000 y 999999 (más / menos). ¡Gracias a bezmax por su gran aportación!

static final String EMPTY = "";
static final String POINT = '.';
static final String COMMA = ',';
static final String POINT_AS_STRING = ".";
static final String COMMA_AS_STRING = ",";

/**
     * Converts a String to a BigDecimal.
     *     if there is more than 1 '.', the points are interpreted as thousand-separator and will be removed for conversion
     *     if there is more than 1 ',', the commas are interpreted as thousand-separator and will be removed for conversion
     *  the last '.' or ',' will be interpreted as the separator for the decimal places
     *  () or - in front or in the end will be interpreted as negative number
     *
     * @param value
     * @return The BigDecimal expression of the given string
     */
    public static BigDecimal toBigDecimal(final String value) {
        if (value != null){
            boolean negativeNumber = false;

            if (value.containts("(") && value.contains(")"))
               negativeNumber = true;
            if (value.endsWith("-") || value.startsWith("-"))
               negativeNumber = true;

            String parsedValue = value.replaceAll("[^0-9\\,\\.]", EMPTY);

            if (negativeNumber)
               parsedValue = "-" + parsedValue;

            int lastPointPosition = parsedValue.lastIndexOf(POINT);
            int lastCommaPosition = parsedValue.lastIndexOf(COMMA);

            //handle '1423' case, just a simple number
            if (lastPointPosition == -1 && lastCommaPosition == -1)
                return new BigDecimal(parsedValue);
            //handle '45.3' and '4.550.000' case, only points are in the given String
            if (lastPointPosition > -1 && lastCommaPosition == -1){
                int firstPointPosition = parsedValue.indexOf(POINT);
                if (firstPointPosition != lastPointPosition)
                    return new BigDecimal(parsedValue.replace(POINT_AS_STRING, EMPTY));
                else
                    return new BigDecimal(parsedValue);
            }
            //handle '45,3' and '4,550,000' case, only commas are in the given String
            if (lastPointPosition == -1 && lastCommaPosition > -1){
                int firstCommaPosition = parsedValue.indexOf(COMMA);
                if (firstCommaPosition != lastCommaPosition)
                    return new BigDecimal(parsedValue.replace(COMMA_AS_STRING, EMPTY));
                else
                    return new BigDecimal(parsedValue.replace(COMMA, POINT));
            }
            //handle '2.345,04' case, points are in front of commas
            if (lastPointPosition < lastCommaPosition){
                parsedValue = parsedValue.replace(POINT_AS_STRING, EMPTY);
                return new BigDecimal(parsedValue.replace(COMMA, POINT));
            }
            //handle '2,345.04' case, commas are in front of points
            if (lastCommaPosition < lastPointPosition){
                parsedValue = parsedValue.replace(COMMA_AS_STRING, EMPTY);
                return new BigDecimal(parsedValue);
            }
            throw new NumberFormatException("Unexpected number format. Cannot convert '" + value + "' to BigDecimal.");
        }
        return null;
    }

Por supuesto que probé el método:

@Test(dataProvider = "testBigDecimals")
    public void toBigDecimal_defaultLocaleTest(String stringValue, BigDecimal bigDecimalValue){
        BigDecimal convertedBigDecimal = DecimalHelper.toBigDecimal(stringValue);
        Assert.assertEquals(convertedBigDecimal, bigDecimalValue);
    }
    @DataProvider(name = "testBigDecimals")
    public static Object[][] bigDecimalConvertionTestValues() {
        return new Object[][] {
                {"5", new BigDecimal(5)},
                {"5,3", new BigDecimal("5.3")},
                {"5.3", new BigDecimal("5.3")},
                {"5.000,3", new BigDecimal("5000.3")},
                {"5.000.000,3", new BigDecimal("5000000.3")},
                {"5.000.000", new BigDecimal("5000000")},
                {"5,000.3", new BigDecimal("5000.3")},
                {"5,000,000.3", new BigDecimal("5000000.3")},
                {"5,000,000", new BigDecimal("5000000")},
                {"+5", new BigDecimal("5")},
                {"+5,3", new BigDecimal("5.3")},
                {"+5.3", new BigDecimal("5.3")},
                {"+5.000,3", new BigDecimal("5000.3")},
                {"+5.000.000,3", new BigDecimal("5000000.3")},
                {"+5.000.000", new BigDecimal("5000000")},
                {"+5,000.3", new BigDecimal("5000.3")},
                {"+5,000,000.3", new BigDecimal("5000000.3")},
                {"+5,000,000", new BigDecimal("5000000")},
                {"-5", new BigDecimal("-5")},
                {"-5,3", new BigDecimal("-5.3")},
                {"-5.3", new BigDecimal("-5.3")},
                {"-5.000,3", new BigDecimal("-5000.3")},
                {"-5.000.000,3", new BigDecimal("-5000000.3")},
                {"-5.000.000", new BigDecimal("-5000000")},
                {"-5,000.3", new BigDecimal("-5000.3")},
                {"-5,000,000.3", new BigDecimal("-5000000.3")},
                {"-5,000,000", new BigDecimal("-5000000")},
                {null, null}
        };
    }
usuario3227576
fuente
1
Lo siento, pero no veo cómo esto puede ser útil debido a casos extremos. Por ejemplo, ¿cómo sabes qué 7,333representa? ¿Es 7333 o 7 y 1/3 (7.333)? En diferentes lugares, ese número se analizaría de manera diferente. Aquí hay una prueba de concepto: ideone.com/Ue9rT8
bezmax
Buen input, nunca tuve este problema en la práctica. Actualizaré mi publicación, ¡muchas gracias! Y mejoraré las pruebas para estos especiales de Locale solo para saber dónde recibiré los problemas.
user3227576
1
He comprobado la solución para diferentes Locales y diferentes números ... y para todos nosotros funciona, porque siempre tenemos números con un punto decimal (estamos leyendo valores monetarios de un archivo de texto). He añadido una advertencia a la respuesta.
user3227576
2
resultString = subjectString.replaceAll("[^.\\d]", "");

eliminará todos los caracteres excepto los dígitos y el punto de su cadena.

Para que sea compatible con la configuración regional, es posible que desee utilizar getDecimalSeparator()from java.text.DecimalFormatSymbols. No sé Java, pero podría verse así:

sep = getDecimalSeparator()
resultString = subjectString.replaceAll("[^"+sep+"\\d]", "");
Tim Pietzcker
fuente
Lo que dará resultados inesperados para números no estadounidenses: vea los comentarios a la respuesta de Justin.
Péter Török
2

Tema antiguo, pero quizás el más fácil es usar Apache commons NumberUtils que tiene un método createBigDecimal (valor de cadena) ....

Supongo (espero) que tenga en cuenta las configuraciones regionales o de lo contrario sería bastante inútil.

Lawrence
fuente
createBigDecimal es solo un contenedor para el nuevo BigDecimal (cadena), como se indica en el código fuente de NumberUtils que no considera la configuración regional AFAIK
Mar Bar
1

Por favor, intente esto, funciona para mí

BigDecimal bd ;
String value = "2000.00";

bd = new BigDecimal(value);
BigDecimal currency = bd;
Rajkumar Teckraft
fuente
2
De esta forma no se admite la especificación de delimitadores personalizados para grupos de separadores decimales y de manos.
bezmax
Sí, pero esto es para obtener el valor de cadena BigDecimal de un objeto como HashMap y almacenarlo en el valor Bigdecimal para llamar a los otros servicios
Rajkumar Teckraft
1
Sí, pero esa no era la cuestión.
bezmax
También funciona para mí
Sábado