3

From this Standard dictionary numbers, I need a fastest way to convert some number below :

1000000 = 1 Million
1435234 = 1.43 Million
350000000 = 350 Million
1000000000 = 1 Billion
1765000000 = 1.76 Billion
1000000000000 = 1 Trillion
1345342345000 = 1.34 Trillion
1000000000000000 = 1 Quadrillion
100000000000000000 = 100 Quadrillion

and further.

I have tried like this below:

public String truncateNumber(float floatNumber) {
    long million = 1000000L;
    long billion = 1000000000L;
    long trillion = 1000000000000L;
    long number = Math.round(floatNumber);
    if ((number >= million) && (number < billion)) {
        float fraction = calculateFraction(number, million);
        return Float.toString(fraction) + "M";
    } else if ((number >= billion) && (number < trillion)) {
        float fraction = calculateFraction(number, billion);
        return Float.toString(fraction) + "B";
    }
    return Long.toString(number);
}

public float calculateFraction(long number, long divisor) {
    long truncate = (number * 10L + (divisor / 2L)) / divisor;
    float fraction = (float) truncate * 0.10F;
    return fraction;
}

but I think my solution is not quite true. So, what is a fastest way to do that in Java? Many Thanks.

eddie
  • 1,252
  • 3
  • 15
  • 20
user3707836
  • 63
  • 1
  • 1
  • 6

4 Answers4

5

The first problem is that float does not have enough precision to represent these numbers. In fact, even double does not have enough precision for values in the Nonillion range - although this may not be so important here, because you obviously want to drop most of the information of this number anyhow.

Nevertheless, I have implemented it here using BigInteger. Converting this to use double should be straightforward, if you don't care about the precision issues.

The basic idea here is to create a NavigableMap from powers of 1000 to the respective number name. This map can be quickly looked up using floorEntry to find the best matching power (and thus, the number name).

import java.math.BigInteger;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.TreeMap;

public class NumberNames
{
    public static void main(String[] args)
    {
        test("100", "Nearly nothing");
        test("1000", "1 Thousand");
        test("1230", "1.23 Thousand");
        test("1000000", "1 Million");
        test("1435234", "1.43 Million");
        test("350000000", "350 Million");
        test("1000000000", "1 Billion");
        test("1765000000", "1.76 Billion");
        test("1000000000000", "1 Trillion");
        test("1345342345000", "1.34 Trillion");
        test("1000000000000000", "1 Quadrillion");
        test("100000000000000000", "100 Quadrillion");
        test("1230000000000000000000000000000000000000000000000000000000000000", "1.23 Vigintillion");
    }

    private static void test(String numberString, String string)
    {
        BigInteger number = new BigInteger(numberString);
        System.out.println(number+" is "+createString(number)+" should be "+string);
    }



    private static final String NAMES[] = new String[]{
        "Thousand",
        "Million",
        "Billion",
        "Trillion",
        "Quadrillion",
        "Quintillion",
        "Sextillion",
        "Septillion",
        "Octillion",
        "Nonillion",
        "Decillion",
        "Undecillion",
        "Duodecillion",
        "Tredecillion",
        "Quattuordecillion",
        "Quindecillion",
        "Sexdecillion",
        "Septendecillion",
        "Octodecillion",
        "Novemdecillion",
        "Vigintillion",
    };
    private static final BigInteger THOUSAND = BigInteger.valueOf(1000);
    private static final NavigableMap<BigInteger, String> MAP;
    static
    {
        MAP = new TreeMap<BigInteger, String>();
        for (int i=0; i<NAMES.length; i++)
        {
            MAP.put(THOUSAND.pow(i+1), NAMES[i]);
        }
    }

    public static String createString(BigInteger number)
    {
        Entry<BigInteger, String> entry = MAP.floorEntry(number);
        if (entry == null)
        {
            return "Nearly nothing";
        }
        BigInteger key = entry.getKey();
        BigInteger d = key.divide(THOUSAND);
        BigInteger m = number.divide(d);
        float f = m.floatValue() / 1000.0f;
        float rounded = ((int)(f * 100.0))/100.0f;
        if (rounded % 1 == 0)
        {
            return ((int)rounded) + " "+entry.getValue();
        }
        return rounded+" "+entry.getValue();
    }
}
Marco13
  • 53,703
  • 9
  • 80
  • 159
  • This surely isn't the *fastest*, but +1 for the sheer range anyway :) – Durandal Jun 26 '14 at 16:40
  • Sure, for the given set of names, a linear array search would probably be faster. But the O(logn) access of the `TreeMap` will pay out when you want to extend this to http://en.wikipedia.org/wiki/Graham%27s_number ;-) – Marco13 Jun 26 '14 at 16:42
  • 1
    You could get it down to O(1) by just taking the length of numberString for determining the magnitude, then taking just the relevant first 3 (maybe 4 for proper rounding) digits to get the number. *Although* the limit to 2^31-1 characters length for Strings may get in the way sometimes :) – Durandal Jun 26 '14 at 16:47
  • @Durandal That's a neat idea, in fact (ignoring the "theoretical" implications, like that you *have* to create a string representation, which is O(logn) in length, and the current method solely works on *numbers*). – Marco13 Jun 27 '14 at 08:47
3

I wouldn't use a float as it doesn't have much precision. Use double instead.

static final long MILLION = 1000000L;
static final long BILLION = 1000000000L;
static final long TRILLION = 1000000000000L;

public static String truncateNumber(double x) {
    return x < MILLION ?  String.valueOf(x) :
           x < BILLION ?  x / MILLION + "M" :
           x < TRILLION ? x / BILLION + "B" : 
                          x / TRILLION + "T";
}
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
1

Personally, I use this. If you need decimals in the input numbers, you can use BigDecimal instead.

BigInteger/BigDecimal is better than Float or Double or Long as it can keep even bigger values.

 public static String customFormat(String pattern, BigInteger value) {
        //To force the output to be equal if the language is set to english, spanish, norwegian or japanese.
        NumberFormat nf = NumberFormat.getNumberInstance(Locale.ENGLISH);
        DecimalFormat df = (DecimalFormat)nf;
        df.applyPattern(pattern);
        String output = df.format(value);
        return output;


    }
    public static String numberConverter(BigInteger input) {
        String points = customFormat("###,###,###,###,###,###,###,###,###.###", input);
        String[] letters = new String[]{"Kc","Mc","Gc","Tc","Pc","Ec","Zc","Yc","Bc"};//your value names. Is not limited to two letters. Can also be more based on your maximum amount
        int size = points.length();
        String after = points;
        if (size > 3) {
            int firstPoint = points.indexOf(".");

            String re = points;
            re = points.substring(0,3);
            System.out.println(re);
            int pVar = 7;
            if(re.contains(",")){
                String[] parts = re.split(",");
                if(parts[0].length() == 2){
                    pVar = 6;
                }else if(parts[0].length() == 1){
                    pVar = 5;
                }
            }

            after = points.substring(0, pVar);
            int x = (size - firstPoint - 4/*3*/)/5;
            String bafter = after + " " + letters[x];//adds the value designation to the letter.
            after = bafter;
        }

        return after;
    }

I realize this is not the most efficient piece of code due to its length, but it works flawlessly.

All values under 1000 show up as their full value. Upon reaching 1000 it shows as 1.000k. The code is designed to ensure 3 decimal spaces at all times. Changing pvar settings down by one in the if statement(which sets it down to two decimals at all times.) Removing the if statement will set a more dynamic number that changes by max chars.

Some technical info about the max size of the BigINteger. Extracted from this question:

"There is no theoretical limit. The BigInteger class allocates as much memory as it needs for all the bits of data it is asked to hold.

There are, however, some practical limits, dictated by the memory available. And there are further technical limits, although you're very unlikely to be affected: some methods assume that the bits are addressable by int indexes, so things will start to break when you go above Integer.MAX_VALUE bits."

So if you are creating for computers, make sure you have enough memory for those massive numbers.

Community
  • 1
  • 1
Zoe
  • 27,060
  • 21
  • 118
  • 148
1

Easy Kotlin version

    const val MILLION = 1000000L
    const val BILLION = 1000000000L
    const val TRILLION = 1000000000000L

    fun appendMillions(x: Long): String? {
        return when {
            x < MILLION -> x.toString()
            x < BILLION -> "${x.times(100).div(MILLION).times(0.01)}M"
            x < TRILLION -> "${x.times(100).div(BILLION).times(0.01)}B"
            else -> "${x.times(100).div(TRILLION).times(0.01)}T"
        }
    }
Akshay
  • 73
  • 8