What is the most efficient way to round a float value to the nearest integer in java?
Asked Answered
K

5

3

I've seen a lot of discussion on SO related to rounding float values, but no solid Q&A considering the efficiency aspect. So here it is:

What is the most efficient (but correct) way to round a float value to the nearest integer?

(int) (mFloat + 0.5);

or

Math.round(mFloat);

or

FloatMath.floor(mFloat + 0.5);

or something else?

Preferably I would like to use something available in standard java libraries, not some external library that I have to import.

Knowlton answered 23/8, 2012 at 11:56 Comment(6)
Do you have any evidence that this is a performance bottleneck in your code?Zaneta
Why do you care? If your code runs too slow, profile it. Most likely, rounding floats won't be a bottleneck.Marte
If you care to check the implementation of Math.round, you'll see the code from your first line, except for a single special case. This is why the performance is expected to be the same, especially if HotSpot inlines your round call.Linotype
Jon and Philipp: I am mainly just curious. But I use rounding a lot in my applications, so I'd like to use the most efficient way. I'm working in the mobile domain, so minimizing CPU usage is always some benefit.Knowlton
"CPU usage is always some benefit." The same is true of code running on a super-computer. The real issue is how big 'some' is, and whether chasing further improvement really pays off in the end.Alixaliza
True. But I can hardly see how seeking knowledge about the relative efficiency of different algorithms is a wasted effort. Maybe the answer is that the three options have equal complexity. But maybe not. Either way, it's useful information to have. I don't understand why some SO users often question the value of these kinds of questions.Knowlton
S
4
public class Main {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            measurementIteration();
        }
    }

    public static void measurementIteration() {
        long s, t1 = 0, t2 = 0;
        float mFloat = 3.3f;
        int f, n1 = 0, n2 = 0;
        for (int i = 0; i < 1E4; i++) {
            switch ((int) (Math.random() * 2)) {
            case 0:
                n1 += 1E4;
                s = System.currentTimeMillis();
                for (int k = 0; k < 1E4; k++)
                    f = (int) (mFloat + 0.5);
                t1 += System.currentTimeMillis() - s;
                break;
            case 1:
                n2 += 1E4;
                s = System.currentTimeMillis();
                for (int k = 0; k < 1E4; k++)
                    f = Math.round(mFloat);
                t2 += System.currentTimeMillis() - s;
                break;
            }
        }
        System.out.println(String.format("(int) (mFloat + 0.5): n1 = %d    -> %.3fms/1000", n1, t1 * 1000.0 / n1));
        System.out.println(String.format("Math.round(mFloat)  : n2 = %d    -> %.3fms/1000", n2, t2 * 1000.0 / n2));
    }
}

Output on Java SE6:

(int) (mFloat + 0.5): n1 = 500410000    -> 0.003ms/1000
Math.round(mFloat)  : n2 = 499590000    -> 0.022ms/1000

Output on Java SE7 (thanks to alex for the results):

(int) (mFloat + 0.5): n1 = 50120000 -> 0,002ms/1000
Math.round(mFloat) : n2 = 49880000 -> 0,002ms/1000

As you can see, there was a huge performance improvement on Math.round from SE6 to SE7. I think in SE7 there is no significant difference anymore and you should choose whatever seems more readable to you.

Shenashenan answered 23/8, 2012 at 12:13 Comment(9)
For good results you should always make an outer loop that calls the measuring function something like 10 times so when HotSpot replaces the interpreted code, it actually takes effect. This code mixes the interpreted performance with the compiled performance.Linotype
@MarkoTopolnik you mean like that?Shenashenan
Yes, that would be close to it. You could pull the printlns into the methods, that would make it unnecessary to use static variables. This is definitely the recommended approach for any microbenchmark and I routinely observe shrinking times after the first one or two runs.Linotype
@MarkoTopolnik Yes, you are right, the t1 measurement (for (int) (mFloat + 0.5)) reduces almost by a factor of 2 during the first 3 runs. Thanks for your suggestion.Shenashenan
Thanks, guys. I may do some benchmarking on my platform (Android) and see if I get the same results. BTW, sorry for the confusion, but FloatMath is part of Android, not standard Java. That was my oversight.Knowlton
What Java version do run? Second way (Math.round(mFloat);) takes about 100% more time in debug mode. But running in standard mode (initiated from Eclipse), the time difference drops below 0.1% with respect to way 1. Tests made in SE7 with 1E9 samples.Culdesac
@Culdesac I ran it from eclipse with SE-1.6. Another answer mentions a performance fix in the "latest" jre's for Math.round. Can you give me your output of my programm (last two lines would be enough). I will include them in the answer.Shenashenan
Your code ran in SE 1.7 gives the following results: (int) (mFloat + 0.5): n1 = 50120000 -> 0,002ms/1000 and Math.round(mFloat) : n2 = 49880000 -> 0,002ms/1000Culdesac
@Culdesac Thanks, added your results to the answer.Shenashenan
B
5

Based on the Q&A's that I think you are referring to, the relative efficiency of the various methods depends on the platform you are using.

But the bottom line is that:

  • the latest JREs have the performance fix for Math.floor / StrictMath.floor, and
  • unless you are doing an awful lot of rounding, it probably doesn't make any difference which way you do it.

References:

Bellona answered 23/8, 2012 at 12:2 Comment(0)
S
4
public class Main {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            measurementIteration();
        }
    }

    public static void measurementIteration() {
        long s, t1 = 0, t2 = 0;
        float mFloat = 3.3f;
        int f, n1 = 0, n2 = 0;
        for (int i = 0; i < 1E4; i++) {
            switch ((int) (Math.random() * 2)) {
            case 0:
                n1 += 1E4;
                s = System.currentTimeMillis();
                for (int k = 0; k < 1E4; k++)
                    f = (int) (mFloat + 0.5);
                t1 += System.currentTimeMillis() - s;
                break;
            case 1:
                n2 += 1E4;
                s = System.currentTimeMillis();
                for (int k = 0; k < 1E4; k++)
                    f = Math.round(mFloat);
                t2 += System.currentTimeMillis() - s;
                break;
            }
        }
        System.out.println(String.format("(int) (mFloat + 0.5): n1 = %d    -> %.3fms/1000", n1, t1 * 1000.0 / n1));
        System.out.println(String.format("Math.round(mFloat)  : n2 = %d    -> %.3fms/1000", n2, t2 * 1000.0 / n2));
    }
}

Output on Java SE6:

(int) (mFloat + 0.5): n1 = 500410000    -> 0.003ms/1000
Math.round(mFloat)  : n2 = 499590000    -> 0.022ms/1000

Output on Java SE7 (thanks to alex for the results):

(int) (mFloat + 0.5): n1 = 50120000 -> 0,002ms/1000
Math.round(mFloat) : n2 = 49880000 -> 0,002ms/1000

As you can see, there was a huge performance improvement on Math.round from SE6 to SE7. I think in SE7 there is no significant difference anymore and you should choose whatever seems more readable to you.

Shenashenan answered 23/8, 2012 at 12:13 Comment(9)
For good results you should always make an outer loop that calls the measuring function something like 10 times so when HotSpot replaces the interpreted code, it actually takes effect. This code mixes the interpreted performance with the compiled performance.Linotype
@MarkoTopolnik you mean like that?Shenashenan
Yes, that would be close to it. You could pull the printlns into the methods, that would make it unnecessary to use static variables. This is definitely the recommended approach for any microbenchmark and I routinely observe shrinking times after the first one or two runs.Linotype
@MarkoTopolnik Yes, you are right, the t1 measurement (for (int) (mFloat + 0.5)) reduces almost by a factor of 2 during the first 3 runs. Thanks for your suggestion.Shenashenan
Thanks, guys. I may do some benchmarking on my platform (Android) and see if I get the same results. BTW, sorry for the confusion, but FloatMath is part of Android, not standard Java. That was my oversight.Knowlton
What Java version do run? Second way (Math.round(mFloat);) takes about 100% more time in debug mode. But running in standard mode (initiated from Eclipse), the time difference drops below 0.1% with respect to way 1. Tests made in SE7 with 1E9 samples.Culdesac
@Culdesac I ran it from eclipse with SE-1.6. Another answer mentions a performance fix in the "latest" jre's for Math.round. Can you give me your output of my programm (last two lines would be enough). I will include them in the answer.Shenashenan
Your code ran in SE 1.7 gives the following results: (int) (mFloat + 0.5): n1 = 50120000 -> 0,002ms/1000 and Math.round(mFloat) : n2 = 49880000 -> 0,002ms/1000Culdesac
@Culdesac Thanks, added your results to the answer.Shenashenan
C
0

I should go for Math.round(mFloat) cause it's encapsuling rounding logic in a method (even if it's not your method).

According with its documentation the code you've written is the same that Math.round executes (except it checks border cases).

Anyway what is more important is the time-complexity of your algorithm, not the time for small constant-like things... Except you are programming something that will be invoked millions of times! :D

Edit: I don't know FloatMath. Is it from JDK?

Chukchi answered 23/8, 2012 at 12:2 Comment(1)
Yes, FloatMath is from android...sorry for that. I didn't look close enough to realize where it was coming from.Knowlton
M
0

You may benchmark it by using System.currentTimeMillis(). You will see that difference is too little

Megalo answered 23/8, 2012 at 12:9 Comment(18)
When you want to do benchmarking then you should use System.nanoTime() and not System.currentTimeMillis().Introduction
It's not absolutely necessary. If you measure times in excess of 10 ms, there's no particular need for nanoTime.Linotype
That's why I said "should" and not "must". Nevertheless it's good style to always use nanoTime for such cases.Introduction
The difference between the first two is actually quite remarkable (not tested it on Android though)...Shenashenan
@MarkoTopolnik I would say if you have more than 10 seconds you could use currentTimeMillis. You shouldn't be running benchmarks for 10 ms. ;)Musil
@PeterLawrey 10 seconds already gives you 5 significant digits, while for most of the benchmarks I'm quite happy with just two, especially when the number is an average---it's the ballpark that interests me. Microbenchmarks aren't useful for anything else, anyway.Linotype
@MarkoTopolnik at around 10 ms you only have one digit of accuracy with a -1.9 to +1.9 ms error ;)Musil
@PeterLawrey Perhaps I should've said just a bit more, like 20 ms. But that's really it, I'm quite happy to consider 20 and 20.95 ms as exactly the same performance.Linotype
@MarkoTopolnik With the JVM warming up, the second 20 ms can be 10x faster than the first 20 ms ;)Musil
@PeterLawrey What do you mean by that? I would of course ignore the warmup numbers.Linotype
@MarkoTopolnik Ok, so you use a 20 ms sample after some time which makes it more than 20 ms in total. ;)Musil
@PeterLawrey What I usually do is run the outer loop, each iteration printing the results. I ignore the first lines, where the warmup was taking place, and use the later ones. I don't make the program calculate the average automatically, I like to see the numbers for each iteration, see if there are any outliers, etc. Usually the lowest number is the most relevant, giving the potential performance. When I said "average", I meant that it timed for example 1e4 inner iterations so it averages over those.Linotype
@MarkoTopolnik So you if timed 1e4 iterations which took 20 ms each, it would take about 200 seconds in total. :) I think what you are really saying is you avoid timing every individual test to avoid the cost of taking the time from biasing the average?Musil
@PeterLawrey No, I take 10 measurements on an inner loop of 1e4 iterations, each of the 10 measurements taking about 20 ms. The total is 200 ms, but that's irrelevant because I never add up those times. I look at individual results.Linotype
@MarkoTopolnik ok, but the JVM won't compile the code unless its been called/iterated 10,000 times and there is a short delay between triggering a background compilation and seeing the code speed up. How do you cater for that?Musil
@PeterLawrey I see. So it's not enough that a method makes 1e4 iterations because that method itself won't be called the 1e4 times?Linotype
@MarkoTopolnik A test of 1e4 iterations is fine provided to ignore the first couple of times, which I imagine you do by taking the minimum ;) In HotSpot, a method which contains a loop which iterates 10,000 time is compiled (in a background thread). In JRockit, the method needs to be called again for it to use optimised code. Note: If you have multiple loops in a method, the first or second loop can trigger the whole method to be optimised, for better or worse.Musil
@PeterLawrey OK, then I'm doing it right already since I invoke the 1e4-iteration method 10 times from outside. This will work even on JRockit.Linotype
N
0

Simply adding 0.5 will give an incorrect result for negative numbers. See Faster implementation of Math.round? for a better solution.

Nilsson answered 4/2, 2015 at 19:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.