2009-06-08 9 views
12

En Europa, los decimales se separan con ',' y utilizamos '' opcional. 'para separar miles. Dejo que los valores de moneda con:Cómo analizar una cantidad de moneda (EE. UU. O UE) para flotar valor en Java

  • EE.UU.-estilo 123,456.78 notación
  • estilo europeo 123.456,78 notación

utilizo el siguiente expresión regular (de biblioteca RegexBuddy) para validar la entrada. Permití fracciones opcionales de dos dígitos y separadores de miles opcionales.

^[+-]?[0-9]{1,3}(?:[0-9]*(?:[.,][0-9]{0,2})?|(?:,[0-9]{3})*(?:\.[0-9]{0,2})?|(?:\.[0-9]{3})*(?:,[0-9]{0,2})?)$ 

Me gustaría analizar una cadena de moneda a un flotador. Por ejemplo

123,456.78 deben almacenarse como 123.456,78
123.456,78 deben almacenarse como 123.456,78
123,45 deben ser almacenados como 123.45
1.234 debe ser almacenada como 1234 12,34 deben ser almacenados como 12,34

y etc ...

¿Hay una manera fácil de hacer esto en Java?

public float currencyToFloat(String currency) { 
    // transform and return as float 
} 

Uso BigDecimal en lugar de flotador


Gracias a todos por las grandes respuestas. He cambiado mi código para usar BigDecimal en lugar de float. Mantendré flotante la parte anterior de esta pregunta para evitar que la gente cometa los mismos errores que yo.

Solución


El código siguiente muestra una función que transforma de Estados Unidos y la moneda de la UE a una cadena aceptada por el constructor BigDecimal (String). Eso es decir una cuerda sin separador de mil y un punto para fracciones.

import java.util.regex.Matcher; 
import java.util.regex.Pattern; 


public class TestUSAndEUCurrency { 

    public static void main(String[] args) throws Exception {  
     test("123,456.78","123456.78"); 
     test("123.456,78","123456.78"); 
     test("123.45","123.45"); 
     test("1.234","1234"); 
     test("12","12"); 
     test("12.1","12.1"); 
     test("1.13","1.13"); 
     test("1.1","1.1"); 
     test("1,2","1.2"); 
     test("1","1");    
    } 

    public static void test(String value, String expected_output) throws Exception { 
     String output = currencyToBigDecimalFormat(value); 
     if(!output.equals(expected_output)) { 
      System.out.println("ERROR expected: " + expected_output + " output " + output); 
     } 
    } 

    public static String currencyToBigDecimalFormat(String currency) throws Exception { 

     if(!doesMatch(currency,"^[+-]?[0-9]{1,3}(?:[0-9]*(?:[.,][0-9]{0,2})?|(?:,[0-9]{3})*(?:\\.[0-9]{0,2})?|(?:\\.[0-9]{3})*(?:,[0-9]{0,2})?)$")) 
       throw new Exception("Currency in wrong format " + currency); 

     // Replace all dots with commas 
     currency = currency.replaceAll("\\.", ","); 

     // If fractions exist, the separator must be a . 
     if(currency.length()>=3) { 
      char[] chars = currency.toCharArray(); 
      if(chars[chars.length-2] == ',') { 
       chars[chars.length-2] = '.'; 
      } else if(chars[chars.length-3] == ',') { 
       chars[chars.length-3] = '.'; 
      } 
      currency = new String(chars); 
     } 

     // Remove all commas   
     return currency.replaceAll(",", "");     
    } 

    public static boolean doesMatch(String s, String pattern) { 
     try { 
      Pattern patt = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); 
      Matcher matcher = patt.matcher(s); 
      return matcher.matches(); 
     } catch (RuntimeException e) { 
      return false; 
     }   
    } 

} 
+1

¿A qué se convierte 1.23? Su conjunto de reglas es contradictorio. ¿Por qué no aprovechar las instalaciones de localización en el extremo del cliente de su aplicación? – spender

+0

1.23 convierte a 1.23 –

+0

OK, mi mal ... pero en realidad, tratando de descifrar esto sin saber que el lugar del que vino tiene un mal olor. – spender

Respuesta

31

para responder a una pregunta un poco diferente: no utilizar el tipo de flotador para representar los valores de moneda. It will bite you. Utilice un tipo base-10 en su lugar, como BigDecimal, o un tipo entero como int o long (que representa la cantidad de su valor, centavo, por ejemplo, en moneda estadounidense).

No podrá almacenar un valor exacto - 123.45, por ejemplo, como un flotante, y las operaciones matemáticas en ese valor (como la multiplicación por un porcentaje de impuesto) producirán errores de redondeo.

Ejemplo de esa página:

float a = 8250325.12f; 
float b = 4321456.31f; 
float c = a + b; 
System.out.println(NumberFormat.getCurrencyInstance().format(c)); 
// prints $12,571,782.00 (wrong) 

BigDecimal a1 = new BigDecimal("8250325.12"); 
BigDecimal b1 = new BigDecimal("4321456.31"); 
BigDecimal c1 = a1.add(b1); 
System.out.println(NumberFormat.getCurrencyInstance().format(c1)); 
// prints $12,571,781.43 (right) 

Usted no quiere ensuciar con errores cuando se trata de dinero.

Con respecto a la pregunta original, no he tocado Java en un momento, pero sé que me gustaría mantenerme alejado de Regex para hacer este tipo de trabajo. Veo esto recomendado; te puede ayudar No probado; desarrollador de advertencias

try { 
    String string = NumberFormat.getCurrencyInstance(Locale.GERMANY) 
              .format(123.45); 
    Number number = NumberFormat.getCurrencyInstance(locale) 
              .parse("$123.45"); 
    // 123.45 
    if (number instanceof Long) { 
     // Long value 
    } else { 
     // too large for long - may want to handle as error 
    } 
} catch (ParseException e) { 
// handle 
} 

Busque una configuración regional que coincida con lo que espera ver. Si no puede encontrar uno, use varios de forma secuencial o cree su propio custom NumberFormat.

También consideraría forzar a los usuarios a ingresar valores en un solo formato canónico. 123.45 y 123.456 mira manera demasiado similar para mis gustos, y según sus reglas resultaría en valores que difieren en un factor de 1000. This is how millions are lost.

+0

Déjame ver, si te entiendo. Quiere decir que debería usar int y almacenar en mi base de datos el valor en centavos. 12.35 como 12 * 100 + 35 = 1235. –

+0

Suena inteligente, pero necesitaría una función para convertir de una cadena de moneda a centavos –

+0

Lo haría, sí. Lo que escribí arriba no aborda eso. Te vi sugiriendo usar float para manejar dinero, y se dispararon las alarmas. –

-1

Una rápida un truco sucio podría ser:

String input = input.replaceAll("\.,",""); // remove *any* , or . 
long amount = Long.parseLong(input); 

BigDecimal bd = BigDecimal.valueOf(amount).movePointLeft(2); 

//then you could use: 
bd.floatValue(); 
//but I would seriously recommended that you don't use floats for monetary amounts. 

Nota esto sólo funcionará si la entrada está en la forma ### 00, es decir, con exactamente 2 cifras decimales.. Por ejemplo, input == "10,022" romperá este código bastante ingenuo.

Alternativa es utilizar el constructor BigDecimal(String), pero tendrá que convertir esos números de estilo de euro para usar '.' como el separador decimal, además de eliminar los mil separadores para ambos.

+0

> Aternative es usar el constructor BigDecimal (String), pero necesitará convertir esos números de estilo de euro para usar '.' como el separador decimal, además de eliminar los mil separadores para ambos. Podría hacer una expresión regular para reemplazar, más dos últimos dígitos con. más los últimos dos dígitos Podría reemplazar todos los puntos y comas con nada más que no. llegando antes de dos últimos dígitos. Debería funcionar, pero parece propenso a errores. –

1

Como solución generalizada puede intentar

char[] chars = currency.toCharArray(); 
chars[currency.lastIndexOf(',')] = '.'; 
currency = new String(chars); 

en lugar de

if(currency.length()>=3) { 
    char[] chars = currency.toCharArray(); 
    if(chars[chars.length-2] == ',') { 
     chars[chars.length-2] = '.'; 
    } else if(chars[chars.length-3] == ',') { 
     chars[chars.length-3] = '.'; 
    } 
    currency = new String(chars); 
} 

de modo que parte fraccional puede ser de cualquier longitud.

Cuestiones relacionadas