

package uk.co.wingpath.util;

/**
* This class provides methods for formatting numbers using the metric/SI
* prefixes. For example, 12345 would be formatted as 12.345k.
*/

public final class Metric
{
    private static final String [] bigPrefixes =
        new String [] { "k", "M", "G", "T", "P", "E" };
//      new String [] { "k", "M", "G", "T", "P", "E", "Z", "Y" };
    private static final String [] smallPrefixes =
        new String [] { "m", "µ", "n", "p", "f", "a" };
//      new String [] { "m", "µ", "n", "p", "f", "a", "z", "y" };

    /**
    * Formats a number using the appropriate metric prefix and the specified
    * number of digits. If the number is outside the range of the metric
    * prefixes, it is formatted using scientific notation.
    * @param n the number to be formatted.
    * @param ndigits the number of significant digits.
    * @return the formatted number.
    */
    static public String format (double n, int ndigits)
    {
        if (ndigits < 1)
        {
            throw new IllegalArgumentException (
                "ndigits must be > 0 : " + ndigits);
        }

        // Handle minus sign.

        String sign = "";
        if (n < 0)
        {
            sign += "-";
            n = -n;
        }

        // Format the number using scientific notation (it's an easy way of
        // getting it rounded to the required number of significant digits).
        String fmt = "%." + (ndigits - 1) + "e";
        String str = String.format (fmt, n);

        // Extract the digits and exponent, checking that the format is what
        // we expect (and returning the scientific notation if it isn't).
        // NaNs and Infs will not produce the expected format, but there may
        // be other cases - so it's easier/safer to test for the expected
        // format than to test for the exceptions.
        String digits;
        String expstr;
        if (ndigits == 1)
        {
            // Only one digit, so no decimal point.
            if (str.length () != 5)
                return str;
            expstr = str.substring (1);
            digits = str.substring (0, 1);
        }
        else
        {
            if (str.length () < ndigits + 5)
                return str;
            expstr = str.substring (ndigits + 1);
            // Strip the decimal point.
            digits = str.substring (0, 1) + str.substring (2, ndigits + 1);
        }
        assert digits.length () == ndigits : digits;
        if (expstr.charAt (0) != 'e' ||
            (expstr.charAt (1) != '+' && expstr.charAt (1) != '-'))
        {
            return str;
        }
        int exp = Integer.parseInt (expstr.substring (2));
        if (expstr.charAt (1) == '-')
            exp = -exp;

        // Find what metric prefix to use.
        String prefix = "";
        if (exp < 0)
        {
            int i = 0;

            while (exp < 0)
            {
                if (i >= smallPrefixes.length)
                    return str;
                prefix = smallPrefixes [i];
                exp += 3;
                ++i;
            }
        }
        else
        {
            int i = 0;

            while (exp > 2)
            {
                if (i >= bigPrefixes.length)
                    return str;
                prefix = bigPrefixes [i];
                exp -= 3;
                ++i;
            }
        }

        // Add trailing zeroes if necessary.
        while (ndigits < exp + 1)
        {
            ++ndigits;
            digits += '0';
        }

        // Insert a decimal point if necessary.
        if (exp + 1 < ndigits)
        {
            digits = digits.substring (0, exp + 1) + '.' +
                digits.substring (exp + 1);
        }

        // Put it all together.
        return sign + digits + prefix;
    }

    /**
    * Formats a time period measured in nanoseconds using the appropriate
    * metric prefix and the specified number of significant digits.
    * @param ndigits the number of significant digits.
    * @param period time period in nanoseconds.
    * @return the formatted time period.
    */
    public static String formatNanoTime (long period, int ndigits)
    {
        return format (period / 1.0e9, ndigits) + 's';
    }

    /**
    * Formats a time period measured in nanoseconds using the appropriate
    * metric prefix and 3 significant digits.
    * @param period time period in nanoseconds.
    * @return the formatted time period.
    */
    public static String formatNanoTime (long period)
    {
        return format (period / 1.0e9, 3) + 's';
    }

/*
    // TEST CODE

    public static void test (double n, int ndigits, String result)
    {
        System.out.print (String.format ("%-12g", n) + " " + ndigits + ": ");
        String str = format (n, ndigits);
        System.out.print (String.format ("%-10s", str));
        if (str.equals (result))
        {
            System.out.println (" OK");
        }
        else
        {
            System.out.println (" FAIL: expected " + result);
        }
    }

    public static void main (String [] args)
    {
        if (args.length == 2)
        {
            double n = Double.parseDouble (args [0]);
            int ndigits = Integer.parseInt (args [1]);
            String str = format (n, ndigits);
            System.out.println (str);
            System.exit (0);
        }

        test (1.2345, 4, "1.235");
        test (12.345, 4, "12.35");
        test (123.45, 4, "123.5");
        test (1234.5, 4, "1.235k");
        test (12345,  4, "12.35k");
        test (123450, 4, "123.5k");
        test (1234500, 4, "1.235M");
        test (0.12345, 4, "123.5m");
        test (0.012345, 4, "12.35m");
        test (0.0012345, 4, "1.235m");
        test (0.00012345, 4, "123.5µ");
        test (0.000012345, 4, "12.35µ");

        test (1.2345, 3, "1.23");
        test (12.345, 3, "12.3");
        test (123.45, 3, "123");
        test (1234.5, 3, "1.23k");
        test (12345,  3, "12.3k");
        test (123450, 3, "123k");
        test (1234500, 3, "1.23M");
        test (0.12345, 3, "123m");
        test (0.012345, 3, "12.3m");
        test (0.0012345, 3, "1.23m");
        test (0.00012345, 3, "123µ");
        test (0.000012345, 3, "12.3µ");

        test (1.2345, 2, "1.2");
        test (12.345, 2, "12");
        test (123.45, 2, "120");
        test (1234.5, 2, "1.2k");
        test (12345,  2, "12k");
        test (123450, 2, "120k");
        test (1234500, 2, "1.2M");
        test (0.12345, 2, "120m");
        test (0.012345, 2, "12m");
        test (0.0012345, 2, "1.2m");
        test (0.00012345, 2, "120µ");
        test (0.000012345, 2, "12µ");

        test (1.2345, 1, "1");
        test (12.345, 1, "10");
        test (123.45, 1, "100");
        test (1234.5, 1, "1k");
        test (12345,  1, "10k");
        test (123450, 1, "100k");
        test (1234500, 1, "1M");
        test (0.12345, 1, "100m");
        test (0.012345, 1, "10m");
        test (0.0012345, 1, "1m");
        test (0.00012345, 1, "100µ");
        test (0.000012345, 1, "10µ");

        test (1.234e-19, 4, "1.234e-19");
        test (1.234e-18, 4, "1.234a");
        test (1.234e-17, 4, "12.34a");
        test (1.234e-16, 4, "123.4a");
        test (1.234e-15, 4, "1.234f");
        test (1.234e-14, 4, "12.34f");
        test (1.234e-13, 4, "123.4f");
        test (1.234e-12, 4, "1.234p");
        test (1.234e-11, 4, "12.34p");
        test (1.234e-10, 4, "123.4p");
        test (1.234e-9, 4, "1.234n");
        test (1.234e-8, 4, "12.34n");
        test (1.234e-6, 4, "1.234µ");
        test (1.234e-5, 4, "12.34µ");
        test (1.234e-4, 4, "123.4µ");
        test (1.234e-3, 4, "1.234m");
        test (1.234e-2, 4, "12.34m");
        test (1.234e-1, 4, "123.4m");
        test (1.234e0, 4, "1.234");
        test (1.234e1, 4, "12.34");
        test (1.234e2, 4, "123.4");
        test (1.234e3, 4, "1.234k");
        test (1.234e4, 4, "12.34k");
        test (1.234e5, 4, "123.4k");
        test (1.234e6, 4, "1.234M");
        test (1.234e7, 4, "12.34M");
        test (1.234e8, 4, "123.4M");
        test (1.234e9, 4, "1.234G");
        test (1.234e10, 4, "12.34G");
        test (1.234e11, 4, "123.4G");
        test (1.234e12, 4, "1.234T");
        test (1.234e13, 4, "12.34T");
        test (1.234e14, 4, "123.4T");
        test (1.234e15, 4, "1.234P");
        test (1.234e16, 4, "12.34P");
        test (1.234e17, 4, "123.4P");
        test (1.234e18, 4, "1.234E");
        test (1.234e19, 4, "12.34E");
        test (1.234e20, 4, "123.4E");
        test (1.234e21, 4, "1.234e+21");
    }
*/
}

