28

Does anybody know how I can round a double value to 3 significant figures like the examples on this website

http://www.purplemath.com/modules/rounding2.htm

Note: significant figures are not the same as decimal places

mh377
  • 1,656
  • 5
  • 22
  • 41

7 Answers7

79
double d = ...;
BigDecimal bd = new BigDecimal(d);
bd = bd.round(new MathContext(3));
double rounded = bd.doubleValue();
Sean Owen
  • 66,182
  • 23
  • 141
  • 173
  • 2
    No. Or else print the value of rounded % 0.001 and explain the result. – user207421 Sep 26 '11 at 20:38
  • 5
    The value of the BigDecimal prints as "0.00100", which is exactly correct. I suggest you try it. MathContext here is nothing to do with radixes, and BigDecimal is an arbitrary-precision library. – Sean Owen Sep 27 '11 at 07:26
  • 2
    Of course the BigDecimal does that, for the reason I stated in my answer. Your code doesn't work, because your 'rounded' variable isn't rounded. My question was what is the value of 'rounded % 0.001'. The OP asked how to round a double value. You haven't answered either question. Please now explain why 'rounded % 0.001' doesn't print zero if 'rounded' has indeed been rounded. – user207421 Sep 27 '11 at 08:23
  • 7
    You're making a different point, which I don't think is helpful to the OP. The best answer is not, "no there's nothing that can be done in Java"; it's the above. 'rounded' is the closest double representation to the correct answer. If double precision is a problem, the whole question doesn't make sense, indeed! And then the answer is use BigDecimal anyway. – Sean Owen Sep 27 '11 at 09:16
  • 1
    The point, and the best answer, is what I said: *leave it in the decimal radix.* Your answer remains wrong. – user207421 Sep 27 '11 at 10:06
  • 8
    I think you're reading the question as, "how can I represent a real exactly as a double, round it, and represent the answer exactly as a double?" You're right there: you can't. Surely, I think, the emphasis of the question was "how do I round to 3 significant figures in Java, since Math.round() rounds to nearest whole number?" I think it far more likely, and, the answer to that question is not "can't be done". – Sean Owen Sep 27 '11 at 11:15
  • I am answering the question as stated above by the OP. If the OP wanted values containing fractional digits beyond the supposed rounding point he didn't say so. If he had, it would be a contradication in terms, like your answer. – user207421 Sep 28 '11 at 02:51
  • With some slight modifications, you can put a long into the big decimal, and get long from it after rounding – IAmGroot May 31 '13 at 15:12
  • 2
    Addendum (for future readers): if you want to specify the number of *decimal places* instead of *precision*, or number of significant digits overall, then you must use something like `bd.setScale(places, BigDecimal.ROUND_HALF_UP)` instead. See [this answer](http://stackoverflow.com/a/2808648/56285) for more. – Jonik Jul 15 '13 at 22:56
  • 4
    Despite @EJP's attitude, his essential point is correct: it is **really important** to suggest to anyone reading this answer, that they might want to **stay in BigDecimal** - and not do that last line that converts to the nearest double value. – ToolmakerSteve Aug 28 '15 at 01:10
  • I second the call to remain in BigDecimal; moreover if you wish to emit a String representation at some point the BigDecimal as currently constructed can be improved. E.g. for d=0.99 and 3 significant digits it yields "0.99" instead of "0.990". See https://stackoverflow.com/a/58455258/274677 – Marcus Junius Brutus Oct 18 '19 at 17:09
5

If you want to do it by hand:

import java.lang.Math;

public class SigDig {

  public static void main(String[] args) {
    System.out.println("   -123.456   rounded up   to 2 sig figures is " + sigDigRounder(-123.456, 2,  1));
    System.out.println("     -0.03394 rounded down to 3 sig figures is " + sigDigRounder(-0.03394, 3, -1));
    System.out.println("    474       rounded up   to 2 sig figures is " + sigDigRounder(474, 2,  1));
    System.out.println("3004001       rounded down to 4 sig figures is " + sigDigRounder(3004001, 4, -1));
  }

  public static double sigDigRounder(double value, int nSigDig, int dir) {

    double intermediate = value/Math.pow(10,Math.floor(Math.log10(Math.abs(value)))-(nSigDig-1));

    if(dir > 0)      intermediate = Math.ceil(intermediate);
    else if (dir< 0) intermediate = Math.floor(intermediate);
    else             intermediate = Math.round(intermediate);

    double result = intermediate * Math.pow(10,Math.floor(Math.log10(Math.abs(value)))-(nSigDig-1));

    return(result);

  }
}

The above method rounds a double to a desired number of significant figures, handles negative numbers, and can be explicitly told to round up or down

awwsmm
  • 1,353
  • 1
  • 18
  • 28
3

There's nothing wrong with the answer given by Sean Owen (https://stackoverflow.com/a/7548871/274677). However depending on your use case you might want to arrive at a String representation. In that case, IMO it is best to convert while still in BigDecimal space using:

bd.toPlainString();

... if that's your use case then you might be frustrated that the code adapted from Owen's answer will produce the following:

d = 0.99, significantDigits = 3 ==> 0.99

... instead of the strictly more accurate 0.990.

If such things are important in your use case then I suggest the following adaptation to Owen's answer ; you can obviously also return the BigDecimal itself instead of calling toPlainString() — I just provide it this way for completeness.

public static String setSignificanDigits(double value, int significantDigits) {
    if (significantDigits < 0) throw new IllegalArgumentException();

    // this is more precise than simply doing "new BigDecimal(value);"
    BigDecimal bd = new BigDecimal(value, MathContext.DECIMAL64);
    bd = bd.round(new MathContext(significantDigits, RoundingMode.HALF_UP));
    final int precision = bd.precision();
    if (precision < significantDigits)
    bd = bd.setScale(bd.scale() + (significantDigits-precision));
    return bd.toPlainString();
}    
Marcus Junius Brutus
  • 26,087
  • 41
  • 189
  • 331
2
    public String toSignificantFiguresString(BigDecimal bd, int significantFigures ){
    String test = String.format("%."+significantFigures+"G", bd);
    if (test.contains("E+")){
        test = String.format(Locale.US, "%.0f", Double.valueOf(String.format("%."+significantFigures+"G", bd)));
    }
    return test;
}
heights1976
  • 480
  • 6
  • 16
0

double d=3.142568; System.out.printf("Answer : %.3f", d);

Gaurav Verma
  • 79
  • 1
  • 3
-3

how I can round a double value to 3 significant figures

You can't. Doubles are representing in binary. They do not have decimal places to be rounded to. The only way you can get a specific number of decimal places is to convert it to a decimal radix and leave it there. The moment you convert it back to double you have lost the decimal precision again.

For all the fans, here and elsewhere, of converting to other radixes and back, or multiplying and dividing by powers of ten, please display the resulting double value % 0.001 or whatever the required precision dictates, and explain the result.

EDIT: Specifically, the proponents of those techniques need to explain the 92% failure rate of the following code:

public class RoundingCounterExample
{
    static float roundOff(float x, int position)
    {
        float a = x;
        double temp = Math.pow(10.0, position);
        a *= temp;
        a = Math.round(a);
        return (a / (float)temp);
    }

    public static void main(String[] args)
    {
        float a = roundOff(0.0009434f,3);
        System.out.println("a="+a+" (a % .0001)="+(a % 0.001));
        int count = 0, errors = 0;
        for (double x = 0.0; x < 1; x += 0.0001)
        {
            count++;
            double d = x;
            int scale = 2;
            double factor = Math.pow(10, scale);
            d = Math.round(d * factor) / factor;
            if ((d % 0.01) != 0.0)
            {
                System.out.println(d + " " + (d % 0.01));
                errors++;
            }
        }
        System.out.println(count + " trials " + errors + " errors");
    }
}
user207421
  • 305,947
  • 44
  • 307
  • 483
  • 2
    Well, no. You're conflating several things -- whether you can round to arbitrary places in Java (yes), represent arbitrary precision reals (yes), and represent arbitrary precision reals as double (no). I can most certainly round a double; whether or not there's something lost in translation is another question. I *think* the OP really wanted to know how to do rounding of reals (though it was said as doubles), and that is definitely possible in Java. See my answer. – Sean Owen Sep 27 '11 at 07:28
  • @Sean Owen you can round to arbitrary places in any language as long as the radix of the places is the same as the radix of the number. I am not conflating anything about arbitrary precision reals, as I didn't mention them. 'What is lost in translation' is the rounding. You cannot round a double to a specified number of decimal places. It is a contradiction in terms, as the simple test I suggested for your answer clearly shows. – user207421 Sep 27 '11 at 08:25
  • 4
    According to your logic, you can't add doubles either, because the sum isn't exactly right. In an abstract sense that's right, but, I would love to see you try this argument on in a software company. "Boss, I can't implement this. There is no way to add floating-point numbers on a computer!" – Sean Owen Sep 27 '11 at 09:20
  • @Sean Owen: Either you are right and `rounded % 0.001` is zero, or I am right and it isn't. Period. Which is it? All this other stuff is just blather. – user207421 Sep 27 '11 at 10:15
  • 2
    It isn't zero, yes. I understand your point and explained why it does not seem helpful here. I don't think you addressed mine. SO seems to think my answer is better, but good that you brought more thinking. – Sean Owen Sep 27 '11 at 10:53
  • @Sean Owen It is wrong answers that aren't helpful. I provided the correct answer above: 'The only way ...'. Your digression about adding doubles is of no relevance or interest. – user207421 Sep 28 '11 at 02:51
  • 3
    Well, I think what OP (and I) probably really wants is to get a String representation of a double to x significant figures, so in a way you're both wrong – Luigi Plinge Jul 02 '13 at 00:00
  • @LuigiPinge Certainly that's what he should really do. He needs to change his return type to String, or perhaps BigDecimal. I haven't said otherwise. It's difficult to see how that makes my answer incorrect. – user207421 Jan 26 '14 at 21:01
  • @EJP While the question is not 100% precise, the OP is expressing a common desire. A `double` value can be rounded *within a certain accuracy*, depending on the machine representation. Equality comparisons then need to be done via a method that takes a tolerance, where the tolerance is stated as a permitted fractional error of the first value, except in range(-1, +1) where it is a fraction of 1.It can be done. But high odds OP will get himself in trouble. Your answer has precautionary value: once someone starts asking for exactness, it is useful to point out the trouble they are heading for. – ToolmakerSteve Aug 28 '15 at 00:48
  • @ToolmakerSteve The trouble is that you can't attach any actual meaning to rounding to a specific number of digits 'within a certain accuracy'. It is a contradiction in terms. – user207421 Aug 10 '16 at 21:18
  • Boss, there is no way to multiply the speed of light with the universal gravitational constant in mathematics! Speed of light and gravitational constant cannot be defined mathematically! – SOFe Oct 12 '18 at 03:34
-12

I usually don't round the number itself but round the String representation of the number when I need to display it because usually it's the display that matters, that needs the rounding (although this may not be true in situations, and perhaps yours, but you need to elaborate on this if so). This way, my number retains its accuracy, but it's display is simplified and easier to read. To do this, one can use a DecimalFormat object, say initialzed with a "0.000" String (new DecimalFormat("0.000")), or use String.format("%.3f", myDouble), or several other ways.

For example:

// yeah, I know this is just Math.PI.
double myDouble = 3.141592653589793;
DecimalFormat myFormat = new DecimalFormat("0.000");
String myDoubleString = myFormat.format(myDouble);
System.out.println("My number is: " + myDoubleString);

// or you can use printf which works like String.format:
System.out.printf("My number is: %.3f%n", myDouble);
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373