7

I need to round integers to be the nearest multiple of another integer. Examples for results in the case of multiples of 100:

  • 36->0
  • 99->100
  • 123->100
  • 164->200

and so on.

I came up with the following code, that works, but feels "dirty":

int RoundToMultiple(int toRound, int multiple)
{
    return (toRound + (multiple / 2)) / multiple * multiple;
}

This counts on the truncating properties of integer division to make it work. Can I count on this code to be portable? Are there any compiler setups where this will fail to give me the desired result? If there are, how can I achieve the same results in a portable way?

If needed for a better answer, it can be assumed that multiples will be powers of 10 (including multiples of 1). Numbers can also be assumed to all be positive.

Eyal K.
  • 1,042
  • 8
  • 23

3 Answers3

4

Yes, you can count on this code to be portable. N4296 (which is the latest open draft of C++14) says in section 5.6 [expr.mul]:

For integral operands the / operator yields the algebraic quotient with any fractional part discarded. [Footnote: This is often called truncation towards zero]

This is not a new feature of the latest C++, it could be relied on in C89 too.

The only caveat, is that if toRound is negative, you need to subtract the offset.

An alternative approach is:

int RoundToMultiple(int toRound, int multiple)
{
    const auto ratio = static_cast<double>(toRound) / multiple;
    const auto iratio = std::lround(ratio);
    return iratio * multiple;
}

This avoid messy +/- offsets, but performance will be worse, and there are problems if toRound is so large that it can't be held precisely in a double. (OTOH, if this is for output, then I suspect multiple will be similarly large in this case, so you will be alright.)

  • [`int` numbers cannot exceed the precision enabled by `double`](https://stackoverflow.com/questions/3793838/which-is-the-first-integer-that-an-ieee-754-float-is-incapable-of-representing-e), so you probably don't need that warning about `toRound` getting too large. – Xirema May 22 '17 at 15:55
  • @Xirema sorry but where in that link you posted does it say that int precision can't exceed double precision? – Klitos Kyriacou May 22 '17 at 15:59
  • @KlitosKyriacou [The first Answer on that post](https://stackoverflow.com/a/3793950/5241642). The number given for `double` is way larger than the maximum range of an `int`. – Xirema May 22 '17 at 16:00
  • @Xirema on some architectures, int can be 64 bits, which is larger than the IEE 754 mantissa would allow. – Klitos Kyriacou May 22 '17 at 16:03
  • @KlitosKyriacou On which architectures? – Xirema May 22 '17 at 16:03
  • 1
    @Xirema current popular compilers on Windows and Linux use 32-bit int, but they are not obliged to. Compilers on Cray supercomputers use 64 bits for everything (even char). The point is that the standard allows int to be any size (at least 16 bits) and there's no guarantee that your program won't be compiled on a future Linux or Windows compiler that uses 64-bit int. – Klitos Kyriacou May 22 '17 at 16:17
  • @Xirema As well as there being no guarantee that double can hold all int values exactly, the OP's question may well be a simplification. What if the real variables are of type `long long`? – Martin Bonner supports Monica May 22 '17 at 16:37
  • Isn't a cast to double just for the sake of rounding a little bit to expensive (compared to integral operators like `/` or `%` (e.g. as in my answer)? – Stephan Lechner May 22 '17 at 18:08
  • @stephanlechner The performance will be much worse, on the other hand I claim three code is clearer. Which is most important will vary. – Martin Bonner supports Monica May 22 '17 at 18:11
  • Btw you probably want to cast `std::lround(ratio)` to `int`, rather than relying on implicit casting on your `return` statement which will probably issue a warning. – Klitos Kyriacou May 22 '17 at 22:22
2

The C++ standard explicitly specifies the behavior of integer division thusly:

[expr.mul]

For integral operands the / operator yields the algebraic quotient with any fractional part discarded.

A.k.a. truncation towards zero. This is as portable as it gets.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
0

Though - as mentioned by others - the integral division behaves as you expect, may be the following solution looks "less wired" (still opinion based).

Concerning a solution that converts an int to a double: I personally feel that this is to expensive just for the sake of rounding, but maybe someone can convince me that my feeling is wrong;

Anyway, by using just integral operators, the following solution makes the discussion on whether a double's mantissa can always hold every int superfluous:

int RoundToMultiple(int toRound, int multiple) {
    toRound += multiple / 2;
    return toRound - (toRound%multiple);
}

If you also wanted to include negative values, the code could be slightly adapted as follows (including tests):

#include <stdio.h>

int RoundToMultiple(int toRound, int multiple) {
    toRound += toRound < 0 ? -multiple / 2 : multiple / 2;
    return toRound - (toRound%multiple);
}

int main(int argc, char const *argv[])
{
    int tests[] = { 36,99,123,164,-36,-99,-123,-164,0 };
    int expectedResults[] = { 0,100,100,200,0,-100,-100,-200,0 };

    int i=0;
    int test=0, result=0, expectedResult=0;
    do {
        test = tests[i];
        result = RoundToMultiple(test, 100);
        expectedResult = expectedResults[i];
        printf("test %d: %d==%d ? %s\n", test, result, expectedResult, (expectedResult==result ? "OK" : "NOK!"));
        i++;
    }
    while(test != 0);
}
Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58