Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Formatting Decimals to strings for performance

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();
}
like image 589
MadMurf Avatar asked Sep 15 '09 02:09

MadMurf


People also ask

What is the correct way to format the decimal as a string to 2 decimal places?

format("%. 2f", 1.23456); This will format the floating point number 1.23456 up-to 2 decimal places, because we have used two after decimal point in formatting instruction %.

Can string have decimals?

ToString("F2") will always show 2 decimal places like 123.40 . You'll have to use 0.

What is the formatting code to round a number to 3 decimals in a formatted string?

The string "#. ##" indicate that we are formatting up-to 2 decimal points, "#. ###" indicates formatting number up-to 3 decimal places. By the way, even DecimalFormat rounds the number if next decimal point is more than 5.

What is .2f in Java?

2f", val); In short, the %. 2f syntax tells Java to return your variable ( val ) with 2 decimal places ( . 2 ) in decimal representation of a floating-point number ( f ) from the start of the format specifier ( % ).


1 Answers

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();
       }
    
  • like image 156
    CPerkins Avatar answered Sep 19 '22 23:09

    CPerkins