Combined multiply divide operation on 64bit signed integer without overflow
Asked Answered
M

1

9

I need to calculate

result = (dividend * factor) / divisor

where

dividend: full range of int64_t values
factor: either a full range of uint32_t values or as a special case 2^32
divisor: positive values of int64_t
result: is guaranteed to fit in a int32_t

I need to do this in plain C/C++ without any libraries on a microcontroller. The compiler supports int64_t and uint64_t types; most likely no hardware implementation for multiply or divide. Currently I have a workaround for the uint32_t factor but I need a solution for factor 2^32.

Mincemeat answered 28/12, 2015 at 18:35 Comment(11)
What architecture are you going to run this on?Clinical
What type is factor. int64_t?Pennywise
Type: for the special case it's a constant 2^32; otherwise it's uint32_tMincemeat
For the special case of 2**32 use a shift, but if you have no hardware implementation for multiply or divide it's unlikely that a 64-bit overflow can be handled.Traynor
Architecture: It's a Tensilica core in a ESP8266 chip.Mincemeat
(pretty vanilla 32 bit "RISC", 32/32 giving 32bit quotient or remainder signed&unsigned, but not even 64/32→32bit quotient?)Jahn
If you "have a workaround" for 32-bit factor, for 2**32 can you tolerate using 2**31 and either doubling the dividend or halving the divisor?Traynor
(Please share your workaround for the uint32_t factor.)Jahn
There is no language C/C++. If you think different provide a link to the specification. I'll happily add a new tag.Hernadez
And result = (dividend / divisor) * factor? Or am I misunderstanding the problem?Tautomer
result = (int32_t)((dividend / divisor) * factor + ((dividend % divisor) * factor) / divisor);? (Can the OP please include the assembly generated for the existing work-around?)Jahn
P
1

OP: "Currently I have a workaround for the uint32_t factor"

The factor == 2^32 is a corner case and is all that is needed to be solved here as OP's "workaround" can handle factors [0 ... 2^32-1].

If the dividend can be doubled without overflow, simple use factor == 2^31 with a doubled dividend.

If the divisor is even, use factor == 2^31 with a halved divisor. @Weather Vane

Else the dividend is large. Recall that the quotient is in [-2^31 ... 2^31-1] range. In general, the product of the large dividend and factor == 2^32 dividend by the divisor will out of the int32_t range, so those out-of-range combinations are not of concern as "result: is guaranteed to fit in a int32_t".

The acceptable edge conditions occur with a final quotient near the edge of the int32_t range.

 pow(2,63) == 9223372036854775808
 pow(2,62) == 4611686018427387904
 pow(2,32) == 4294967296
 pow(2,31) == 2147483648

 Smallest Dividends   Factor      Largest Divisors       Smallest Quotients 
-4611686018427387905  4294967296  -9223372036854775807   2147483648.00+
-4611686018427387905  4294967296   9223372036854775807  -2147483648.00+  OK
 4611686018427387904  4294967296  -9223372036854775807  -2147483648.00+  OK
 4611686018427387904  4294967296   9223372036854775807   2147483648.00+

After testing, dividend and divisor, the only representable answer in INT32_MIN.


Sample Code:

int32_t muldiv64(int64_t dividend, uint64_t factor, int64_t divisor) {
  if (factor >= 0x100000000) {
    assert(factor == 0x100000000);
    factor /= 2;
    if (dividend >= INT64_MIN/2 && dividend <= INT64_MAX/2) {
      dividend *= 2;
    } else if (divisor %2 == 0) {
      divisor /= 2;
    } else {
      return INT32_MIN;
    }
  }
  return  workaround_for_the_uint32_t_factor(dividend, factor, divisor);
}

The original problem is to detect this edge condition and how to handle it.. The workaround_for_the_uint32_t_factor() may not have been coded yet, thus it was not posted.

Pennywise answered 28/12, 2015 at 22:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.