【问题标题】:NumberFormat.parse() fails for some currency stringsNumberFormat.parse() 对某些货币字符串失败
【发布时间】:2013-03-13 05:45:13
【问题描述】:

我有一个简单的EditText,它允许用户输入一个数字,例如45.60(例如美元)。然后我使用以下方法格式化这个数字:

public String format() {
    NumberFormat formatter = NumberFormat.getCurrencyInstance(Locale.getDefault());
    return formatter.format(amount.doubleValue());
}

在我的 Android 手机上,语言设置为英语(美国) - 因此 Locale.getDefault() 应该返回美国区域设置(确实如此)。

现在编辑文本已正确更新为:$45.60(因此格式化输入的数字有效)。

但是,如果我尝试使用以下方法解析上述字符串"$45.60"

NumberFormat numberFormat = NumberFormat.getInstance(Locale.getDefault());
Number result = numberFormat.parse("$45.60");

它失败了:

java.lang.IllegalArgumentException: Failed to parse amount $45.60 using locale en_US.

如果我将手机设置为英语/英国,则将此 "45.60" 格式化为 "£45.60" 可以正常工作(对于美国而言),但解析 "£45.60" 会失败,就像上面美国示例一样。

但是,如果我将手机设置为德语(德国),则将"45,60" 格式化为"45,60€" 可以正常工作,并且解析"45,60€" 也可以正常工作!

我看到这三种货币之间的唯一区别:欧元附加在金额后,而美元和英镑附加在金额前。

有没有人知道,为什么相同的代码适用于欧元,但不适用于英镑和美元?我错过了什么吗?

我还创建了一个单元测试来重现该问题:

public void testCreateStringBased() throws Exception {

    // For German locale
    CurrencyAmount amount = new CurrencyAmount("25,46€", Locale.GERMANY);
    assertEquals(25.46, amount.getAsDouble());

    // For French locale
    amount = new CurrencyAmount("25,46€", Locale.FRANCE);
    assertEquals(25.46, amount.getAsDouble());

    // For US locale
    amount = new CurrencyAmount("$25.46", Locale.US);
    assertEquals(25.46, amount.getAsDouble());

    // For UK locale
    amount = new CurrencyAmount("£25.46", Locale.UK);
    assertEquals(25.46, amount.getAsDouble());
}

CurrencyAmount 基本上包装了我发布的用于解析货币字符串的代码,只是它采用给定的语言环境而不是默认的语言环境。在上面的示例中,德国和法国地区的测试成功,但美国和英国地区的测试失败。

【问题讨论】:

    标签: java android currency numberformatexception


    【解决方案1】:

    尝试 NumberFormat.getCurrencyInstance().parse() 代替 NumberFormat.getInstance().parse()。

    【讨论】:

    • 好的,试过了,没用。我需要能够使用相同的代码解析 $45.65 和 45.65。如果我按照您的建议更改代码,则解析数字字符串(没有货币符号)将不再有效。并且:在这些更改之后,我可以解析 $45.56 但 45,56€ 不再起作用 oO ...
    • 这是 Java 解析例程的限制。对于您的特殊要求,您应该同时尝试。
    • 感谢您的澄清。但是在这种情况下,我认为我最好简单地替换货币符号:) - 因为这是一回事,适用于每种情况。而且我觉得有点麻烦,如果我需要两种不同的方法来做同样的事情(解析货币字符串),只是因为有些货币有一个前置货币符号,而另一些有它附加 :/ 。从逻辑上讲,它应该很简单:每种货币解析都有一个例程。由于它是基于语言环境的,这应该没有问题,但遗憾的是它并不是那么简单。
    【解决方案2】:

    尝试以下操作:

    NumberFormat numberFormat = new DecimalFormat("¤#.00", new DecimalFormatSymbols(Locale.UK));
    numberFormat.parse("£123.5678");
    

    ¤ - 货币符号,期望与 Locale 的货币符号匹配。

    您可以通过以下链接查看其他模式符号http://docs.oracle.com/javase/6/docs/api/java/text/DecimalFormat.html

    【讨论】:

    • 感谢您的建议,它适用于美元和英镑,但不适用于欧元。我怀疑,因为在欧元的情况下,货币符号是附加的,而不是附加的?我宁愿寻找通用的东西。我唯一能想到的另一件事是使用正则表达式去除货币符号并解析值:/但我宁愿避免这种情况......
    • 另一种方法:String value = "123.5678€".replace(new DecimalFormatSymbols(Locale.GERMANY).getCurrencySymbol(), ""); BigDecimal bigDecimal = new BigDecimal(value);它可以帮助您不关心当前的货币符号
    • 嘿,感谢您的建议,这几乎就是我昨天发布的内容;)(请参阅下面我自己的答案),这就是我现在正在做的事情。
    【解决方案3】:

    由于迄今为止提出的答案并没有完全解决问题,我采取了一种非常业余的方法:

    String value = "$24,76" 
    value = value.replace(getCurrencySymbol(locale), StringUtils.EMPTY);
    
    NumberFormat numberFormat = NumberFormat.getInstance(locale);
    Number result = numberFormat.parse(value);
    

    所以现在我只需将字符串值从它的货币符号中剥离出来......这样我就可以处理我想要的一切,例如:45.78 或 45,78 或 $45.78 或 45,78€ ....

    无论输入什么,货币符号都会被简单地剥离,最终得到纯数字。我的单元测试(参见 OP)现在已成功完成。

    如果有人想出更好的方法,请告诉我。

    【讨论】:

    • 我不会称其为“非常业余”。这是一个常见的用例,尤其是在解析 EditText 的输入时。我还考虑直接在 EditText(使用 TextWatcher)上强制执行正确的(带货币符号)格式,但我最终使用此解决方案对用户的干扰较小。谢谢。
    【解决方案4】:

    您必须知道要解析的字符串的区域设置,才能拥有可识别区域设置的解析器。仅当 NumberFormat 的语言环境为 en_GB 时,GBP 字符串才会解析为数字;没有“通用”解析器之类的东西。

    例如,字符串“12.000”如何解析?对于 en-us,答案是十二;对于德德,答案是一万二千。

    始终使用 NumberFormat.getCurrencyInstance( java.util.Locale ) 来解析货币金额。

    【讨论】:

      【解决方案5】:

      我在下面使用改编自https://dzone.com/articles/currency-format-validation-and

      import java.math.BigDecimal;
      import org.apache.commons.validator.routines.*;
      
      BigDecimalValidator currencyValidator = CurrencyValidator.getInstance();
      BigDecimal parsedCurrency = currencyValidator.validate(currencyString);
      if ( parsedCurrency == null ) {
              throw new Exception("Invalid currency format (please also ensure it is UTF-8)");
      }
      

      如果您需要确保每个用户使用正确的区域设置,请查看 Change locale on login

      【讨论】:

        【解决方案6】:

        抱歉,提供的任何答案都具有误导性。这就是我所说的 Java 中的 BUG。 像这样的例子更好地解释了它。如果我想使用Locale.USEUR 中打印一个值,然后再次解析它,除非我在DecimalFormat 上指定货币(EUR),否则它会失败。使用美元,它可以工作:

                DecimalFormat df = new DecimalFormat("¤#,##0.00", new DecimalFormatSymbols(Locale.US));
                df.setCurrency(Currency.getInstance("EUR"));
                BigDecimal value = new BigDecimal("1.23");
                String text = df.format(value);
                System.out.println(text);
        
                DecimalFormat df2 = new DecimalFormat("¤#,##0.00", new DecimalFormatSymbols(Locale.US));
                df2.setParseBigDecimal(true);
                BigDecimal parsed = (BigDecimal) df2.parse(text);
        
                BigDecimalAsserts.assertBigDecimalEquals("parsed value is the same of the original", value, parsed);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多