4

In porting an algorithm from JavaScript to Java, I've run into the problem that I need a replacement for JavaScript's toPrecision(). The problem is that I don't have a clue how small or large the numbers will be, so I can't use a simple NumberFormat with the right format.

Is there a standard class that offers a similar functionality?

EDIT Here is what I came up with:

   double toPrecision(double n, double p) {
        if (n==0) return 0;

        double e = Math.floor(Math.log10(Math.abs(n)));
        double f = Math.exp((e-p+1)*Math.log(10));

        return Math.round(n/f)*f;
    }

In principle, it does the right thing, but rounding errors completely ruin it. For example, toPrecision(12.34567, 3) returns 12.299999999999997

EDIT 2 This version works perfectly for 11 out of 12 test cases...

   double toPrecision(double n, double p) {
        if (n==0) return 0;

        double e = Math.floor(Math.log10(Math.abs(n)));
        double f = Math.round(Math.exp((Math.abs(e-p+1))*Math.log(10)));
        if (e-p+1<0) {
            f = 1/f;
        }

        return Math.round(n/f)*f;
    }

But toPrecision(0.00001234567, 3) still returns 1.2299999999999999E-5 instead of 1.23E-5

Erich Kitzmueller
  • 36,381
  • 5
  • 80
  • 102

6 Answers6

12

Use BigDecimal and setScale() method to set the precision

BigDecimal bd = new BigDecimal("1.23456789");
System.out.println(bd.setScale(3,BigDecimal.ROUND_HALF_UP));

Output

1.235

See

jmj
  • 237,923
  • 42
  • 401
  • 438
  • Finding the right scale is something that is missing in your solution, but the use of BigDecimal seems like a good idea. – Erich Kitzmueller Aug 21 '12 at 12:15
  • 2
    This doesn't show three significant digits e.g. "12345678.9 and should return 12300000" – Peter Lawrey Aug 21 '12 at 12:17
  • @ErichKitzmueller This is wrong solution. Try this : input 0.0000001 toPrecision(3) your code gives answer 0.000 whereas Javascript gives 1.00e-7 i.e 0.0000001 – null Oct 18 '17 at 20:13
3

The simplest solution I came up with for this uses a combination of java.math.BigDecimal and java.math.MathContext like so.

String toPrecision(double number, int precision) {
    return new BigDecimal(number, new MathContext(precision)).toString();
}

I'm using this in the dynjs implementation of Number.prototype.toPrecision.

lanceball
  • 595
  • 4
  • 9
  • To let it exactly mimic the javascript behavior (especially for scientific notation), we have to convert the result to lower case. – Daniel Beer Feb 13 '17 at 14:41
  • This actually works with the input of `404279.164` and `4` == `404300.0` which is what I was after. `inline fun Double.toPrecision(s : Int) = BigDecimal(this, MathContext(s)).toDouble()` in Kotlin – Simon Featherstone Mar 19 '18 at 18:40
2

Here's a java solution using String.format.

public static String toPrecision(double d, int digits) {
    s = String.format("%."+((digits>0)?digits:16)+"g",d).replace("e+0","e+").replace("e-0","e-");
    return s;
}

The .replace is only needed if you want to mimic javascript where it has no leading zero on exponents. If you are just using it for a rounding then return the value as

return Double.parseDouble(s);

Here is some unit test code:

public void testToPrecision() {
    String s = NumberFormat.toPrecision(1234567.0,5);
    assertEquals("1.2346e+6",s);
    s = NumberFormat.toPrecision(12.34567,5);
    assertEquals("12.346",s);
    s = NumberFormat.toPrecision(0.1234567,5);
    assertEquals("0.12346",s);
    s = NumberFormat.toPrecision(0.1234567e20,5);
    assertEquals("1.2346e+19",s);
    s = NumberFormat.toPrecision(-0.1234567e-8,5);
    assertEquals("-1.2346e-9",s);
    s = NumberFormat.toPrecision(1.0/3.0,5);
    assertEquals("0.33333",s);
    s = NumberFormat.toPrecision(1.0/3.0,0);
    assertEquals("0.3333333333333333",s);
}
Glenn
  • 1,996
  • 2
  • 24
  • 32
1

You can use double with

double d = 1.23456789;
System.out.println(Math.round(d * 1e3) / 1e3);

prints

1.235

or

System.out.printf("%.3f%n", d);

does the same.


public static void main(String... args) {
    System.out.println(round3significant(12345678.9));
    System.out.println(round3significant(0.0000012345));
}

public static double round3significant(double d) {
    if (d < 100) {
        double divide = 1;
        while(d < 100) {
            d *= 10;
            divide *= 10;
        }
        return Math.round(d) / divide;
    } else {
        double multi = 1;
        while(d > 1000) {
            d /= 10;
            multi *= 10;
        }
        return Math.round(d) * multi;
    }
}

prints

1.23E7
1.23E-6

You can use NumberFormat to only display as a decimal.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • Doesn't meet my requirements. I don't know the scale of the numbers in advance, so in one case, the input might be 12345678.9 and should return 12300000; in another case, 0.00000012345 should return 000000123. There are also rounding problems, see my edited post. – Erich Kitzmueller Aug 21 '12 at 12:08
  • @ammoQ The code I gave doesn't have rounding issues and gives the same answer as Jigar's. Will revise to handle N significant digits. – Peter Lawrey Aug 21 '12 at 12:10
1

This finally works...

double toPrecision(double n, double p) {
    if (n==0) return 0;

    double e = Math.floor(Math.log10(Math.abs(n)));
    double f = Math.round(Math.exp((Math.abs(e-p+1))*Math.log(10)));

    if (e-p+1<0) {
        return Math.round(n*f)/f;
    }

    return Math.round(n/f)*f;
}
Erich Kitzmueller
  • 36,381
  • 5
  • 80
  • 102
0
    import java.text.*;  
Class Decimals
{  
    public static void main(String[] args)
    {  
        float f = 125.069f;  
        DecimalFormat form = new DecimalFormat("#.##");  
        System.out.println(form.format(f));  
    }
}

.## represents upto what decimal places you want I hope this suits your requirement.

icr
  • 211
  • 1
  • 2
  • 10