3

According to SF documentation Decimal.format() method "Returns the String value of this Decimal using the locale of the context user.", but it leaves only 3 digits after decimal point. It doesn't even matter if you use setScale() method before calling format(). Example from SF documentation:

// U.S. locale
Decimal myDecimal = 12345.6789;
system.assertEquals('12,345.679', myDecimal.format());

As you can see it leaves only 3 digits after decimal point.

Is there a "standard" way to format number in Apex (not Visualforce) using the locale of the context user and leave all the digits after decimal point? Or it should be custom code/logic to handle this?

3 Answers 3

1

I've logged an Idea for this issue to ask Salesforce for solving this.

Please also find the following 'work-a-round' code that I've written for my current project to make this failure work either way.

/**
 * Function to print a number String or Decimal into the local Currency
 * Format() only returns up till an accuracy of 3 digits, causing a potential issue
 */
public static String formatCurrency( Object o, Integer numberOfDecimals ) {
    // Construct separators based on UserLocale (Decimal.format() is locale sensitive)
    Decimal value           = 1000.10;
    String formattedValue   = value.format();
    String thousandChar     = formattedValue.substring(1,2);
    String decimalChar      = formattedValue.substring(5,6);

    // fetch input number
    Decimal decVal = 0;
    if      ( o == null )            decVal = 0;
    else if ( o instanceof Decimal ) decVal = ( Decimal ) o;
    else if ( o instanceof String )  decVal = Decimal.valueOf( String.valueOf( o ) );

    // apply rounding or add additional 0 decimals
    decVal = decVal.setScale( numberOfDecimals, RoundingMode.CEILING );

    // use format() to print the Integer including thousand separators in user locale
    // Note: only prints up to an accuracy of 3 and does not print 'ending zeros'
    String formattedString = decVal.format();

    // fetch full decimals string
    String originalNumber = decVal.ToPlainString(); // prints with System separators 
    String decimalString  = originalNumber.substringAfter('.');

    // fetch the integer string (before decimal separator), containing thousand separators
    String integerString  = formattedString.substringBefore( decimalChar );

    // concatenate those together
    return integerString + decimalChar + decimalString;
}

Hope this can help you and please upvote the Idea :)

0

I don't know of an API for this.

So have this ugly code in one of my projects (which is based on someone else's post that I no longer have a link to):

private static final Map<Integer, Integer> MULTIPLIERS = new Map<Integer, Integer> {
        1 => 10,
        2 => 100,
        3 => 1000,
        4 => 10000,
        5 => 100000,
        6 => 1000000
        };
public static String format(Decimal d, Integer places) {

    if (d != null) {

        // Round first
        Decimal dd = d.setScale(places);

        // Format to get thousand separators
        String w = dd.longValue().format();

        if (places != 0) {

            if (dd < 0) dd = -dd;
            Integer multiplier = MULTIPLIERS.get(places);

            // For example 0.0067 becomes 10067 then the leading 1 is dropped to get 0067
            String f = String.valueOf(multiplier + Math.mod((dd * multiplier).longValue(), multiplier));
            return w + '.' + f.substring(1);
        } else {
            return w;
        }
    } else {
        return null;
    }
}

with test:

@isTest
static void testFormat() {

    System.assertEquals(null, StringUtil.format(null, 2));

    System.assertEquals('12.34', StringUtil.format(12.34, 2));
    System.assertEquals('12.30', StringUtil.format(12.3, 2));
    System.assertEquals('12.00', StringUtil.format(12, 2));

    // Now rounds; didn't use to
    System.assertEquals('12.34', StringUtil.format(12.3444, 2));
    System.assertEquals('12.35', StringUtil.format(12.3466, 2));

    System.assertEquals('12.34', StringUtil.format(12.344, 2));
    System.assertEquals('12.35', StringUtil.format(12.346, 2));

    System.assertEquals('12.3400', StringUtil.format(12.34, 4));
    System.assertEquals('12.340', StringUtil.format(12.34, 3));
    System.assertEquals('12.34', StringUtil.format(12.34, 2));
    System.assertEquals('12.3', StringUtil.format(12.34, 1));
    System.assertEquals('12', StringUtil.format(12.34, 0));

    System.assertEquals('12.00', StringUtil.format(12, 2));
    System.assertEquals('12.0', StringUtil.format(12, 1));
    System.assertEquals('12', StringUtil.format(12, 0));

    System.assertEquals('12.3456', StringUtil.format(12.345644, 4));
    System.assertEquals('12.3457', StringUtil.format(12.345666, 4));

    System.assertEquals('123,456.12', StringUtil.format(123456.1234, 2));
    System.assertEquals('123,456.1235', StringUtil.format(123456.123488, 4));
    System.assertEquals('-55,123,456.12', StringUtil.format(-55123456.1234, 2));

    System.assertEquals('-123,456.123457', StringUtil.format(-123456.12345678, 6));

    System.assertEquals('12', StringUtil.format(12.34, 0));
    System.assertEquals('13', StringUtil.format(12.67, 0));
}
1
  • I'm also thinking about some custom logic/code to handle this, but still hope there is more straight forward solution. So will also appreciate other suggestions if any. Thanks!
    – sfman
    Commented Nov 13, 2015 at 9:17
0

It does not appear that a better standard answer has come along since this was posted, but I tried making a version that converts the value to a plain string and a format string, taking advantage of the ToPlainString() method giving the correct precision, and uses the separators from format().

public static string formatPlaces(decimal dec, integer places)
{
    String rawString = dec.setScale(places).ToPlainString(), 
        formatString = (dec.round() == dec ? dec + .1 : dec).format(); //Adjust for integers
    return formatString.substring(0, lastIndexOfRegex(formatString, '\\D') + 1)
        + rawString.substring(rawString.indexOf('.') + 1);
}

//Adapted from Tomer Godinger's Java solution: https://stackoverflow.com/a/10999868/2554810
public static integer lastIndexOfRegex(String str, String toFind)
{
    Pattern regPattern = Pattern.compile(toFind);
    Matcher regMatcher = regPattern.matcher(str);

    integer lastIndex = -1;

    while (regMatcher.find())
    {
        lastIndex = regMatcher.start();
    }

    return lastIndex;
}

You must log in to answer this question.

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