0

I have an expression where I halve very large even integers, and I know the numerators are even so there is no fractional part, but I get an overflow error "OverflowError: integer division result too large for a float" because internally Python converts to floats.

return int(n/2) if (n%2) == 0 else int((3*n+1)/2)

I can fix this with mpmath via int(mpf(n)/2) if (n%2) == 0 else int(mpf(3*n+1)/2) but it runs 3x slower than native division and I am looping millions of times so this is too long.

Is there an efficient way to halve a very large even integer?


Some have suggested integer division // and I have tested it. The stats are: -

  • Using / 45 tests per sec
  • Using // 5 tests per sec
  • Using mpf 12 tests per sec
  • Using >> 5 tests per sec

The integers I am working with are of the order 2**2000 but I want to consider larger ones so I need a method that works on unbounded integers.

Mark Kortink
  • 1,770
  • 4
  • 21
  • 36
  • 3
    How large is this large number? Also, what happens if you try `n >> 1`? – Chrispresso Nov 30 '21 at 20:36
  • 1
    Integer division `//`? – Thomas Weller Nov 30 '21 at 20:36
  • Looping millions of times and "too long" seems to be typical for Python. Maybe a different programming language would be faster. – Thomas Weller Nov 30 '21 at 20:41
  • Python is strongly typed and does not internally convert integers to floats. You explicitly converted to floats with the `/` operator. – Woodford Nov 30 '21 at 20:45
  • @Woodford Not sure what about that you think is explicit. If you know that's what the `/` operator does then you will know that's what's going to happen, but there is nothing in the code which *explicitly* says there is a conversion happening. – kaya3 Nov 30 '21 at 20:47
  • if you have a _great many_ values to convert or need to repeat the calculation with some frequency, you may instead want to work with a scientific python library like [Numpy](https://numpy.org/doc/stable/user/whatisnumpy.html), which will be _much_ faster than native Python – ti7 Nov 30 '21 at 21:05
  • @Woodford practically, Python is [Duck Typed](https://en.wikipedia.org/wiki/Duck_typing), so objects which behave like a float can still be made of wood! – ti7 Nov 30 '21 at 21:08

2 Answers2

3

You can use // for integer division or >> to bit-shift it

>>> 10 / 2   # results in float
5.0
>>> 10 // 2  # results in int
5
>>> 10 >> 1  # bit shift
5
>>> f"{10:04b}"  # binary format display
'1010'
>>> f"{5:04b}"   # shows shift
'0101'

Demo on a big number

>>> _ = 10**10000 / 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: integer division result too large for a float
>>> _ = 10**10000 // 2
>>> (10**10000 >> 1) == (10**10000 // 2)
True

Beware, you must coerce non-int inputs in either case

>>> 10.0 / 2
5.0
>>> 10.0 // 2
5.0
>>> 10.0 >> 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for >>: 'float' and 'int'

Take a look at the PEP 238 Abstract which introduced the split of / vs // and the rest for a little about how this is implemented as __div__() (now __truediv__() as of some more recent revision) and __floordiv__() methods on instances of int

ti7
  • 16,375
  • 6
  • 40
  • 68
0

Both of these options seem pretty fast:

In [387]: n = int("12" * 10_000)

In [388]: %timeit n//2
20.6 µs ± 593 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [389]: %timeit n>>1
3.81 µs ± 37.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

You can use wombo_combo = lambda x : x>>1 if x%2 == 0 else (3*x + 1)>>1

Edit:

I think if you're looking to speed things up you can also do something like this, which is slightly faster than %

if n & 1 == 0: # even
    return n >> 1
# odd
return (n*3 + 1) >> 1

Here are more timings for you:

In [78]: n = 2**2000

In [79]: %timeit n>>1 if n&1==0 else (n*3+1)>>1
288 ns ± 8.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [80]: %timeit n//2 if n&1==0 else (n*3+1)//2
566 ns ± 5.37 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [81]: %timeit n//2 if n%2==0 else (n*3+1)//2
965 ns ± 3.24 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Also if you know the numerator is always positive you should just avoid doing the conditional check. That's extra wasted time that could be spent shifting them bits!

Chrispresso
  • 3,660
  • 2
  • 19
  • 31