将小数格式化为字符串以提高性能

时间:2009-09-15 02:25:33

标签: java performance formatting decimal

我正在编写一个应用程序,需要输出不同长度的小数,并将不同比例缩放为字符串,不带小数点,以便将平面文件写入另一个系统的输入。 e.g。

 12345  -> Length:10, Scale:2              -> 0001234500
 123.45 -> Length:10, Scale:2              -> 0000012345
 123.45 -> Length:10, Scale:3              -> 0000123450
-123.45 -> Length:10, Scale:3, signed:true -> -000123450
 123.45 -> Length:10, Scale:3, signed:true -> +000123450

我编写的用于处理此功能的功能在下面,并将被称为数十万次,所以我想确保没有更好,更有效的方法来执行此操作。我已经看过让DecimalFormat为我做更多事情的方法,但我看不到它处理我需要用小数位格式化但没有小数点。

protected String getFormattedDecimal( String value, int len, int scale, Boolean signed ) throws Exception{
    StringBuffer retVal = new StringBuffer();

    //Need a BigDecimal to facilitiate correct formatting
    BigDecimal bd = new BigDecimal( value );

    //set the scale to ensure that the correct number of zeroes 
    //at the end in case of rounding
    bd = bd.setScale( scale );

    //taking it that if its supposed to have negative it'll be part of the num
    if ( ( bd.compareTo( BigDecimal.ZERO ) >= 0 ) && signed ){
        retVal.append( "+" );
    }           

    StringBuffer sbFormat = new StringBuffer();
    for (int i = 0; i < len; i++)
    {
        sbFormat.append('0');
    }

    DecimalFormat df = new DecimalFormat( sbFormat.toString() );

    retVal.append( df.format( bd.unscaledValue() ) );

    return retVal.toString();
}

3 个答案:

答案 0 :(得分:8)

我的性能增强实现如下。它的速度是基于DecimalFormatter的解决方案的4.5倍:在我的机器上运行,使用带有不错的自制测试工具的Eclipse,结果如下:

  • 旧方式需要5421毫秒才能拨打600,000个电话(平均每次通话0.009035毫秒)
  • 新方式需要1219毫秒才能拨打600,000个电话(平均每个电话0.002032毫秒)

一些注意事项:

  • 我的解决方案使用固定大小的零块进行填充。如果你预计任何一侧都需要更多填充,那么你必须增加尺寸......显然你可以根据需要动态增加它。
  • 您的上述评论与代码不完全匹配。具体来说,如果返回了一个符号字符,则返回的长度比请求的长一个(您的注释另有说明)。我选择相信代码而不是评论。
  • 我的方法是静态的,因为它不需要实例状态。这是个人品味的东西 - ymmv。

另外:为了模仿原文的行为(但未在评论中给出),这个:

  • 如果传入值中的小数位数多于比例值,则抛出ArithmeticException 如果输入值中的整数位数多于(len-scale),则返回的字符串长于len。 如果signed为true,则返回的字符串将比len长

    • 然而:如果len为负数,则原始字符串将返回逗号分隔的字符串。这会抛出IllegalARgumentException
    package com.pragmaticsoftwaredevelopment.stackoverflow;
    ...
       final static String formatterZeroes="00000000000000000000000000000000000000000000000000000000000";
       protected static String getFormattedDecimal ( String value, int len, int scale, Boolean signed ) throws IllegalArgumentException {
           if (value.length() == 0) throw new IllegalArgumentException ("Cannot format a zero-length value");
           if (len <= 0) throw new IllegalArgumentException ("Illegal length (" + len + ")");
           StringBuffer retVal = new StringBuffer();
           String sign=null;
           int numStartIdx; 
           if ("+-".indexOf(value.charAt(0)) < 0) {
              numStartIdx=0;
           } else {
              numStartIdx=1;
              if (value.charAt(0) == '-')
                 sign = "-";
           }
           if (signed && (value.charAt(0) != '-'))
              sign = "+";
           if (sign==null)
              sign="";
           retVal.append(sign);
    
    
           int dotIdx = value.indexOf('.');
           int requestedWholePartLength = (len-scale);
    
           if (dotIdx < 0) { 
              int wholePartPadLength = (requestedWholePartLength - ((value.length()-numStartIdx)));
              if (wholePartPadLength > 0)
                 retVal.append(formatterZeroes.substring(0, wholePartPadLength));
              retVal.append (value.substring(numStartIdx));
              if (scale > 0)
                 retVal.append(formatterZeroes.substring(0, scale));
           }
           else {
              int wholePartPadLength = (requestedWholePartLength - (dotIdx - numStartIdx));
              if (wholePartPadLength > 0)
                 retVal.append(formatterZeroes.substring(0, wholePartPadLength));
              retVal.append (value.substring(numStartIdx, dotIdx));
              retVal.append (value.substring (dotIdx+1));
              int fractionalPartPadLength = (scale - (value.length() - 1 - dotIdx));
              if (fractionalPartPadLength > 0)
                 retVal.append(formatterZeroes.substring(0, fractionalPartPadLength));
    
    
           }
    
           return retVal.toString();
       }
    
  • 答案 1 :(得分:4)

    如果您要将输入作为字符串开头,为什么需要将其转换为BigDecimal并返回?

    似乎找到小数点的位置要快得多,将其与长度/比例相比较并相应地填充字符串。

    答案 2 :(得分:2)

    我同意ChssPly76 wrt手动字符串操作。

    但是,如果您要沿着BigDecimal / DecimalFormat路线走下去,您可能需要考虑共享DecimalFormat而不是每次迭代都创建一个新的ThreadLocal。请注意,这些类不是线程安全的,因此如果您使用多个线程进行处理,则需要使用Thread存储之类的内容来维护每{{1}}的格式化程序。

    顺便说一句,您是否对此进行了测试并发现性能不可接受,或者您只是在寻找最有效的解决方案? Note what Donald Knuth says on the topic of early optimization