23

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.

3
  • 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? Aug 16, 2012 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, 2012 at 17:59
  • I didn't have a problem with this question specifically, as long as the context and motivation was sound. Enjoy. Aug 16, 2012 at 18:05

9 Answers 9

26

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.

6
  • 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) Aug 12, 2012 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) Aug 12, 2012 at 14:28
  • Out of interest, why is 0.001 added after the rounding and before the call to format? Aug 29, 2012 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 Sep 12, 2012 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, 2013 at 1:45
13

@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);
}
4
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.

1
  • my particular requirement needed dynamic json, using hardcorded vf tags mixed with json wasnt a direction I wanted to take.
    – ebt
    Aug 17, 2012 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'));
} 
2
  • Should the last assert read system.assertEquals('1,500.26',currency('1500.26')); Aug 10, 2012 at 22:31
  • the value being passed in should be .256, its testing the decimal rounding part of the method.
    – ebt
    Aug 10, 2012 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;
}
3
  • 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, 2012 at 22:46
  • Believe that edit should do it. Aug 10, 2012 at 23:10
  • Not for 0.1. It returns 0.1 rather than 0.10 Aug 12, 2012 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.

1
  • 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, 2013 at 16:02
0

One of options is to store the value in the currency field in some object, like

Opportunity o = new Opportunity(
    Name='Test', StageName='Closed',CloseDate=Date.today(),Amount = 12345.60
);
insert o;
o = [SELECT FORMAT(Amount) formattedAmount FROM Opportunity WHERE Id = :o.Id];
System.debug(o.get('formattedAmount'));

Then in debug log we can see the formatted value

USER_DEBUG|[14]|DEBUG|€12,345.60

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .