Java - Decimal Format.parse方法返回指定小数位数的double值

14

我希望能够将一个字符串按照格式字符串中指定的小数位数转换为Double类型,例如"###,##0.000"应该返回保留3位小数的Double

编辑 - 添加更多有关操作的信息

用户在UI中输入值,该值被存储为字符串。规则是该值限制为3位小数。底层代码将该值存储在数据库中,然后用于计算。因此,多余的小数位将导致计算结果与预期有所偏差。

我有以下代码:

try {
        // output current locale we are running under (this happens to be "nl_BE")
        System.out.println( "Current Locale is " + Locale.getDefault().toString() );

        // number in Central European Format with a format string specified in UK format
        String numberCE = "1,234567"; // 1.234567
        String formatUK = "###,##0.000";

        // do the format
        DecimalFormat formatterUK = new DecimalFormat( formatUK );
        Double valCEWithUKFormat = formatterUK.parse( numberCE ).doubleValue();

        // I want the number to DPs in the format string!!!
        System.out.println( "CE Value     " + numberCE + " in UK format (" + formatUK + ") is "
        + valCEWithUKFormat );

    } catch( ParseException ex ) {
        System.out.println("Cannot parse number");
    }
}

DecimalFormat似乎忽略了格式字符串并将完整的字符串作为1.234567的Double返回。

是否可以强制DecimalFormat在解析时使用格式字符串?我有什么遗漏的吗?

干杯,

Andez


你真的想尝试做这个吗?因为从十进制转换为二进制(内部表示)时精度损失很大,你可能得不到你期望的结果。 - Raedwald
4个回答

19

DecimalFormat有两个不同的用途:解析输入和格式化输出。如果你想同时完成这两个任务,你需要使用两个格式对象。

如果你想将一个值格式化输出,并限制其有效数字的数量,你需要再次使用格式对象。这次使用你的格式规则从数值创建一个输出字符串:

String output = formatterUK.format(valCEWithUKFormat.doubleValue() );

这将为您提供输出1,235

看起来您希望将此数字值以1.235格式呈现。为此,您应该使用特定区域设置(如果您的区域设置使用不同格式)来格式化输出。

但是,我建议以不同的方式解决此问题:

String text = "1,234567";
NumberFormat nf_in = NumberFormat.getNumberInstance(Locale.GERMANY);
double val = nf_in.parse(text).doubleValue();

NumberFormat nf_out = NumberFormat.getNumberInstance(Locale.UK);
nf_out.setMaximumFractionDigits(3);
String output = nf_out.format(val);

注意事项:

  • 应将输入解析与输出格式分开处理。特别是当你开始使用多种语言环境时。
  • 让标准库来确定给定语言环境下的有效输入值,你只需要选择一个适当的语言环境(我选择了德国,但显然也可以使用其他语言环境)。尽可能使用语言环境,不要试图重新创建格式化字符串。
  • 始终将输入值与任何输出格式分开存储。例如,如果您想在输出中仅显示三个数字,则可以,但无论如何都要存储整个双精度值。

6
考虑到您的建议,我稍微修改了我的代码以涵盖不同的本地化信息。关键是将以本地化格式表示的值字符串转换为基于格式字符串进行舍入的Double类型。
格式字符串始终基于英国格式,小数分隔符为“.”,千位分隔符为“,”。
我使用DecimalFormat来解析指定区域设置的本地化格式。这样可以正确获得字符串的Double等效项。然后我使用BigDecimal来处理舍入。我可以从DecimalFormat实例获取小数位数,然后调用setScale方法对BigDecimal进行舍入操作。
初始代码结构已经修改,以便您可以查看在不同语言环境下发生的情况。感谢@RD01指出其他语言环境的重要性。
现在我的代码如下:
private void runTests3() {
    // output current locale we are running under
    System.out.println( "Current Locale is " + Locale.getDefault().toString() );

    // number in Central European Format with a format string specified in UK format
    String numbersInEuropeanFormatString[] = new String[] { "1.000,234567", "1,2345678", "1.222.333,234567" };
    String formatUK = "###,##0.0000";

    // output numbers using the german locale
    System.out.println("Output numbers using the German locale\n");
    for(String num : numbersInEuropeanFormatString ) {
        formatNumberAsDouble(num, formatUK, Locale.GERMAN);
    }

    // output numbers using the UK locale.  
    // this should return unexpected results as the number is in European format
    System.out.println("Output numbers using the UK locale\n");
    for(String num : numbersInEuropeanFormatString ) {
        formatNumberAsDouble(num, formatUK, Locale.UK);
    }

    // output numbers using new DecimalFormat( formatUK ) - no locale specified
    System.out.println("\n\nOutput numbers using new DecimalFormat( " + formatUK + " )\n");
    for(String num : numbersInEuropeanFormatString ) {
        formatNumberAsDouble( num, formatUK, null);
    }
}

private void formatNumberAsDouble(String value, String format, Locale locale) {


    NumberFormat formatter;
    int decimalPlaces;

    // create the formatter based on the specified locale
    if( locale != null ) {
         formatter = NumberFormat.getNumberInstance(locale);
         // creating the above number format does not take in the format string
         // so create a new one that we won't use at all just to get the
         // decimal places in it
         decimalPlaces = (new DecimalFormat(format)).getMaximumFractionDigits();
    } else {
        formatter = new DecimalFormat( format );
        decimalPlaces = formatter.getMaximumFractionDigits();
    }

    // get the result as number
    Double result = null;
    try {
        result = formatter.parse( value ).doubleValue();
    } catch( ParseException ex ) {
        // not bothered at minute
    }

    // round the Double to the precision specified in the format string


    BigDecimal bd = new BigDecimal(result );
    Double roundedValue = bd.setScale( decimalPlaces, RoundingMode.HALF_UP ).doubleValue();

    // output summary
    System.out.println("\tValue = " + value);
    System.out.println( locale == null  ? "\tLocale not specified" : "\tLocale = " + locale.toString());
    System.out.println( format == null || format.length() == 0 ? "\tFormat = Not specified" : "\tFormat = " + format);
    System.out.println("\tResult (Double) = " + result);
    System.out.println("\tRounded Result (Double) (" + decimalPlaces + "dp) = " + roundedValue);
    System.out.println("");
}

这将产生以下输出:
Current Locale is nl_BE
Output numbers using the German locale

    Value = 1.000,234567
    Locale = de
    Format = ###,##0.0000
    Result (Double) = 1000.234567
    Rounded Result (Double) (4dp) = 1000.2346

    Value = 1,2345678
    Locale = de
    Format = ###,##0.0000
    Result (Double) = 1.2345678
    Rounded Result (Double) (4dp) = 1.2346

    Value = 1.222.333,234567
    Locale = de
    Format = ###,##0.0000
    Result (Double) = 1222333.234567
    Rounded Result (Double) (4dp) = 1222333.2346

Output numbers using the UK locale

    Value = 1.000,234567
    Locale = en_GB
    Format = ###,##0.0000
    Result (Double) = 1.0
    Rounded Result (Double) (4dp) = 1.0

    Value = 1,2345678
    Locale = en_GB
    Format = ###,##0.0000
    Result (Double) = 1.2345678E7
    Rounded Result (Double) (4dp) = 1.2345678E7

    Value = 1.222.333,234567
    Locale = en_GB
    Format = ###,##0.0000
    Result (Double) = 1.222
    Rounded Result (Double) (4dp) = 1.222



Output numbers using new DecimalFormat( ###,##0.0000 )

    Value = 1.000,234567
    Locale not specified
    Format = ###,##0.0000
    Result (Double) = 1000.234567
    Rounded Result (Double) (4dp) = 1000.2346

    Value = 1,2345678
    Locale not specified
    Format = ###,##0.0000
    Result (Double) = 1.2345678
    Rounded Result (Double) (4dp) = 1.2346

    Value = 1.222.333,234567
    Locale not specified
    Format = ###,##0.0000
    Result (Double) = 1222333.234567
    Rounded Result (Double) (4dp) = 1222333.2346

2

DecimalFormat中小数位数的限制实际上是为了在format()方法中使用,并且在parse()方法中几乎没有影响。

为了得到您想要的结果,您需要这样做:

    try {
        // output current locale we are running under (this happens to be "nl_BE")
        System.out.println("Current Locale is " + Locale.getDefault().toString());

        // number in Central European Format with a format string specified in UK format
        String numberCE = "1,234567"; // 1.234567
        String formatUK = "###,##0.000";

        // do the format
        DecimalFormat formatterUK = new DecimalFormat(formatUK);
        Double valCEWithUKFormat = formatterUK.parse(numberCE).doubleValue();

        // first convert to UK format string
        String numberUK = formatterUK.format(valCEWithUKFormat);
        // now parse that string to a double
        valCEWithUKFormat = formatterUK.parse(numberUK).doubleValue();

        // I want the number to DPs in the format string!!!
        System.out.println("CE Value     " + numberCE + " in UK format (" + formatUK + ") is " + valCEWithUKFormat);

    } catch (ParseException ex) {
        System.out.println("Cannot parse number");
    }

首先,您需要将数字作为英国格式字符串获取,然后使用英国格式化程序解析该数字。这将给您所需的结果。注意,这将四舍五入到3个小数位,而不是截断。

顺便说一句,我有点惊讶您的英国格式化程序能够解析CE格式数字。您真的应该使用CE格式解析器解析原始数字。


1

当然可以。尝试运行这个:

String in = "1,234567";
System.out.println(NumberFormat.getNumberFormat(new Locale("fr", "FR")).parse(in));
System.out.println(NumberFormat.getNumberFormat(new Locale("en", "GB")).parse(in));

显然,它们会产生不同的输出,第一个读取1.234567,第二个读取1234567。也许您的模式有问题?无论如何,最后一行是获取UK标准格式的首选方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接