189

I'd like to format following numbers into the numbers next to them with java:

1000 to 1k
5821 to 5.8k
10500 to 10k
101800 to 101k
2000000 to 2m
7800000 to 7.8m
92150000 to 92m
123200000 to 123m

The number on the right will be long or integer the number on the left will be string. How should I approach this. I already did little algorithm for this but I thought there might be already something invented out there that does nicer job at it and doesn't require additional testing if I start dealing with billions and trillions :)

Additional Requirements:

  • The format should have maximum of 4 characters
  • The above means 1.1k is OK 11.2k is not. Same for 7.8m is OK 19.1m is not. Only one digit before decimal point is allowed to have decimal point. Two digits before decimal point means not digits after decimal point.
  • No rounding is necessary. (Numbers being displayed with k and m appended are more of analog gauge indicating approximation not precise article of logic. Hence rounding is irrelevant mainly due to nature of variable than can increase or decrees several digits even while you are looking at the cached result.)
MatBanik
  • 26,356
  • 39
  • 116
  • 178
  • 1
    If no one has a library would you mind posting your code? – Grammin Jan 20 '11 at 22:15
  • 1
    This may assist, though this isn't a dup. http://stackoverflow.com/questions/529432 – rfeak Jan 20 '11 at 22:22
  • 1
    @Mat I was curious as to what solution you were using before. If you don't mind would you post it as an answer as well. – jzd Jan 21 '11 at 18:49
  • Check my solution, it's the fastest and more important it doesn't fail for certain numbers which all answers casting to double (even the one which got the earlier bounty) are doing because of rounding errors due to casting! – maraca Jun 07 '15 at 11:49
  • 1
    What's the idea behind `No rounding is necessary` this seems absurd to me. Is it just to complicate things? Wouldn't it be better to rephrase this to *`Rounding is not necessary, but welcome`*? – Wolf Jun 10 '15 at 10:17
  • @Wolf: I agree; I'd imagine rounding `HALF_UP`, or perhaps `HALF_EVEN` in some domains would be more commonly required than just truncating. [My solution below](http://stackoverflow.com/a/30759915/983430) should allow any rounding mode (though not tested with all). – Amos M. Carpenter Jun 10 '15 at 15:03
  • Downvote for inattention regarding the `101800 to 101k` case - this can only be an error. – Wolf Jun 10 '15 at 15:29
  • 1
    In case you didn't notice the numbers being displayed with k and m appended are more of analog gauge indicating approximation not precise article of logic. Hence rounding is irrelevant mainly due to nature of variable than can increase or decrees several digits even while you are looking at the cashed result. – MatBanik Jun 10 '15 at 20:27
  • @MatBanik Ok, I see. I'd remove my downvote (and maybe upvote instead) as soon as there is this (the analog gauge idea) becomes evident in the question itself. – Wolf Jun 11 '15 at 10:37
  • [Here is the easiest way to do formation.](https://stackoverflow.com/a/59984378/8329786) – Ashav Kothari May 15 '20 at 07:18
  • Try out this library https://github.com/tygalive/Number-Shortener [Disclaimer am the owner] – Richard Muvirimi Apr 06 '21 at 09:18

26 Answers26

187

Here is a solution that works for any long value and that I find quite readable (the core logic is done in the bottom three lines of the format method).

It leverages TreeMap to find the appropriate suffix. It is surprisingly more efficient than a previous solution I wrote that was using arrays and was more difficult to read.

private static final NavigableMap<Long, String> suffixes = new TreeMap<> ();
static {
  suffixes.put(1_000L, "k");
  suffixes.put(1_000_000L, "M");
  suffixes.put(1_000_000_000L, "G");
  suffixes.put(1_000_000_000_000L, "T");
  suffixes.put(1_000_000_000_000_000L, "P");
  suffixes.put(1_000_000_000_000_000_000L, "E");
}

public static String format(long value) {
  //Long.MIN_VALUE == -Long.MIN_VALUE so we need an adjustment here
  if (value == Long.MIN_VALUE) return format(Long.MIN_VALUE + 1);
  if (value < 0) return "-" + format(-value);
  if (value < 1000) return Long.toString(value); //deal with easy case

  Entry<Long, String> e = suffixes.floorEntry(value);
  Long divideBy = e.getKey();
  String suffix = e.getValue();

  long truncated = value / (divideBy / 10); //the number part of the output times 10
  boolean hasDecimal = truncated < 100 && (truncated / 10d) != (truncated / 10);
  return hasDecimal ? (truncated / 10d) + suffix : (truncated / 10) + suffix;
}

Test code

public static void main(String args[]) {
  long[] numbers = {0, 5, 999, 1_000, -5_821, 10_500, -101_800, 2_000_000, -7_800_000, 92_150_000, 123_200_000, 9_999_999, 999_999_999_999_999_999L, 1_230_000_000_000_000L, Long.MIN_VALUE, Long.MAX_VALUE};
  String[] expected = {"0", "5", "999", "1k", "-5.8k", "10k", "-101k", "2M", "-7.8M", "92M", "123M", "9.9M", "999P", "1.2P", "-9.2E", "9.2E"};
  for (int i = 0; i < numbers.length; i++) {
    long n = numbers[i];
    String formatted = format(n);
    System.out.println(n + " => " + formatted);
    if (!formatted.equals(expected[i])) throw new AssertionError("Expected: " + expected[i] + " but found: " + formatted);
  }
}
Community
  • 1
  • 1
assylias
  • 321,522
  • 82
  • 660
  • 783
  • 1
    Nice solution. Looks like you can just add more suffixes for those really large numbers (quadrillion, quintillion, etc), and the output continues to scale. – Cypher Jun 05 '15 at 18:40
  • Your code is not quite correct with negative numbers: `-5821` should be formatted as `-5k`, not as `-5.8k`. – std.denis Jun 08 '15 at 16:29
  • 1
    @std.denis The OP did not indicate how to format negative numbers. I decided to format them like positive numbers but prefixed with `-` to keep the same number of significant digits. There are other options... – assylias Jun 08 '15 at 16:50
  • Once again a solution receives the bounty after I pointed out the flaws and it was corrected. I would at least like to know why you've chosen this one. @MatBanik – maraca Jun 11 '15 at 17:24
  • @maraca I'm sorry you feel that way but you should not take it personally - I have written numerous answers that did not get the attention I think they deserved and I got upvoted a lot on ridiculously trivial ones (for example [this one](http://stackoverflow.com/a/14145246/829571)). Is it fair? Maybe not but that's not the point - I keep answering not for the votes or bounties I may receive but because it helps me improve.... See also: http://meta.stackexchange.com/questions/202652/life-isnt-fair – assylias Jun 11 '15 at 18:38
  • 1
    First: I deleted the bad comments, because it is obviously not your fault. Second: It is not the problem that good answers don't get enough attention as long as they get more as others, but as it is you often have to dig for good answers and just some wrong, bad or generic answer gets upvoted (really bad to learn new stuff). And for people issuing bounties when already that many answers are there I would have expected to specify more clearly what is missing and then carefully choose the answer which fits the criterias best... – maraca Jun 11 '15 at 18:52
  • long[] numbers1={1006,1007,1008,1008,1009,1009}; this type of data its not working – Chandu D Jul 17 '15 at 09:19
  • @ChanduD What do you mean? Those numbers get printed as 1k which I think is what is expected. – assylias Jul 17 '15 at 11:37
  • I modified your code to make it work for more decimals, I mean 10400000 will be displayed in your code as 10M but I want it to print 10.4M, so I modified it like this: boolean hasDecimal = truncated < 1000 && (truncated / 100d) != (truncated / 100); however, very nice answer and code, upvoted and thanked. – eyadMhanna Apr 04 '16 at 13:23
  • Works like a charm! – Satheesh Apr 08 '18 at 14:54
  • 1
    but does whole world understand this standard? be careful if you make app for everyone in the world. For English it's __10M__ but for Russian it's __10 млн__ and so on – user924 Jun 28 '18 at 18:14
  • Excellent answer. Just be aware, you'll need to change the suffixes to English ones: "K", "M", "B", "T", "Quadrillion", "Quintillion". – Craigo May 21 '20 at 03:34
  • Something I do wonder here is, how it would look like when instead of longs, the provided numbers would have decimal points themself. I want to achieve a similar solution, but preferably by using either doubles or a BigDecimal and by discarding any decimal points of the result (So only whole numbers are returned). Does anyone have a link to an answer covering this? – Andre_601 Mar 26 '23 at 23:41
107

I know, this looks more like a C program, but it's super lightweight!

public static void main(String args[]) {
    long[] numbers = new long[]{1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000, 9999999};
    for(long n : numbers) {
        System.out.println(n + " => " + coolFormat(n, 0));
    }
}

private static char[] c = new char[]{'k', 'm', 'b', 't'};

/**
 * Recursive implementation, invokes itself for each factor of a thousand, increasing the class on each invokation.
 * @param n the number to format
 * @param iteration in fact this is the class from the array c
 * @return a String representing the number n formatted in a cool looking way.
 */
private static String coolFormat(double n, int iteration) {
    double d = ((long) n / 100) / 10.0;
    boolean isRound = (d * 10) %10 == 0;//true if the decimal part is equal to 0 (then it's trimmed anyway)
    return (d < 1000? //this determines the class, i.e. 'k', 'm' etc
        ((d > 99.9 || isRound || (!isRound && d > 9.99)? //this decides whether to trim the decimals
         (int) d * 10 / 10 : d + "" // (int) d * 10 / 10 drops the decimal
         ) + "" + c[iteration]) 
        : coolFormat(d, iteration+1));

}

It outputs:

1000 => 1k
5821 => 5.8k
10500 => 10k
101800 => 101k
2000000 => 2m
7800000 => 7.8m
92150000 => 92m
123200000 => 123m
9999999 => 9.9m
Ilya Saunkin
  • 18,934
  • 9
  • 36
  • 50
  • Is outputting 9.9m for 9999999 desirable? – Michael McGowan Jan 20 '11 at 23:30
  • What should it output? 10M? I haven't read a word about rounding here. – Ilya Saunkin Jan 20 '11 at 23:33
  • 3
    His example shows "10500 to 11k" so I'd say he wants rounding. – Gabe Jan 20 '11 at 23:41
  • Agreed. Then this needs a rewrite)) – Ilya Saunkin Jan 20 '11 at 23:48
  • Ok, then this is still on! God, my judgement is just awesome today! – Ilya Saunkin Jan 21 '11 at 00:00
  • @Elijah borrowed some of your testing code for my answer, I still like your answer better because it is lightweight. – jzd Jan 21 '11 at 01:55
  • 1
    No problem jzd, anytime. I fixed the algorithm to return up to 4 chars. Plus added comments. – Ilya Saunkin Jan 21 '11 at 06:47
  • 18
    Obfuscated code. We don't have to code like this nowadays. May work as expected, but I'd encourage the author to have a look at [Roger C. Martin:Clean Code](http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) – Andreas Dolk Jan 21 '11 at 07:10
  • 36
    Obfuscated? I beg your pardon, but you probably read one book and think you can code somehow differently nowadays. Tell Joel (http://www.joelonsoftware.com/articles/ThePerilsofJavaSchools.html) about that. I dare any code you can possibly write to get anywhere close to the speed of my method! – Ilya Saunkin Jan 21 '11 at 07:20
  • 12
    Changing d,c,n variables to something more readable (faster understanding) makes this code decent in my view – Henadzi Rabkin Sep 10 '14 at 08:30
  • I would agree with Elijah Saounkine, there is no need for fancy names for local variables, especially in small functions. Maybe just "c" could be "sufs" or something, since its global and the definition might be far away from the use. Furthermore, there are javadoc comments for the other ones. As for the ternary operator, if-else might be more readable for the same performance: http://stackoverflow.com/questions/9745389/is-the-ternary-operator-faster-than-an-if-condition . – stanm Sep 10 '14 at 12:33
  • 2
    Ohh god, c var... compiler takes care of shortening the var names. Typing small letters for var names is just plain lazy. – Oliver Dixon May 27 '15 at 12:23
  • @iLoveUnicorns right, thank you for the input. To tell the truth, that actually was plain lazy indeed :) – Ilya Saunkin May 27 '15 at 12:43
  • You are concerned about your runtime performance and have division through 10 and 100 in it? Although compilers and CPU are way better than they used to be I'm almost sure that multiplications are still faster then divisions. – Roman Vottner Jun 05 '15 at 02:07
  • speaking about speed mentioned in above comment the second check on `isRound` is redundant I would change from `(!isRound && d > 9.99)` to (d > 9.99) since `(!isRound && d > 9.99)` is evaluated only if the previous OR entries are false hence `!isRound` is always true – Paizo Jun 09 '15 at 12:09
  • 9
    Why this obsession with performance? Why would anyone want to execute a large enough number of these conversions to warrant even thinking about performance...? Readability first, performance tweaking only if required. – Amos M. Carpenter Jun 10 '15 at 05:16
  • @maraca: If your version is five times faster than this one, and it's only typically used to update, say, 10 labels at a time... then, yeah, 5 times faster _is just a little bit_. I think you've missed my point entirely and instead tried to use it to push your own agenda. Sorry, won't be drawn into a discussion about this, just wanted to add my two cents' worth. – Amos M. Carpenter Jun 10 '15 at 07:31
  • 13
    I'd have to agree with @AmosM.Carpenter. Little did I know about code maintainability when I wrote this answer 4 years ago. It's not bad to optimize, generally speaking, BUT readability comes first. By the way, it's not so bad performance-wise: not 5 times slower than the one maraca wrote - it's about the same (I've put up some of the solutions for a benchmark here https://github.com/esaounkine/number-format-benchmark). – Ilya Saunkin Jun 10 '15 at 08:00
  • Ok, I deleted my comments about speed (depends a lot on how you measure), but this solution fails for some numbers, which is what is really bothering me, not the speed (and none of the voters seem to care about that). I also don't say it's completely unusable (didn't downvote), but it has problems. – maraca Jun 10 '15 at 11:31
  • @ElijahSaounkine Congrats, your solution is pretty good, you have nice logic!, but if the most part of the array is large numbers like millions, billions, then [the accepted solution](http://stackoverflow.com/a/30661479/3525886) is much better, because uses a tree and the time is constant. – Johnny Willer Nov 07 '15 at 00:50
  • 72k likes for you – AzizAhmad Nov 23 '16 at 21:10
  • for 400 it outputs 0.4k haaa – Shifatul Mar 21 '17 at 01:40
45

Here a solution that makes use of DecimalFormat's engineering notation:

public static void main(String args[]) {
    long[] numbers = new long[]{7, 12, 856, 1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000, 9999999};
    for(long number : numbers) {
        System.out.println(number + " = " + format(number));
    }
}

private static String[] suffix = new String[]{"","k", "m", "b", "t"};
private static int MAX_LENGTH = 4;

private static String format(double number) {
    String r = new DecimalFormat("##0E0").format(number);
    r = r.replaceAll("E[0-9]", suffix[Character.getNumericValue(r.charAt(r.length() - 1)) / 3]);
    while(r.length() > MAX_LENGTH || r.matches("[0-9]+\\.[a-z]")){
        r = r.substring(0, r.length()-2) + r.substring(r.length() - 1);
    }
    return r;
}

Output:

7 = 7
12 = 12
856 = 856
1000 = 1k
5821 = 5.8k
10500 = 10k
101800 = 102k
2000000 = 2m
7800000 = 7.8m
92150000 = 92m
123200000 = 123m
9999999 = 10m
jzd
  • 23,473
  • 9
  • 54
  • 76
  • @Mat Updated to handle new requirements – jzd Jan 21 '11 at 02:24
  • Is there an easy way to combine this with Currency Instance to get similar functionality with currency? – xdumaine Nov 03 '11 at 13:10
  • @roviuser, not sure what you mean, but this sounds like a separate question. – jzd Nov 08 '11 at 12:45
  • Thanks, very helpful. I'd also add a check for number < 1000 to leave numbers like 856 as is (currently such numbers are trimmed: 856 -> 800) – ernazm Jun 26 '13 at 13:55
  • @ernazm, this code works fine for numbers <1000. I just updated the answer to include tests for values that are less than 1000. – jzd Nov 04 '13 at 14:00
  • the rounding seems a bit odd - `5821` is rounded downwards whereas `101800` is rounded upwards. – SpaceTrucker Nov 04 '13 at 14:08
  • @SpaceTrucker, This was due the 4 character limit and trying to simplify the code. I have expanded the code to try to perfect the rounding and updated my answer. It is slightly more complicated but does show 4 characters in the 5821 case. – jzd Nov 04 '13 at 14:32
  • 7
    rounds 160000 to 200k and also rounds 120000 down to 100k – k1komans Jan 17 '14 at 20:32
  • This code is sensitive to locale, for example in sv_SE locale 1000 converts to 10x10³, which is not matched correctly by the regexp. – Joakim Lundborg Feb 05 '14 at 22:50
  • This answer is definitely less convoluted than the accepted answer – race_carr May 29 '14 at 03:28
  • with the exception of k, the metric prefixes should be upper case http://en.wikipedia.org/wiki/Metric_prefix – Dónal Jul 22 '14 at 17:09
  • @Dónal, following the OP's lead here. His example was all lowercase. – jzd Aug 06 '14 at 13:24
  • 4
    This is broken, I entered the number 10000000000000.0 and it says 103. – Oliver Dixon May 27 '15 at 12:36
24

Need some improvement, but: StrictMath to the rescue!
You can put the suffix in a String or array and fetch'em based on power, or something like that.
The division can also be managed around the power, i think almost everything is about the power value. Hope it helps!

public static String formatValue(double value) {
int power; 
    String suffix = " kmbt";
    String formattedNumber = "";

    NumberFormat formatter = new DecimalFormat("#,###.#");
    power = (int)StrictMath.log10(value);
    value = value/(Math.pow(10,(power/3)*3));
    formattedNumber=formatter.format(value);
    formattedNumber = formattedNumber + suffix.charAt(power/3);
    return formattedNumber.length()>4 ?  formattedNumber.replaceAll("\\.[0-9]+", "") : formattedNumber;  
}

outputs:

999
1.2k
98k
911k
1.1m
11b
712b
34t

jhurtado
  • 8,587
  • 1
  • 23
  • 34
  • 2
    Improved readability a bit, Just needed to add return statement from jzd to solve the 4 char issue. And remember to add suffix if going over t to avoid AIOOB exception. ;) – jhurtado Jan 21 '11 at 05:13
  • This code is sensitive to locale, for example in sv_SE locale 1000 converts to 10x10³, which is not matched correctly by the regexp. – Joakim Lundborg Feb 05 '14 at 22:53
  • 2
    throws an exception for 0, doesn't work for negative numbers, doesn't round 9,999,999 properly (prints 10m)... – assylias Jun 05 '15 at 08:03
24

With Java-12 +, you can use NumberFormat.getCompactNumberInstance to format the numbers. You can create a NumberFormat first as

NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);

and then use it to format:

fmt.format(1000)
$5 ==> "1K"

fmt.format(10000000)
$9 ==> "10M"

fmt.format(1000000000)
$11 ==> "1B"
Naman
  • 27,789
  • 26
  • 218
  • 353
17

Problems with Current Answers

  • Many of the current solutions are using these prefixes k=103, m=106, b=109, t=1012. However, according to various sources, the correct prefixes are k=103, M=106, G=109, T=1012
  • Lack of support for negative numbers (or at least a lack of tests demonstrating that negative numbers are supported)
  • Lack of support for the inverse operation, e.g. converting 1.1k to 1100 (though this is outside the scope of the original question)

Java Solution

This solution (an extension of this answer) addresses the above issues.

import org.apache.commons.lang.math.NumberUtils;

import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.regex.Pattern;


/**
 * Converts a number to a string in <a href="http://en.wikipedia.org/wiki/Metric_prefix">metric prefix</a> format.
 * For example, 7800000 will be formatted as '7.8M'. Numbers under 1000 will be unchanged. Refer to the tests for further examples.
 */
class RoundedMetricPrefixFormat extends Format {

    private static final String[] METRIC_PREFIXES = new String[]{"", "k", "M", "G", "T"};

    /**
     * The maximum number of characters in the output, excluding the negative sign
     */
    private static final Integer MAX_LENGTH = 4;

    private static final Pattern TRAILING_DECIMAL_POINT = Pattern.compile("[0-9]+\\.[kMGT]");

    private static final Pattern METRIC_PREFIXED_NUMBER = Pattern.compile("\\-?[0-9]+(\\.[0-9])?[kMGT]");

    @Override
    public StringBuffer format(Object obj, StringBuffer output, FieldPosition pos) {

        Double number = Double.valueOf(obj.toString());

        // if the number is negative, convert it to a positive number and add the minus sign to the output at the end
        boolean isNegative = number < 0;
        number = Math.abs(number);

        String result = new DecimalFormat("##0E0").format(number);

        Integer index = Character.getNumericValue(result.charAt(result.length() - 1)) / 3;
        result = result.replaceAll("E[0-9]", METRIC_PREFIXES[index]);

        while (result.length() > MAX_LENGTH || TRAILING_DECIMAL_POINT.matcher(result).matches()) {
            int length = result.length();
            result = result.substring(0, length - 2) + result.substring(length - 1);
        }

        return output.append(isNegative ? "-" + result : result);
    }

    /**
     * Convert a String produced by <tt>format()</tt> back to a number. This will generally not restore
     * the original number because <tt>format()</tt> is a lossy operation, e.g.
     *
     * <pre>
     * {@code
     * def formatter = new RoundedMetricPrefixFormat()
     * Long number = 5821L
     * String formattedNumber = formatter.format(number)
     * assert formattedNumber == '5.8k'
     *
     * Long parsedNumber = formatter.parseObject(formattedNumber)
     * assert parsedNumber == 5800
     * assert parsedNumber != number
     * }
     * </pre>
     *
     * @param source a number that may have a metric prefix
     * @param pos if parsing succeeds, this should be updated to the index after the last parsed character
     * @return a Number if the the string is a number without a metric prefix, or a Long if it has a metric prefix
     */
    @Override
    public Object parseObject(String source, ParsePosition pos) {

        if (NumberUtils.isNumber(source)) {

            // if the value is a number (without a prefix) don't return it as a Long or we'll lose any decimals
            pos.setIndex(source.length());
            return toNumber(source);

        } else if (METRIC_PREFIXED_NUMBER.matcher(source).matches()) {

            boolean isNegative = source.charAt(0) == '-';
            int length = source.length();

            String number = isNegative ? source.substring(1, length - 1) : source.substring(0, length - 1);
            String metricPrefix = Character.toString(source.charAt(length - 1));

            Number absoluteNumber = toNumber(number);

            int index = 0;

            for (; index < METRIC_PREFIXES.length; index++) {
                if (METRIC_PREFIXES[index].equals(metricPrefix)) {
                    break;
                }
            }

            Integer exponent = 3 * index;
            Double factor = Math.pow(10, exponent);
            factor *= isNegative ? -1 : 1;

            pos.setIndex(source.length());
            Float result = absoluteNumber.floatValue() * factor.longValue();
            return result.longValue();
        }

        return null;
    }

    private static Number toNumber(String number) {
        return NumberUtils.createNumber(number);
    }
}

Groovy Solution

The solution was originally written in Groovy as shown below.

import org.apache.commons.lang.math.NumberUtils

import java.text.DecimalFormat
import java.text.FieldPosition
import java.text.Format
import java.text.ParsePosition
import java.util.regex.Pattern


/**
 * Converts a number to a string in <a href="http://en.wikipedia.org/wiki/Metric_prefix">metric prefix</a> format.
 * For example, 7800000 will be formatted as '7.8M'. Numbers under 1000 will be unchanged. Refer to the tests for further examples.
 */
class RoundedMetricPrefixFormat extends Format {

    private static final METRIC_PREFIXES = ["", "k", "M", "G", "T"]

    /**
     * The maximum number of characters in the output, excluding the negative sign
     */
    private static final Integer MAX_LENGTH = 4

    private static final Pattern TRAILING_DECIMAL_POINT = ~/[0-9]+\.[kMGT]/

    private static final Pattern METRIC_PREFIXED_NUMBER = ~/\-?[0-9]+(\.[0-9])?[kMGT]/

    @Override
    StringBuffer format(Object obj, StringBuffer output, FieldPosition pos) {

        Double number = obj as Double

        // if the number is negative, convert it to a positive number and add the minus sign to the output at the end
        boolean isNegative = number < 0
        number = Math.abs(number)

        String result = new DecimalFormat("##0E0").format(number)

        Integer index = Character.getNumericValue(result.charAt(result.size() - 1)) / 3
        result = result.replaceAll("E[0-9]", METRIC_PREFIXES[index])

        while (result.size() > MAX_LENGTH || TRAILING_DECIMAL_POINT.matcher(result).matches()) {
            int length = result.size()
            result = result.substring(0, length - 2) + result.substring(length - 1)
        }

        output << (isNegative ? "-$result" : result)
    }

    /**
     * Convert a String produced by <tt>format()</tt> back to a number. This will generally not restore
     * the original number because <tt>format()</tt> is a lossy operation, e.g.
     *
     * <pre>
     * {@code
     * def formatter = new RoundedMetricPrefixFormat()
     * Long number = 5821L
     * String formattedNumber = formatter.format(number)
     * assert formattedNumber == '5.8k'
     *
     * Long parsedNumber = formatter.parseObject(formattedNumber)
     * assert parsedNumber == 5800
     * assert parsedNumber != number
     * }
     * </pre>
     *
     * @param source a number that may have a metric prefix
     * @param pos if parsing succeeds, this should be updated to the index after the last parsed character
     * @return a Number if the the string is a number without a metric prefix, or a Long if it has a metric prefix
     */
    @Override
    Object parseObject(String source, ParsePosition pos) {

        if (source.isNumber()) {

            // if the value is a number (without a prefix) don't return it as a Long or we'll lose any decimals
            pos.index = source.size()
            toNumber(source)

        } else if (METRIC_PREFIXED_NUMBER.matcher(source).matches()) {

            boolean isNegative = source[0] == '-'

            String number = isNegative ? source[1..-2] : source[0..-2]
            String metricPrefix = source[-1]

            Number absoluteNumber = toNumber(number)

            Integer exponent = 3 * METRIC_PREFIXES.indexOf(metricPrefix)
            Long factor = 10 ** exponent
            factor *= isNegative ? -1 : 1

            pos.index = source.size()
            (absoluteNumber * factor) as Long
        }
    }

    private static Number toNumber(String number) {
        NumberUtils.createNumber(number)
    }
}

Tests (Groovy)

The tests are written in Groovy but can be used to verify either either the Java or Groovy class (because they both have the same name and API).

import java.text.Format
import java.text.ParseException

class RoundedMetricPrefixFormatTests extends GroovyTestCase {

    private Format roundedMetricPrefixFormat = new RoundedMetricPrefixFormat()

    void testNumberFormatting() {

        [
                7L         : '7',
                12L        : '12',
                856L       : '856',
                1000L      : '1k',
                (-1000L)   : '-1k',
                5821L      : '5.8k',
                10500L     : '10k',
                101800L    : '102k',
                2000000L   : '2M',
                7800000L   : '7.8M',
                (-7800000L): '-7.8M',
                92150000L  : '92M',
                123200000L : '123M',
                9999999L   : '10M',
                (-9999999L): '-10M'
        ].each { Long rawValue, String expectedRoundValue ->

            assertEquals expectedRoundValue, roundedMetricPrefixFormat.format(rawValue)
        }
    }

    void testStringParsingSuccess() {
        [
                '7'    : 7,
                '8.2'  : 8.2F,
                '856'  : 856,
                '-856' : -856,
                '1k'   : 1000,
                '5.8k' : 5800,
                '-5.8k': -5800,
                '10k'  : 10000,
                '102k' : 102000,
                '2M'   : 2000000,
                '7.8M' : 7800000L,
                '92M'  : 92000000L,
                '-92M' : -92000000L,
                '123M' : 123000000L,
                '10M'  : 10000000L

        ].each { String metricPrefixNumber, Number expectedValue ->

            def parsedNumber = roundedMetricPrefixFormat.parseObject(metricPrefixNumber)
            assertEquals expectedValue, parsedNumber
        }
    }

    void testStringParsingFail() {

        shouldFail(ParseException) {
            roundedMetricPrefixFormat.parseObject('notNumber')
        }
    }
}
Community
  • 1
  • 1
Dónal
  • 185,044
  • 174
  • 569
  • 824
  • 1
    I think you are thinking about the CS prefixes, given that he is talking about billions and trillions I guess he wants short scale numerals. – jhurtado Sep 24 '14 at 20:17
  • 1
    9999999 should print as 9.9m I believe (the numbers are truncated, not rounded). – assylias Jun 05 '15 at 08:15
  • This solution does not have support for the prefixes for values that are smaller than 1, e.g. u (micro) and m (milli). – gbmhunter Mar 25 '16 at 09:08
17

My function for convert big number to small number (with 2 digits). You can change the number of digits by change #.## in DecimalFormat

public String formatValue(float value) {
    String arr[] = {"", "K", "M", "B", "T", "P", "E"};
    int index = 0;
    while ((value / 1000) >= 1) {
        value = value / 1000;
        index++;
    }
    DecimalFormat decimalFormat = new DecimalFormat("#.##");
    return String.format("%s %s", decimalFormat.format(value), arr[index]);
}

Testing

System.out.println(formatValue(100));     //  100
System.out.println(formatValue(1000));    // 1 K
System.out.println(formatValue(10345));   // 10.35 K
System.out.println(formatValue(10012));   // 10.01 K
System.out.println(formatValue(123456));  // 123.46 K
System.out.println(formatValue(4384324)); // 4.38 M
System.out.println(formatValue(10000000)); // 10 M
System.out.println(formatValue(Long.MAX_VALUE)); // 9.22 E

Hope it help

Linh
  • 57,942
  • 23
  • 262
  • 279
14

The ICU lib has a rule based formatter for numbers, which can be used for number spellout etc. I think using ICU would give you a readable and maintanable solution.

[Usage]

The right class is RuleBasedNumberFormat. The format itself can be stored as separate file (or as String constant, IIRC).

Example from http://userguide.icu-project.org/formatparse/numbers

double num = 2718.28;
NumberFormat formatter = 
    new RuleBasedNumberFormat(RuleBasedNumberFormat.SPELLOUT);
String result = formatter.format(num);
System.out.println(result);

The same page shows Roman numerals, so I guess your case should be possible, too.

Landei
  • 54,104
  • 13
  • 100
  • 195
  • The only solution in the thread that doesn't completely fall apart if you need localization. – Grozz Feb 23 '18 at 12:57
  • 3
    If you need it for Android development, this is already included in the framework. Look for `CompactDecimalFormat`. API Level 24+ – Gokhan Arik Jul 10 '19 at 01:41
  • @GokhanArik `CompactDecimalFormat` is pretty useless in Android, see [parse](https://developer.android.com/reference/kotlin/android/icu/text/CompactDecimalFormat#parse) – lasec0203 Nov 03 '20 at 07:01
9

Here's a short implementation without recursion and just a very small loop. Doesn't work with negative numbers but supports all positive longs up to Long.MAX_VALUE:

private static final char[] SUFFIXES = {'k', 'm', 'g', 't', 'p', 'e' };

public static String format(long number) {
    if(number < 1000) {
        // No need to format this
        return String.valueOf(number);
    }
    // Convert to a string
    final String string = String.valueOf(number);
    // The suffix we're using, 1-based
    final int magnitude = (string.length() - 1) / 3;
    // The number of digits we must show before the prefix
    final int digits = (string.length() - 1) % 3 + 1;

    // Build the string
    char[] value = new char[4];
    for(int i = 0; i < digits; i++) {
        value[i] = string.charAt(i);
    }
    int valueLength = digits;
    // Can and should we add a decimal point and an additional number?
    if(digits == 1 && string.charAt(1) != '0') {
        value[valueLength++] = '.';
        value[valueLength++] = string.charAt(1);
    }
    value[valueLength++] = SUFFIXES[magnitude - 1];
    return new String(value, 0, valueLength);
}

Outputs:

1k
5.8k
10k
101k
2m
7.8m
92m
123m
9.2e (this is Long.MAX_VALUE)

I also did some really simple benchmarking (formatting 10 million random longs) and it's considerably faster than Elijah's implementation and slightly faster than assylias' implementation.

Mine: 1137.028 ms
Elijah's: 2664.396 ms
assylias': 1373.473 ms

Raniz
  • 10,882
  • 1
  • 32
  • 64
8

Important: Answers casting to double will fail for numbers like 99999999999999999L and return 100P instead of 99P because double uses the IEEE standard:

If a decimal string with at most 15 significant digits is converted to IEEE 754 double precision representation and then converted back to a string with the same number of significant digits, then the final string should match the original. [long has up to 19 significant digits.]

System.out.println((long)(double)99999999999999992L); // 100000000000000000
System.out.println((long)(double)99999999999999991L); //  99999999999999984
// it is even worse for the logarithm:
System.out.println(Math.log10(99999999999999600L)); // 17.0
System.out.println(Math.log10(99999999999999500L)); // 16.999999999999996

This solution cuts off unwanted digits and works for all long values. Simple but performant implementation (comparison below). -120k can't be expressed with 4 characters, even -0.1M is too long, that's why for negative numbers 5 characters have to be okay:

private static final char[] magnitudes = {'k', 'M', 'G', 'T', 'P', 'E'}; // enough for long

public static final String convert(long number) {
    String ret;
    if (number >= 0) {
        ret = "";
    } else if (number <= -9200000000000000000L) {
        return "-9.2E";
    } else {
        ret = "-";
        number = -number;
    }
    if (number < 1000)
        return ret + number;
    for (int i = 0; ; i++) {
        if (number < 10000 && number % 1000 >= 100)
            return ret + (number / 1000) + '.' + ((number % 1000) / 100) + magnitudes[i];
        number /= 1000;
        if (number < 1000)
            return ret + number + magnitudes[i];
    }
}

The test in the else if at the beginning is necessairy because the min is -(2^63) and the max is (2^63)-1 and therefore the assignment number = -number would fail if number == Long.MIN_VALUE. If we have to do a check, then we can as well include as many numbers as possible instead of just checking for number == Long.MIN_VALUE.

The comparison of this implementation with the one who got the most upvotes (said to be the fastest currently) showed that it is more than 5 times faster (it depends on the test settings, but with more numbers the gain gets bigger and this implementation has to do more checks because it handles all cases, so if the other one would be fixed the difference would become even bigger). It is that fast because there are no floating point operations, no logarithm, no power, no recursion, no regex, no sophisticated formatters and minimization of the amount of objects created.


Here is the test program:

public class Test {

    public static void main(String[] args) {
        long[] numbers = new long[20000000];
        for (int i = 0; i < numbers.length; i++)
            numbers[i] = Math.random() < 0.5 ? (long) (Math.random() * Long.MAX_VALUE) : (long) (Math.random() * Long.MIN_VALUE);
        System.out.println(convert1(numbers) + " vs. " + convert2(numbers));
    }

    private static long convert1(long[] numbers) {
        long l = System.currentTimeMillis();
        for (int i = 0; i < numbers.length; i++)
            Converter1.convert(numbers[i]);
        return System.currentTimeMillis() - l;
    }

    private static long convert2(long[] numbers) {
        long l = System.currentTimeMillis();
        for (int i = 0; i < numbers.length; i++)
            Converter2.coolFormat(numbers[i], 0);
        return System.currentTimeMillis() - l;
    }

}

Possible output: 2309 vs. 11591 (about the same when only using positive numbers and much more extreme when reversing the order of execution, maybe it has something to do with garbage collection)

maraca
  • 8,468
  • 3
  • 23
  • 45
8

For anyone that wants to round. This is a great, easy to read solution, that takes advantage of the Java.Lang.Math library

 public static String formatNumberExample(Number number) {
        char[] suffix = {' ', 'k', 'M', 'B', 'T', 'P', 'E'};
        long numValue = number.longValue();
        int value = (int) Math.floor(Math.log10(numValue));
        int base = value / 3;
        if (value >= 3 && base < suffix.length) {
            return new DecimalFormat("~#0.0").format(numValue / Math.pow(10, base * 3)) + suffix[base];
        } else {
            return new DecimalFormat("#,##0").format(numValue);
        }
    }
Chris
  • 81
  • 1
  • 3
7

I don't know if it's the best approach but, this is what i did.

7=>7
12=>12
856=>856
1000=>1.0k
5821=>5.82k
10500=>10.5k
101800=>101.8k
2000000=>2.0m
7800000=>7.8m
92150000=>92.15m
123200000=>123.2m
9999999=>10.0m

--- Code---

public String Format(Integer number){
    String[] suffix = new String[]{"k","m","b","t"};
    int size = (number.intValue() != 0) ? (int) Math.log10(number) : 0;
    if (size >= 3){
        while (size % 3 != 0) {
            size = size - 1;
        }
    }
    double notation = Math.pow(10, size);
    String result = (size >= 3) ? + (Math.round((number / notation) * 100) / 100.0d)+suffix[(size/3) - 1] : + number + "";
    return result
}
7

The following code shows how you can do this with easy expansion in mind.

The "magic" lies mostly in the makeDecimal function which, for the correct values passed in, guarantees you will never have more than four characters in the output.

It first extracts the whole and tenths portions for a given divisor so, for example, 12,345,678 with a divisor of 1,000,000 will give a whole value of 12 and a tenths value of 3.

From that, it can decide whether it outputs just the whole part or both the whole and tenths part, using the rules:

  • If tenths part is zero, just output whole part and suffix.
  • If whole part is greater than nine, just output whole part and suffix.
  • Otherwise, output whole part, tenths part and suffix.

The code for that follows:

static private String makeDecimal(long val, long div, String sfx) {
    val = val / (div / 10);
    long whole = val / 10;
    long tenths = val % 10;
    if ((tenths == 0) || (whole >= 10))
        return String.format("%d%s", whole, sfx);
    return String.format("%d.%d%s", whole, tenths, sfx);
}

Then, it's a simple matter of calling that helper function with the correct values, including some constants to make life easier for the developer:

static final long THOU =                1000L;
static final long MILL =             1000000L;
static final long BILL =          1000000000L;
static final long TRIL =       1000000000000L;
static final long QUAD =    1000000000000000L;
static final long QUIN = 1000000000000000000L;

static private String Xlat(long val) {
    if (val < THOU) return Long.toString(val);
    if (val < MILL) return makeDecimal(val, THOU, "k");
    if (val < BILL) return makeDecimal(val, MILL, "m");
    if (val < TRIL) return makeDecimal(val, BILL, "b");
    if (val < QUAD) return makeDecimal(val, TRIL, "t");
    if (val < QUIN) return makeDecimal(val, QUAD, "q");
    return makeDecimal(val, QUIN, "u");
}

The fact that the makeDecimal function does the grunt work means that expanding beyond 999,999,999 is just a matter of adding an extra line to Xlat, so easy that I've done it for you.

The final return in Xlat doesn't need a conditional since the largest value you can hold in a 64-bit signed long is only about 9.2 quintillion.

But if, by some bizarre requirement, Oracle decides to add a 128-bit longer type or a 1024-bit damn_long type, you'll be ready for it :-)


And, finally, a little test harness you can use for validating the functionality.

public static void main(String[] args) {
    long vals[] = {
        999L, 1000L, 5821L, 10500L, 101800L, 2000000L,
        7800000L, 92150000L, 123200000L, 999999999L,
        1000000000L, 1100000000L, 999999999999L,
        1000000000000L, 999999999999999L,
        1000000000000000L, 9223372036854775807L
    };
    for (long val: vals)
        System.out.println ("" + val + " -> " + Xlat(val));
    }
}

You can see from the output that it gives you what you need:

999 -> 999
1000 -> 1k
5821 -> 5.8k
10500 -> 10k
101800 -> 101k
2000000 -> 2m
7800000 -> 7.8m
92150000 -> 92m
123200000 -> 123m
999999999 -> 999m
1000000000 -> 1b
1100000000 -> 1.1b
999999999999 -> 999b
1000000000000 -> 1t
999999999999999 -> 999t
1000000000000000 -> 1q
9223372036854775807 -> 9.2u

And, as an aside, be aware that passing in a negative number to this function will result in a string too long for your requirements, since it follows the < THOU path). I figured that was okay since you only mention non-negative values in the question.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
6

this is my code. clean and simple .

public static String getRoughNumber(long value) {
    if (value <= 999) {
        return String.valueOf(value);
    }

    final String[] units = new String[]{"", "K", "M", "B", "P"};
    int digitGroups = (int) (Math.log10(value) / Math.log10(1000));
    return new DecimalFormat("#,##0.#").format(value / Math.pow(1000, digitGroups)) + "" + units[digitGroups];

}
5

My favorite. You could use "k" and so on as indicator for decimal too, as common in the electronic domain. This will give you an extra digit without additional space

Second column tries to use as much digits as possible

1000 => 1.0k | 1000
5821 => 5.8k | 5821
10500 => 10k | 10k5
101800 => 101k | 101k
2000000 => 2.0m | 2m
7800000 => 7.8m | 7m8
92150000 => 92m | 92m1
123200000 => 123m | 123m
9999999 => 9.9m | 9m99

This is the code

public class HTTest {
private static String[] unit = {"u", "k", "m", "g", "t"};
/**
 * @param args
 */
public static void main(String[] args) {
    int[] numbers = new int[]{1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000, 9999999};
    for(int n : numbers) {
        System.out.println(n + " => " + myFormat(n) + " | " + myFormat2(n));
    }
}

private static String myFormat(int pN) {
    String str = Integer.toString(pN);
    int len = str.length ()-1;
    if (len <= 3) return str;
    int level = len / 3;
    int mode = len % 3;
    switch (mode) {
    case 0: return str.substring(0, 1) + "." + str.substring(1, 2) + unit[level];
    case 1: return str.substring(0, 2) + unit[level];
    case 2: return str.substring(0, 3) + unit[level];
    }
    return "how that?";
}
private static String trim1 (String pVal) {
    if (pVal.equals("0")) return "";
    return pVal;
}
private static String trim2 (String pVal) {
    if (pVal.equals("00")) return "";
    return pVal.substring(0, 1) + trim1(pVal.substring(1,2));
}
private static String myFormat2(int pN) {
    String str = Integer.toString(pN);
    int len = str.length () - 1;
    if (len <= 3) return str;
    int level = len / 3;
    int mode = len % 3;
    switch (mode) {
    case 0: return str.substring(0, 1) + unit[level] + trim2(str.substring(1, 3));
    case 2: return str.substring(0, 3) + unit[level];
    case 1: return str.substring(0, 2) + unit[level] + trim1(str.substring(2, 3));
    }
    return "how that?";
}
}
stefan bachert
  • 9,413
  • 4
  • 33
  • 40
5

My Java is rusty, but here's how I'd implement it in C#:

private string  FormatNumber(double value)
    {
    string[]  suffixes = new string[] {" k", " m", " b", " t", " q"};
    for (int j = suffixes.Length;  j > 0;  j--)
        {
        double  unit = Math.Pow(1000, j);
        if (value >= unit)
            return (value / unit).ToString("#,##0.0") + suffixes[--j];
        }
    return value.ToString("#,##0");
    }

It'd be easy to adjust this to use CS kilos (1,024) rather than metric kilos, or to add more units. It formats 1,000 as "1.0 k" rather than "1 k", but I trust that's immaterial.

To meet the more specific requirement "no more than four characters", remove the spaces before the suffixes and adjust the middle block like this:

if (value >= unit)
  {
  value /= unit;
  return (value).ToString(value >= unit * 9.95 ? "#,##0" : "#,##0.0") + suffixes[--j];
  }
4

Staying true to my comment that I'd value readability above performance, here's a version where it should be clear what's happening (assuming you've used BigDecimals before) without excessive commenting (I believe in self-documenting code), without worrying about performance (since I can't picture a scenario where you'd want to do this so many millions of times that performance even becomes a consideration).

This version:

  • uses BigDecimals for precision and to avoid rounding issues
  • works for rounding down as requested by the OP
  • works for other rounding modes, e.g. HALF_UP as in the tests
  • allows you to adjust the precision (change REQUIRED_PRECISION)
  • uses an enum to define the thresholds, i.e. could easily be adjusted to use KB/MB/GB/TB instead of k/m/b/t, etc., and could of course be extended beyond TRILLION if required
  • comes with thorough unit tests, since the test cases in the question weren't testing the borders
  • should work for zero and negative numbers

Threshold.java:

import java.math.BigDecimal;

public enum Threshold {
  TRILLION("1000000000000", 12, 't', null),
  BILLION("1000000000", 9, 'b', TRILLION),
  MILLION("1000000", 6, 'm', BILLION),
  THOUSAND("1000", 3, 'k', MILLION),
  ZERO("0", 0, null, THOUSAND);

  private BigDecimal value;
  private int zeroes;
  protected Character suffix;
  private Threshold higherThreshold;

  private Threshold(String aValueString, int aNumberOfZeroes, Character aSuffix,
      Threshold aThreshold) {
    value = new BigDecimal(aValueString);
    zeroes = aNumberOfZeroes;
    suffix = aSuffix;
    higherThreshold = aThreshold;
  }

  public static Threshold thresholdFor(long aValue) {
    return thresholdFor(new BigDecimal(aValue));
  }

  public static Threshold thresholdFor(BigDecimal aValue) {
    for (Threshold eachThreshold : Threshold.values()) {
      if (eachThreshold.value.compareTo(aValue) <= 0) {
        return eachThreshold;
      }
    }
    return TRILLION; // shouldn't be needed, but you might have to extend the enum
  }

  public int getNumberOfZeroes() {
    return zeroes;
  }

  public String getSuffix() {
    return suffix == null ? "" : "" + suffix;
  }

  public Threshold getHigherThreshold() {
    return higherThreshold;
  }
}

NumberShortener.java:

import java.math.BigDecimal;
import java.math.RoundingMode;

public class NumberShortener {

  public static final int REQUIRED_PRECISION = 2;

  public static BigDecimal toPrecisionWithoutLoss(BigDecimal aBigDecimal,
      int aPrecision, RoundingMode aMode) {
    int previousScale = aBigDecimal.scale();
    int previousPrecision = aBigDecimal.precision();
    int newPrecision = Math.max(previousPrecision - previousScale, aPrecision);
    return aBigDecimal.setScale(previousScale + newPrecision - previousPrecision,
        aMode);
  }

  private static BigDecimal scaledNumber(BigDecimal aNumber, RoundingMode aMode) {
    Threshold threshold = Threshold.thresholdFor(aNumber);
    BigDecimal adjustedNumber = aNumber.movePointLeft(threshold.getNumberOfZeroes());
    BigDecimal scaledNumber = toPrecisionWithoutLoss(adjustedNumber, REQUIRED_PRECISION,
        aMode).stripTrailingZeros();
    // System.out.println("Number: <" + aNumber + ">, adjusted: <" + adjustedNumber
    // + ">, rounded: <" + scaledNumber + ">");
    return scaledNumber;
  }

  public static String shortenedNumber(long aNumber, RoundingMode aMode) {
    boolean isNegative = aNumber < 0;
    BigDecimal numberAsBigDecimal = new BigDecimal(isNegative ? -aNumber : aNumber);
    Threshold threshold = Threshold.thresholdFor(numberAsBigDecimal);
    BigDecimal scaledNumber = aNumber == 0 ? numberAsBigDecimal : scaledNumber(
        numberAsBigDecimal, aMode);
    if (scaledNumber.compareTo(new BigDecimal("1000")) >= 0) {
      scaledNumber = scaledNumber(scaledNumber, aMode);
      threshold = threshold.getHigherThreshold();
    }
    String sign = isNegative ? "-" : "";
    String printNumber = sign + scaledNumber.stripTrailingZeros().toPlainString()
        + threshold.getSuffix();
    // System.out.println("Number: <" + sign + numberAsBigDecimal + ">, rounded: <"
    // + sign + scaledNumber + ">, print: <" + printNumber + ">");
    return printNumber;
  }
}

(Uncomment the println statements or change to use your favourite logger to see what it's doing.)

And finally, the tests in NumberShortenerTest (plain JUnit 4):

import static org.junit.Assert.*;

import java.math.BigDecimal;
import java.math.RoundingMode;

import org.junit.Test;

public class NumberShortenerTest {

  private static final long[] NUMBERS_FROM_OP = new long[] { 1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000 };
  private static final String[] EXPECTED_FROM_OP = new String[] { "1k", "5.8k", "10k", "101k", "2m", "7.8m", "92m", "123m" };
  private static final String[] EXPECTED_FROM_OP_HALF_UP = new String[] { "1k", "5.8k", "11k", "102k", "2m", "7.8m", "92m", "123m" };
  private static final long[] NUMBERS_TO_TEST = new long[] { 1, 500, 999, 1000, 1001, 1009, 1049, 1050, 1099, 1100, 12345, 123456, 999999, 1000000,
      1000099, 1000999, 1009999, 1099999, 1100000, 1234567, 999999999, 1000000000, 9123456789L, 123456789123L };
  private static final String[] EXPECTED_FROM_TEST = new String[] { "1", "500", "999", "1k", "1k", "1k", "1k", "1k", "1k", "1.1k", "12k", "123k",
      "999k", "1m", "1m", "1m", "1m", "1m", "1.1m", "1.2m", "999m", "1b", "9.1b", "123b" };
  private static final String[] EXPECTED_FROM_TEST_HALF_UP = new String[] { "1", "500", "999", "1k", "1k", "1k", "1k", "1.1k", "1.1k", "1.1k", "12k",
      "123k", "1m", "1m", "1m", "1m", "1m", "1.1m", "1.1m", "1.2m", "1b", "1b", "9.1b", "123b" };

  @Test
  public void testThresholdFor() {
    assertEquals(Threshold.ZERO, Threshold.thresholdFor(1));
    assertEquals(Threshold.ZERO, Threshold.thresholdFor(999));
    assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(1000));
    assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(1234));
    assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(9999));
    assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(999999));
    assertEquals(Threshold.MILLION, Threshold.thresholdFor(1000000));
  }

  @Test
  public void testToPrecision() {
    RoundingMode mode = RoundingMode.DOWN;
    assertEquals(new BigDecimal("1"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 1, mode));
    assertEquals(new BigDecimal("1.2"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 2, mode));
    assertEquals(new BigDecimal("1.23"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 3, mode));
    assertEquals(new BigDecimal("1.234"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 4, mode));
    assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 4, mode).stripTrailingZeros()
        .toPlainString());
    assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 2, mode).stripTrailingZeros()
        .toPlainString());
    assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999.9"), 2, mode).stripTrailingZeros()
        .toPlainString());

    mode = RoundingMode.HALF_UP;
    assertEquals(new BigDecimal("1"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 1, mode));
    assertEquals(new BigDecimal("1.2"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 2, mode));
    assertEquals(new BigDecimal("1.23"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 3, mode));
    assertEquals(new BigDecimal("1.235"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 4, mode));
    assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 4, mode).stripTrailingZeros()
        .toPlainString());
    assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 2, mode).stripTrailingZeros()
        .toPlainString());
    assertEquals(new BigDecimal("1000").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999.9"), 2, mode)
        .stripTrailingZeros().toPlainString());
  }

  @Test
  public void testNumbersFromOP() {
    for (int i = 0; i < NUMBERS_FROM_OP.length; i++) {
      assertEquals("Index " + i + ": " + NUMBERS_FROM_OP[i], EXPECTED_FROM_OP[i],
          NumberShortener.shortenedNumber(NUMBERS_FROM_OP[i], RoundingMode.DOWN));
      assertEquals("Index " + i + ": " + NUMBERS_FROM_OP[i], EXPECTED_FROM_OP_HALF_UP[i],
          NumberShortener.shortenedNumber(NUMBERS_FROM_OP[i], RoundingMode.HALF_UP));
    }
  }

  @Test
  public void testBorders() {
    assertEquals("Zero: " + 0, "0", NumberShortener.shortenedNumber(0, RoundingMode.DOWN));
    assertEquals("Zero: " + 0, "0", NumberShortener.shortenedNumber(0, RoundingMode.HALF_UP));
    for (int i = 0; i < NUMBERS_TO_TEST.length; i++) {
      assertEquals("Index " + i + ": " + NUMBERS_TO_TEST[i], EXPECTED_FROM_TEST[i],
          NumberShortener.shortenedNumber(NUMBERS_TO_TEST[i], RoundingMode.DOWN));
      assertEquals("Index " + i + ": " + NUMBERS_TO_TEST[i], EXPECTED_FROM_TEST_HALF_UP[i],
          NumberShortener.shortenedNumber(NUMBERS_TO_TEST[i], RoundingMode.HALF_UP));
    }
  }

  @Test
  public void testNegativeBorders() {
    for (int i = 0; i < NUMBERS_TO_TEST.length; i++) {
      assertEquals("Index " + i + ": -" + NUMBERS_TO_TEST[i], "-" + EXPECTED_FROM_TEST[i],
          NumberShortener.shortenedNumber(-NUMBERS_TO_TEST[i], RoundingMode.DOWN));
      assertEquals("Index " + i + ": -" + NUMBERS_TO_TEST[i], "-" + EXPECTED_FROM_TEST_HALF_UP[i],
          NumberShortener.shortenedNumber(-NUMBERS_TO_TEST[i], RoundingMode.HALF_UP));
    }
  }
}

Feel free to point out in the comments if I missed a significant test case or if expected values should be adjusted.

Amos M. Carpenter
  • 4,848
  • 4
  • 42
  • 72
  • The only obvious downside in your solution seem the V+H-scrollbars for your code, this lowers readability. Do you think a reformatting would be possible without losing clarity? – Wolf Jun 10 '15 at 15:22
  • @Wolf: Was hoping to get away with copy/pasting from my IDE, but you're right, it's hypocritical of me to claim readability and require horizontal scrolling, so thanks for pointing that out. ;-) I've updated the first two bits of code, as those are the ones you'd be looking at to see what's happening, but left the test code, as looking at it by itself isn't so helpful - you'd probably want to paste that into your own IDE to run the unit tests if you wanted to see that the tests work. Hope that's ok. – Amos M. Carpenter Jun 10 '15 at 15:35
  • Ah, good. But in the last box, the test cases, the expected results could be - optically - better related to the inputs (I mean the literals in the first 6 arrays). – Wolf Jun 10 '15 at 15:51
  • @Wolf: I'm not a fan of trying to align items in a line with spaces or tabs - that cannot be easily configured consistently for all cases in my favourite formatter (eclipse), and doing it manually... that way lies madness, because of all the adjustments you have to make each time you add or remove an item. If I really wanted to see them aligned, I'd just paste the numbers/values into a spreadsheet as CSV. – Amos M. Carpenter Jun 10 '15 at 23:37
  • @maraca: if the OP had wanted "short, compressed and less readable code", he should've asked it on http://codegolf.stackexchange.com/ instead of SO. ;-) About the same time to analyse? Uhm... try debugging your edge cases in both your and my version. As I said, if required for larger numbers, the enum could easily be extended. As for doing this in excel files, see [this answer](http://stackoverflow.com/a/19573971/983430). – Amos M. Carpenter Jun 10 '15 at 23:43
  • @AmosM.Carpenter less readable is the wrong expression, I think you can even grab it faster as good programmer, just need to understand initialization and one iteration and you know it works for all. We don't know if he wanted fast and simple or generalized. About excel, it is not that easy to reproduce this exact format (and excel could make rounding errors itself) and you have to do it with poi. or it could be an html table, xml, json,... – maraca Jun 11 '15 at 00:25
  • I personally don't find this more readable - it's a lot of boiler plate code (2 classes and 9 methods) for a fairly simple task. That's my subjective opinion of course... – assylias Jun 11 '15 at 14:26
  • 1
    All depends on what you're after, @assylias. If you're just after solving a one-off use case, your solution should work fine; I like the `TreeMap` approach. "Readability" is subjective, of course. ;-) Now what if someone wants to round differently than truncating in your version? (For instance, when using this to indicate file size, who'd want to truncate?) If you want powers of 2 rather than 10? You'd have to rewrite a fair bit, wouldn't you? Like I said, I was deliberately not trying to golf my code, much of which could've been shortened (I'd never keep an if-then on one line, for example). – Amos M. Carpenter Jun 12 '15 at 00:03
2

Adding my own answer, Java code, self explanatory code..

import java.math.BigDecimal;

/**
 * Method to convert number to formatted number.
 * 
 * @author Gautham PJ
 */
public class ShortFormatNumbers
{

    /**
     * Main method. Execution starts here.
     */
    public static void main(String[] args)
    {

        // The numbers that are being converted.
        int[] numbers = {999, 1400, 2500, 45673463, 983456, 234234567};


        // Call the "formatNumber" method on individual numbers to format 
        // the number.
        for(int number : numbers)
        {
            System.out.println(number + ": " + formatNumber(number));
        }

    }


    /**
     * Format the number to display it in short format.
     * 
     * The number is divided by 1000 to find which denomination to be added 
     * to the number. Dividing the number will give the smallest possible 
     * value with the denomination.
     * 
     * @param the number that needs to be converted to short hand notation.
     * @return the converted short hand notation for the number.
     */
    private static String formatNumber(double number)
    {
        String[] denominations = {"", "k", "m", "b", "t"};
        int denominationIndex = 0;

        // If number is greater than 1000, divide the number by 1000 and 
        // increment the index for the denomination.
        while(number > 1000.0)
        {
            denominationIndex++;
            number = number / 1000.0;
        }

        // To round it to 2 digits.
        BigDecimal bigDecimal = new BigDecimal(number);
        bigDecimal = bigDecimal.setScale(2, BigDecimal.ROUND_HALF_EVEN);


        // Add the number with the denomination to get the final value.
        String formattedNumber = bigDecimal + denominations[denominationIndex];
        return formattedNumber;
    }

}
LearningDeveloper
  • 638
  • 2
  • 11
  • 23
1
//code longer but work sure...

public static String formatK(int number) {
    if (number < 999) {
        return String.valueOf(number);
    }

    if (number < 9999) {
        String strNumber = String.valueOf(number);
        String str1 = strNumber.substring(0, 1);
        String str2 = strNumber.substring(1, 2);
        if (str2.equals("0")) {
            return str1 + "k";
        } else {
            return str1 + "." + str2 + "k";
        }
    }

    if (number < 99999) {
        String strNumber = String.valueOf(number);
        String str1 = strNumber.substring(0, 2);
        return str1 + "k";
    }

    if (number < 999999) {
        String strNumber = String.valueOf(number);
        String str1 = strNumber.substring(0, 3);
        return str1 + "k";
    }

    if (number < 9999999) {
        String strNumber = String.valueOf(number);
        String str1 = strNumber.substring(0, 1);
        String str2 = strNumber.substring(1, 2);
        if (str2.equals("0")) {
            return str1 + "m";
        } else {
            return str1 + "." + str2 + "m";
        }
    }

    if (number < 99999999) {
        String strNumber = String.valueOf(number);
        String str1 = strNumber.substring(0, 2);
        return str1 + "m";
    }

    if (number < 999999999) {
        String strNumber = String.valueOf(number);
        String str1 = strNumber.substring(0, 3);
        return str1 + "m";
    }

    NumberFormat formatterHasDigi = new DecimalFormat("###,###,###");
    return formatterHasDigi.format(number);
}
demongolem
  • 9,474
  • 36
  • 90
  • 105
1

This code snippet just deadly simple, and clean code, and totally works:

private static char[] c = new char[]{'K', 'M', 'B', 'T'};
private String formatK(double n, int iteration) {
    if (n < 1000) {
        // print 999 or 999K
        if (iteration <= 0) {
            return String.valueOf((long) n);
        } else {
            return String.format("%d%s", Math.round(n), c[iteration-1]);
        }
    } else if (n < 10000) {
        // Print 9.9K
        return String.format("%.1f%s", n/1000, c[iteration]);
    } else {
        // Increase 1 iteration
        return formatK(Math.round(n/1000), iteration+1);
    }
}
KimKha
  • 4,370
  • 1
  • 37
  • 45
1

try this :

public String Format(Integer number){
    String[] suffix = new String[]{"k","m","b","t"};
    int size = (number.intValue() != 0) ? (int) Math.log10(number) : 0;
    if (size >= 3){
        while (size % 3 != 0) {
            size = size - 1;
        }
    }
    double notation = Math.pow(10, size);
    String result = (size >= 3) ? + (Math.round((number / notation) * 100) / 100.0d)+suffix[(size/3) - 1] : + number + "";
    return result
}
AzizAhmad
  • 637
  • 1
  • 9
  • 20
1

There is a solution on the Maven Central

<dependency>
  <groupId>com.github.bogdanovmn.humanreadablevalues</groupId>
  <artifactId>human-readable-values</artifactId>
  <version>1.0.1</version>
</dependency>

You can just get values for amount of bytes or seconds. Also you can create you own factorization class.

Docs https://github.com/bogdanovmn/java-human-readable-values

Seconds example

assertEquals(
    "2h 46m 40s",
    new SecondsValue(10000).fullString()
);

assertEquals(
    "2.8h",
    new SecondsValue(10000).shortString()
);

Bytes example

assertEquals(
    "9K 784b",
    new BytesValue(10000).fullString()
);

assertEquals(
    "9.8K",
    new BytesValue(10000).shortString()
);
Elegant.Obj
  • 131
  • 1
  • 12
1

Set the divisor according to the the input number: 1000, 100000, 1000000, 1000000000 etc...

check the whole part(first part without fraction) of the number if its size is 1 then cast the input to long + String. if the size is >= 2 then divide the input and use DecimalFormat to show fractional part as desired.

you can use // .setRoundingMode(RoundingMode.DOWN) to deal with rounding

public static String format(long num) {

        String suffix = "", result;
        double divisor = 0;

        DecimalFormat df = new DecimalFormat("##");
        DecimalFormat ds = new DecimalFormat("##.#");
        // ds.setRoundingMode(RoundingMode.DOWN);

        if ( num >= 1000  && num < 1000000 ) {
            divisor = 1000;
            suffix = "K";
        } else if ( num >= 1000000 && num < 1000000000 ) {
            divisor = 1000000;
            suffix = "M";
        } else if (num >= 1000000000) {
            divisor = 1000000000;
            suffix = "B";
        } else {
            System.out.print("The number is Too big > T or TOO small < K");
        }

        int numlengt = df.format(num / divisor).length();

        if (numlengt >= 2) {
            result = (long) (num / divisor) + suffix;
        } else {
            result = ds.format(num / divisor) + suffix;
        }
        return result;
    }
adam
  • 105
  • 5
  • Please fix your variable names. What the hell is df and ds? – TheRealChx101 Aug 09 '21 at 20:34
  • @TheRealChx101 Can you suggest better names please , maybe (df > getint, ds > fractionFormat)? . Frankly I dont see any problem with those names – adam Aug 10 '21 at 10:14
0
public class NumberToReadableWordFormat {

    public static void main(String[] args) {
        Integer[] numbers = new Integer[]{1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000, 9999999,999};
        for(int n : numbers) {
            System.out.println(n + " => " + coolFormat(n));
        }
    }

    private static String[] c = new String[]{"K", "L", "Cr"};
    private static String coolFormat(int n) {
        int size = String.valueOf(n).length();
        if (size>=4 && size<6) {
                int value = (int) Math.pow(10, 1);
                double d = (double) Math.round(n/1000.0 * value) / value;
                return (double) Math.round(n/1000.0 * value) / value+" "+c[0];
        } else if(size>5 && size<8) {
                int value = (int) Math.pow(10, 1);
                return (double) Math.round(n/100000.0 * value) / value+" "+c[1];
        } else if(size>=8) {
                int value = (int) Math.pow(10, 1);
                return (double) Math.round(n/10000000.0 * value) / value+" "+c[2];
        } else {
            return n+"";
        }
    }
}

Output:

1000 => 1.0 K

5821 => 5.8 K

10500 => 10.5 K

101800 => 1.0 L

2000000 => 20.0 L

7800000 => 78.0 L

92150000 => 9.2 Cr

123200000 => 12.3 Cr

9999999 => 100.0 L

999 => 999
frido
  • 13,065
  • 5
  • 42
  • 56
Ankit Singh
  • 325
  • 3
  • 12
0

Here is a another simple solution for your problem. let say

String abbr="M,K,T,B";

double yvalue=some random number; String string ="#.##" //decimal places whatever you want

public  String format(Double yvalue, String string,String abbr) {
    DecimalFormat df = new DecimalFormat(getnumberformatpattern(string));
      if (yvalue < 0) return "-" + format(-yvalue,string,abbr);
          double finalvalue= yvalue;
          String newnumber="";
          
                if (abbr.indexOf("K")>0){   
                    finalvalue= (yvalue / 1e3);
                    newnumber=df.format(finalvalue) +'K';
                }
                if (abbr.indexOf("M")>0 ){
                if(yvalue>=1e6){
                    finalvalue= (yvalue / 1e6);
                    newnumber=df.format(finalvalue) +'M';
                    };
                }
                if (abbr.indexOf("B")>0 )
                {
                if((newnumber.indexOf("M")<0) || yvalue>=1e9){
                    finalvalue= (yvalue / 1e9);
                    newnumber=df.format(finalvalue) +'B';                   }
                }
                if (abbr.indexOf("T")>0 ){ 
                if((newnumber.indexOf("B")<0) || yvalue>=1e12){
                    finalvalue= (yvalue / 1e12);
                    newnumber=df.format(finalvalue) +'T';                   }
                }
                return newnumber;
    }
Sunil Sunny
  • 531
  • 7
  • 30
0
  fun getKLCrValue(input: Long):String{
        return if(input.toString().length<4){
            input.toString()
        }else if(input.toString().length<5)
            ( ""+getExactValue(input.toString()[0] +"."+ input.toString()[1]) +"K")
        else if(input.toString().length<6)
            (""+getExactValue(""+input.toString().subSequence(0, 2) +"."+ input.toString()[2]) +"K")
        else if(input.toString().length<7)
            (""+ getExactValue( input.toString()[0] +"."+input.toString().subSequence(1, 3))+"L")
        else if(input.toString().length<8)
            (""+ getExactValue( ""+input.toString().subSequence(0, 2)+"."+input.toString().subSequence(2,4))+"L")
        else if(input.toString().length<9)
            (""+ getExactValue( input.toString()[0] +"."+input.toString().subSequence(1,3))+"Cr")
        else
            (""+ getExactValue( ""+input.toString().subSequence(0, input.toString().length-7)+"."+input.toString().subSequence( input.toString().length-7, input.toString().length-5))+"cr")

    }

    private fun getExactValue(value: String): String {
        return value.replace(".00", "")
    }

You can just call getKLCrValue(1234) and you will get desired output

Output-->

1 -> 1
10 -> 10
12 -> 12
100 -> 100
123 -> 123
1000 -> 1K
1234 -> 1.2K
10000 -> 10K
12345 -> 12.3K
100000 -> 1L
123456 -> 1.23L
1000000 -> 10L
1234567 -> 12.34L
10000000 -> 1cr
12345678 -> 1.23cr
100000000 -> 10cr
123456789 -> 12.34cr
1000000000 -> 100cr
1234567890 -> 123.45cr
10000000000 -> 1000cr
11111111111 -> 1111.11cr
Ranajit Sawant
  • 181
  • 1
  • 6