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

5
我正在编写一个应用程序,需要将不同长度和不同精度的小数输出为字符串,但不带小数点,以便将其写入平面文件并输入到另一个系统中。例如:
 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个回答

8
我的性能优化实现如下。 它比基于DecimalFormatter的解决方案快约4.5倍:在我的机器上运行,在Eclipse中使用不错的自制测试工具包,结果为:
旧方法需要5421毫秒才能进行600,000次调用(每个调用平均0.009035毫秒) 新方法需要1219毫秒才能进行600,000次调用(每个调用平均0.002032毫秒)
一些注意事项: - 我的解决方案利用了一个固定大小的零块进行填充。如果您预计需要更多的填充位置而不是我使用的30个左右,那么您必须增加其大小...如果需要,可以动态增加其大小。 - 您上面的评论与代码不完全匹配。 具体来说,如果返回一个符号字符,则返回长度比请求的长度大1(您的注释表示否则)。 我选择相信代码而不是注释。 - 我将我的方法设置为静态方法,因为它不需要实例状态。 这是个人品味问题-您的情况可能有所不同。
另外:为了模仿原始行为(但未在注释中给出),此字符串:
- 如果传入值中有比规模还多的小数位数,则抛出ArithmeticException - 如果传入值中有比(len-比例)更多的整数位数,则返回的字符串长度大于len。 - 如果signed为真,则返回的字符串比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(虽然这个努力应该得到+100) - ChssPly76
谢谢。当然,这是你第一次提出的建议。 :) - CPerkins
加一,伙计,你的工作量和努力令人印象深刻,非常感激。我也采纳了@ChssPly76的建议,在我的分析中看到了4倍的改进。如果今天有时间,我会把这个插入进去,再次运行统计数据,看看是否有进一步的改进。 - MadMurf

4
如果您一开始就将输入作为字符串获取,那么为什么需要将其转换为BigDecimal然后再转回去呢?
好像找到小数点的位置,将其与长度/比例进行比较,并相应地填充字符串会更快。

我会尝试一下...看起来需要更多的代码来进行分割、填充和处理有符号值,但我愿意尝试。 - MadMurf
看起来代码量更多,但实际上运行速度快了将近4倍,非常感谢。 - MadMurf

2
我同意ChssPly76关于手动字符串操作的观点。然而,如果您打算使用BigDecimal/DecimalFormat方法,您可能需要考虑共享您的DecimalFormats而不是在每次迭代中创建一个新的。请注意,这些类不是线程安全的,因此如果您使用多个线程进行处理,您将需要使用ThreadLocal存储来维护每个线程的格式化程序。
顺便说一下,您是否测试过这个方法并发现性能不可接受,还是只是寻找最有效的解决方案?请注意唐纳德·克努斯(Donald Knuth)在早期优化方面的看法。

是的,通过分析器运行它,其平均基准时间为0.00058,因此它运行的600k个奇数迭代花费了349秒。我将查看如何使用长度操作字符串,并查看其性能如何,我认为这样会更糟糕,因为需要拆分和处理负数。 - MadMurf

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