Question

I'm writing an app that needs to output Decimals of varying lengths, and varying scale to strings without the decimal point for writing to a flat file as input to another system. 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

The function I have written to handle this is below and is going to be called hundreds of thousands of times so I want to make sure that there's not a better, more efficient way to do this. I've looked at ways to get DecimalFormat to do more for me but I can't see it handling my need to format with decimal places but without decimal point.

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();
}
Was it helpful?

Solution

My performance-enhanced implementation is below. It is about 4.5 times as fast as the DecimalFormatter-based solution: running on my machine, using Eclipse with a decent home-brewed test harness, the results are:

  • Old way took 5421 ms to make 600,000 calls (average 0.009035 ms per call)
  • New way took 1219 ms to make 600,000 calls (average 0.002032 ms per call)

Some notes:

  • My solution makes use of a fixed-size block of zeroes for padding. If you anticipate needing more padding on either side than the thirty or so I used, you'd have to increase the size... clearly you could increase it dynamically if required.
  • Your comments above didn't quite match the code. Specifically, if a sign character was returned, the returned length is one greater than requested (your comments say otherwise). I have chosen to believe the code rather than the comments.
  • I made my method static, since it requires no instance state. That's a personal taste thing - ymmv.

Also: in order to mimic the behavior of the original (but not given in the comments), this:

  • If there are more fractional digits in the incoming value than fit in scale, throws an ArithmeticException If there are more whole digits in the incoming value than fit in (len-scale), the returned string is longer than len. If signed is true, the returned string will be one longer than len

    • However: if len is negative, the original returns a comma-delimited string. This throws an 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();
       }
    
  • OTHER TIPS

    If you are getting your input as String to begin with, why do you need to convert it to BigDecimal and back?

    Seems like it'd be a lot faster to find the position of decimal point, compare that to length / scale and pad the string accordingly.

    I agree with ChssPly76 wrt manual String manipulation.

    However, if you are going to go down the BigDecimal/DecimalFormat route, you might want to consider sharing your DecimalFormats instead of creating a new one with every iteration. Note that these classes are not thread-safe, so if you are using multiple threads to do your processing, you will want to use something like ThreadLocal storage to maintain a formatter per Thread.

    btw, have you tested this and found the performance not acceptable or are you just looking for the most efficient solution possible? Note what Donald Knuth says on the topic of early optimization.

    Licensed under: CC-BY-SA with attribution
    Not affiliated with StackOverflow
    scroll top