4

I want a function

int rounded_division(const int a, const int b) { 
    return round(1.0 * a/b); 
}

So we have, for example,

rounded_division(3, 2) // = 2
rounded_division(2, 2) // = 1
rounded_division(1, 2) // = 1
rounded_division(0, 2) // = 0
rounded_division(-1, 2) // = -1
rounded_division(-2, 2) // = -1
rounded_division(-3, -2) // = 2

Or in code, where a and b are 32 bit signed integers:

int rounded_division(const int a, const int b) {
    return ((a < 0) ^ (b < 0)) ? ((a - b / 2) / b) : ((a + b / 2) / b);
}

And here comes the tricky part: How to implement this guy efficiently (not using larger 64 bit values) and without a logical operators such as ?:, &&, ...? Is it possible at all?

The reason why I am wondering of avoiding logical operators, because the processor I have to implement this function for, has no conditional instructions (more about missing conditional instructions on ARM.).

Community
  • 1
  • 1
Michael Dorner
  • 17,587
  • 13
  • 87
  • 117
  • 1
    I doubt it is possible to read the question as fast as you down-voted. Why so? – Michael Dorner Jan 20 '16 at 17:22
  • Why do you need to do it this way? Are the logical operators on your CPU broken? Or is this just an intellectual puzzle? – Steve Summit Jan 20 '16 at 17:22
  • Why not just cast one of them to `float`, perform the division to get a real number as a result, then call `ceil`? – user4520 Jan 20 '16 at 17:24
  • Because on my embedded system I have to implement this function, there are no conditional instructions. And of course, due to performance, it would be nice not to go larger than 32 bits. – Michael Dorner Jan 20 '16 at 17:24
  • 3
    is it even possible for a cpu to not have conditional instructions? – gmoshkin Jan 20 '16 at 17:30
  • There are conditional instructions, but they are expensive and destroy the pipeline, because conditionals are jumps. – Michael Dorner Jan 20 '16 at 17:31
  • 1
    What is the range of `a` and `b`? `rounded_division(a, 0)` and `rounded_division(INT_MAX, -1)` are problems. It looks like we can assume `b > 0`. – chux - Reinstate Monica Jan 20 '16 at 17:34
  • The ranges are any signed integer with 32 bit. – Michael Dorner Jan 20 '16 at 17:35
  • 3
    `floor(a/b + 0.5)` and `rounded_division(-1, 2) // = -1` are contradictory. as `floor(-1.0/2.0 + 0.5) --> 0` Which one is correct? Do you want floor or truncate? – chux - Reinstate Monica Jan 20 '16 at 17:37
  • Does the code need to work on pre-C99 compiler. Integer division has implementation dependent issues when `a,b` are negative. – chux - Reinstate Monica Jan 20 '16 at 17:41
  • @chux: Thanks for pointing this out. The examples show what I want. I corrected the pseudo code. No pre-C99. – Michael Dorner Jan 20 '16 at 17:43
  • @MichaelDorner Are you sure that there's not a single predicated instruction in the processor? Not even [conditional-move](http://stackoverflow.com/a/14131292/2726892)? – MooseBoys Jan 20 '16 at 18:00
  • @MooseBoys unfortunately yes: http://stackoverflow.com/q/22168992/1864294 – Michael Dorner Jan 20 '16 at 18:29
  • @MichaelDorner If you're really on ARMv8, the instruction set *does* include a limited set of conditional "[data processing instructions](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/CHDEEABE.html)", including `CSEL` (conditional-move). – MooseBoys Jan 20 '16 at 18:41
  • 2
    Suggest the reference function you want is `int rounded_division(int a, int b) { return round(1.0*a/b); }`. ` – chux - Reinstate Monica Jan 20 '16 at 18:50
  • Honestly, I think the "problem" with missing conditional instructions is not as bad as you think. AFAIK branch instructions are not that much heavier than C.I. that it would be worth writing messy code :) Better spare a stack frame and wrap your rounding functionality into a macro instead of a function – lima.sierra Jan 20 '16 at 19:00
  • A far amount of work appears to be needed to get the correct functionality when either `a` or `b` are near `INT_MIN, INT_MAX`. If requirements were relaxed such that `a +/- b/2` could be assumed to not overflow, then speedier code is possible. – chux - Reinstate Monica Jan 20 '16 at 21:46
  • 2
    Note: `((a < 0) ^ (b < 0)) ? ((a - b / 2) / b) : ((a + b / 2) / b);` fails to match `round(1.0 * a/b);` for many large values of `a` and _dramatically_ (wrong sign) for all sorts of values of `b`. – chux - Reinstate Monica Jan 20 '16 at 22:06

8 Answers8

9

a/b + a%b/(b/2 + b%2) works quite well - not failed in billion+ test cases. It meets all OP's goals: No overflow, no long long, no branching, works over entire range of int when a/b is defined.

No 32-bit dependency. If using C99 or later, no implementation behavior restrictions.

int rounded_division(int a, int b) {
  int q = a / b;
  int r = a % b;
  return q + r/(b/2 + b%2);
}

This works with 2's complement, 1s' complement and sign-magnitude as all operations are math ones.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • I mentioned the signed integer `a` and `b`. This is the reason why this problem is not trivial. – Michael Dorner Jan 20 '16 at 17:29
  • Yes, this was meant to be an example. :) But they are both signed integers. – Michael Dorner Jan 20 '16 at 17:34
  • Hey! I know this is a very old post, but I'd like to know how you got that formula and is it perfect? Proof would be pretty cool too. Sorry if I'm bothering you. – NemPlayer Sep 21 '18 at 16:43
  • 1
    @NemPlayer "how you got that formula" --> self derived. Consider `b%2 == 0` that is `a/b + 2*r/b` which rounds as desired. Try yourself walking through remaining `b%2 == 1` and `b%2 == -1` cases. "is it perfect?" --> tested good with billions of test cases 2 yrs ago. I also explored corners and not just random and found no fault. – chux - Reinstate Monica Sep 21 '18 at 20:48
3

How about this:

int rounded_division(const int a, const int b) {
    return (a + b/2 + b * ((a^b) >> 31))/b;
}

(a ^ b) >> 31 should evaluate to -1 if a and b have different signs and 0 otherwise, assuming int has 32 bits and the leftmost is the sign bit.

EDIT

As pointed out by @chux in his comments this method is wrong due to integer division. This new version evaluates the same as OP's example, but contains a bit more operations.

int rounded_division(const int a, const int b) {
    return (a + b * (1 + 2 * ((a^b) >> 31)) / 2)/b;
}

This version still however does not take into account the overflow problem.

gmoshkin
  • 1,106
  • 8
  • 22
1

What about

  ...
  return ((a + (a*b)/abs(a*b) * b / 2) / b);
}

Without overflow:

  ...
  return ((a + ((a/abs(a))*(b/abs(b))) * b / 2) / b);    
}
alk
  • 69,737
  • 10
  • 105
  • 255
1

This is a rough approach that you may use. Using a mask to apply something if the operation a*b < 0.

Please note that I did not test this appropriately.

int function(int a, int b){
    int tmp = float(a)/b + 0.5;
    int mask = (a*b) >> 31; // shift sign bit to set rest of the bits

    return tmp - (1 & mask);//minus one if a*b was < 0
}
MSR
  • 11
  • 3
1

The following rounded_division_test1() meets OP's requirement of no branching - if one counts sign(int a), nabs(int a), and cmp_le(int a, int b) as non-branching. See here for ideas of how to do sign() without compare operators. These helper functions could be rolled into rounded_division_test1() without explicit calls.

The code demonstrates the correct functionality and is useful for testing various answers. When a/b is defined, this answer does not overflow.

#include <limits.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

int nabs(int a) {
  return (a < 0) * a - (a >= 0) * a;
}

int sign(int a) {
  return (a > 0) - (a < 0);
}

int cmp_le(int a, int b) {
  return (a <= b);
}

int rounded_division_test1(int a, int b) {
  int q = a / b;
  int r = a % b;
  int flag = cmp_le(nabs(r), (nabs(b) / 2 + nabs(b % 2)));
  return q + flag * sign(b) * sign(r);
}

// Alternative that uses long long
int rounded_division_test1LL(int a, int b) {
  int c = (a^b)>>31;
  return (a + (c*2 + 1)*1LL*b/2)/b;
}

// Reference code
int rounded_division(int a, int b) {
  return round(1.0*a/b);
}

int test(int a, int b) {
  int q0 = rounded_division(a, b);
  //int q1 = function(a,b);
  int q1 = rounded_division_test1(a, b);
  if (q0 != q1) {
    printf("%d %d --> %d %d\n", a, b, q0, q1);
    fflush(stdout);
  }
  return q0 != q1;
}

void tests(void) {
  int err = 0;
  int const a[] = { INT_MIN, INT_MIN + 1, INT_MIN + 1, -3, -2, -1, 0, 1, 2, 3,
      INT_MAX - 1, INT_MAX };
  for (unsigned i = 0; i < sizeof a / sizeof a[0]; i++) {
    for (unsigned j = 0; j < sizeof a / sizeof a[0]; j++) {
      if (a[j] == 0) continue;
      if (a[i] == INT_MIN && a[j] == -1) continue;
      err += test(a[i], a[j]);
    }
  }
  printf("Err %d\n", err);
}

int main(void) {
  tests();
  return 0;
}
Community
  • 1
  • 1
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
1

Let me give my contribution:

What about:

int rounded_division(const int a, const int b) {
    return a/b + (2*(a%b))/b;
}

No branch, no logical operators, only mathematical operators. But it could fail if b is great than INT_MAX/2 or less than INT_MIN/2.

But if 64 bits are allowed to compute 32 bits rounds. It will not fail

int rounded_division(const int a, const int b) {
    return a/b + (2LL*(a%b))/b;
}
Amadeus
  • 10,199
  • 3
  • 25
  • 31
1

Code that I came up with for use on ARM M0 (no floating point, slow divide). It only uses one divide instruction and no conditionals, but will overflow if numerator + (denominator/2) > INT_MAX.

Cycle count on ARM M0 = 7 cycles + the divide (M0 has no divide instruction, so it is toolchain dependant).

int32_t Int32_SignOf(int32_t val)
{
    return (+1 | (val >> 31)); // if v < 0 then -1, else +1
}

uint32_t Int32_Abs(int32_t val)
{
    int32_t tmp = val ^ (val >> 31);
    return (tmp - (val >> 31));

    // the following code looks like it should be faster, using subexpression elimination
    // except on arm a bitshift is free when performed with another operation,
    // so it would actually end up being slower
    // tmp = val >> 31;
    // dst = val ^ (tmp);
    // dst -= tmp;
    // return dst;
}

int32_t Int32_DivRound(int32_t numerator, int32_t denominator)
{
    // use the absolute (unsigned) demominator in the fudge value
    // as the divide by 2 then becomes a bitshift
    int32_t  sign_num  = Int32_SignOf(numerator);
    uint32_t abs_denom = Int32_Abs(denominator);
    return (numerator + sign_num * ((int32_t)(abs_denom / 2u))) / denominator;
}
Craig
  • 76
  • 2
0

since the function seems to be symmetric how about sign(a/b)*floor(abs(a/b)+0.5)