22

goal:
write the most concise function that formats a (String) decimal into a currency format in Apex. Feel free to trainwreck the code for points, I've kept mine exploded for readability purposes.

  • Is this an actual problem you are facing or just a way to entertain the troops? Typically we don't allow "getting to know you" -type questions posted strictly for social and entertainment value. Perhaps your question is better suited to codegolf.stackexchange.com? – Robert Cartaino Aug 16 '12 at 17:55
  • 3
    it was a problem I was facing, and I solved it with the code I supplied. I had a hunch I was recreating the wheel so I used the code golf format to elicit responses. I have seen other examples of code golf in the past on SE. Of course a new exchange would be created for that purpose, cant seem to keep up with you guys ;) – ebt Aug 16 '12 at 17:59
  • I didn't have a problem with this question specifically, as long as the context and motivation was sound. Enjoy. – Robert Cartaino Aug 16 '12 at 18:05
22

UPDATE: More robust solution from @jkraybill elsewhere.

Here's one that's shorter AND is locale safe:

public static String currency(String i) {
    String s = ( Decimal.valueOf(i==null||i.trim()==''?'0':i).setScale(2) + 0.001 ).format();
    return s.substring(0,s.length()-1);
}

Check out Number Format in the list of supported locales. There's "1.000,00" and "1 000.00" amongst others. Not everyone uses "1,000.00" so we shouldn't assume that in a formatter. Decimal's format() method is locale aware.

  • I could do it all in one line if Apex's substring() acccepted negatives in the second argument for chopping off the right. You'd have blah.format().substring(0,-1) – RickMeasham Aug 12 '12 at 9:01
  • 1
    Your method doesn't handle empty strings, I made a slight edit to fix that. Basically changing your (i==null?'0':i) to (i==null||i.trim()==''?'0':i) – E.J. Wilburn Aug 12 '12 at 14:28
  • Out of interest, why is 0.001 added after the rounding and before the call to format? – Daniel Ballinger Aug 29 '12 at 0:45
  • 1
    Adding 0.001 means we have two zeroes after the decimal place without altering those two decimal places. The format then strips off the 1 – RickMeasham Sep 12 '12 at 10:29
  • 1
    In case anyone needs to handle fractional currency and/or negative values, be aware that this method doesn't handle either quite correctly -- I posted an edited version that does. – jkraybill Sep 24 '13 at 1:45
12

@RickMeasham's method is a good one, but I ran into a couple rounding issues with negative values and fractional values. Here's my edited version of the method that passes the tests I needed it to (not rendering -0.001 as "-0.00", not rendering -1.10 as "-1.09").

public static String formatCurrency(Decimal i) {
    if (i == null || Math.abs(i) < 0.005) return '$0.00';
    String s = (i.setScale(2) + (i >= 0 ? 0.001 : -0.001)).format();
    return s.substring(0, s.length() - 1);
}

(EDIT: changed "<= 0.005" to "< 0.005" per @RickMeasham's advice below.)

(EDIT 2: actually realized, when I finished tests, that this updated method still had a few shortcomings related to rounding. I updated to delegate to Math.roundToLong per code below [which uses round half even, not half up as I stated in my comments erroneously]. It now passes all my unit tests, which you can see here: http://codepad.org/ycttSXjq)

private String formatCurrency(Decimal i) {
    if (i == null) return '0.00';
    i = Decimal.valueOf(Math.roundToLong(i * 100)) / 100;
    String s = (i.setScale(2) + (i >= 0 ? 0.001 : -0.001)).format();
    return s.substring(0, s.length() - 1);
}
3

Ok - first, I'm not actually recommending the following approach. It's more of an intellectual exercise - an exploration of a different path to a solution.

First, create the following Apex page:

< apex:page Controller="currencyfuncontroller" showHeader="false" sidebar="false" >
    < apex:outputField id="formattedamount" value="{!DummyOpportunity.Amount}" />
< /apex:page>

The controller is as follows:

public class currencyfuncontroller
{
   public Opportunity getDummyOpportunity()
   {
       String currencystring = 
         Apexpages.currentPage().getParameters().get('currencystring');
       Decimal currencyvalue = 0;
       try
       {
           currencyvalue = Decimal.ValueOf(currencystring);
       } catch(Exception ex) {}
       Opportunity op = new Opportunity(amount = currencyvalue);
       return op;
   }
}

Here's some simple code to use it:

ApexPages.PageReference pr = Page.currencyfun;
pr.getParameters().put('currencystring','1.56');
String contents = pr.GetContent().toString();
system.debug(contents);

You still need to parse out the actually currency value - but that's easy enough to do use a regular expression or some substring work, so I left it out here. I also cheated by using an exception handler instead of the (better) validation shown in other answers - just to keep things simple.

What does this approach accomplish?

  • The formatting now includes the correct currency symbol for the user's locale (and currency formatting should it differ from standard decimal formatting).
  • You could extend this to include a parameter for the currency to use and use that info to set the currency type for the opportunity. On multi-currency organizations this should provide automatic currency conversion to the current locale with the formatting (standard SFDC formatting on multi-currency organizations).

Again - If I really needed this I'd probably actually write the code to lookup currency symbols, examine the current and corporate currency, and do the necessary conversions and formatting directly. But that's quite a bit of code (especially on orgs using advanced currency management).

Ultimately my point is - the original question was asking for formatting a decimal string into currency format, and the job isn't really done until you have the correct currency symbol and multi-currency handling in place :-)

3

Of course, it all depends on your use case. Most of the time, you don't need to do this in Apex since the value is destined either for display on a VisualForce page or storage via a Currency field. That said, you'd be able to do this in pure Apex if it weren't for the type restriction on the String.format(...) method. Requiring a List<String> for the arguments prevents you from doing it in pure Apex, at least easily.

If the point of the Apex code is to provide a value for display in a VisualForce page, then Apex really need not do anything but provide the raw decimal value:

<apex:outputText value="Total: {0, number, currency}">
  <apex:param value="{!aDecimalValue}"/>
</apex:outputText>

No need for a dummy SObject with a currency field, although that certainly works.

  • my particular requirement needed dynamic json, using hardcorded vf tags mixed with json wasnt a direction I wanted to take. – ebt Aug 17 '12 at 20:05
2

The original Solution posted above is slightly incorrect as it doesn't handle negative numbers..

Use this method instead..

public static String currency(String i)
{
    // - Error this doesn't handle negative number converts -15 to -14.999  instead of -15.001 before round
    //String s = ( Decimal.valueOf(i==null||i.trim()==''?'0':i).setScale(2) + 0.001 ).format();
    //return s.substring(0,s.length()-1);

    if (( i == null) || ( i == ''))
    {
        return '0';
    }
    else
    {
        string s;
        if ( Decimal.valueOf(i) >= 0 )
        {
            s =  ( Decimal.valueOf(i).setScale(2) + 0.001 ).format();
        }
        else
        {
            s = ( Decimal.valueOf(i).setScale(2) - 0.001 ).format();
        }
    return s.substring(0,s.length()-1);
    }
}
0

670 characters

public static String currency(String input){
    if(input == null){return '0.00';}
    if(input.indexOf('.') == -1){input = input+'.00';}
    if(input.length() == 5){return input+'0';}
    String newValue = input.substring(input.indexOf('.'));
    newValue = String.valueOf(Decimal.valueOf(newValue).setScale(2));
    newValue = newValue.substring(newValue.indexOf('.'));
    input = input.substring(0,input.indexOf('.'));
    Integer sz = input.length();
    Integer n=0;
    for(Integer i = sz - 1;i > -1;i--){
        if(n!= 0 && math.mod(n,3) == 0)
            newValue = input.substring(i,i+1)+','+newValue;
        else
            newValue = input.substring(i,i+1)+newValue;
        n++; 
    }
    return newValue;
}
//unit test, doesnt count
static testmethod void test_currency(){
    system.assertEquals('2,105.00',currency('2105.0'));
    system.assertEquals('320.00',currency('320.0'));
    system.assertEquals('1,500.26',currency('1500.256'));
    system.assertEquals('0.00',currency('0.0'));
    system.assertEquals('0.00',currency(null));
    system.assertEquals('10,000,123.56',currency('10000123.558'));
} 
  • Should the last assert read system.assertEquals('1,500.26',currency('1500.26')); – David Gillen Aug 10 '12 at 22:31
  • the value being passed in should be .256, its testing the decimal rounding part of the method. – ebt Aug 10 '12 at 22:33
0

The following should do the trick, with the assumption that your locale for currency formatting is correct. Edited to handle null being passed, and zero values after the decimal.

public static String currency(String input) {
    if ( input == null ) {
        return '0.00';
    }

    Decimal d1 = Decimal.valueOf(input).setScale(2);
    String str = d1.format();
    if( !str.contains('.' ) ) {
        str = str + '.00';
    }

    return str;
}
  • failed the first test: 15:44:29.595 (595496000)|FATAL_ERROR|System.AssertException: Assertion Failed: Expected: 2,105.00, Actual: 2,105 – ebt Aug 10 '12 at 22:46
  • Believe that edit should do it. – David Gillen Aug 10 '12 at 23:10
  • Not for 0.1. It returns 0.1 rather than 0.10 – RickMeasham Aug 12 '12 at 8:44
0

Rarely should you ever need to format variables like that in Apex. Since that's typically a view issue, I tend to rely on apex:outputField for that scenario in Visualforce. Kibitzer has the right idea.

  • 3
    Depends on your definition of rare: this post wouldn't exist and neither would ideas like this sites.secure.force.com/success/ideaView?id=08730000000KlHoAAK if there wasn't a need sometimes for this functionality e.g. building message strings in Apex code. I suggest that salesforce's product managers don't spend enough time using their product to understand how many gaps there are in what they provide. And they do a great job of ignoring the ideas forum they created. – Keith C Feb 12 '13 at 16:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.