2

I have the following code:

def main(n):
    sequence = []
    while n not in sequence:
        sequence.append(n)

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

    print("Sequence length: " + str(len(sequence)-1))

n = int(input("Number: "))
main(n)

It takes an integer from the user, then calculates the '3x+1' (or 'Collatz') sequence for that number (see http://www.ericr.nl/wondrous/index.html#part1 for details). It then displays the length of the sequence.

To make sure my code works as expected, I'm comparing my results against the table on http://www.ericr.nl/wondrous/delrecs.html The 'Delay' column appears to be the sequence length, and 'N' is the initial integer that's input. My code gives the correct length for all 'N' values (that I've random tested) up to 1,899148,184679 (#92). However for the next value of 'N' (2,081751,768559, #93) it gives a length of '385' (instead of '1437')... The same is true for all values on 'N' (that I've tested) greater than that on the table - my code gives a much smaller answer than what they do.

Does Python have issues with large numbers that might cause this behaviour, or is there something wrong with my code?

I admit that I don't fully understand 'delay records', or indeed much of the information on that webpage. But even if I'm wrong in my assumption that a 'delay record' is just the length of the sequence generated by this method, it seems odd that my code would calculate the same values up until that specific number...

I'm using Python 3.8.10 in case that's relevant.

BWPanda
  • 145
  • 7
  • One of Python's great strengths is that it can handle unlimited size integers. Chances are about 99.99% that it's your code. – Mark Ransom Aug 04 '21 at 03:39
  • 2
    *floating point error?* `float` objects are fixed-size, binary floating point numbers. Use `//` instead for integer division, Python `int` objects are arbitrarily sized – juanpa.arrivillaga Aug 04 '21 at 03:40
  • @drum mm well, *integer* overflow isn't really a problem in Python – juanpa.arrivillaga Aug 04 '21 at 03:41
  • https://en.wikipedia.org/wiki/Double-precision_floating-point_format — see precision for integer values, as n / 2 will return a float (64-bit IEEE) in Python 3. Previous comment gives the correction. – user2864740 Aug 04 '21 at 03:45

2 Answers2

4

Somewhere along the way you are getting a floating point error. "But I am using integers!" I hear you say, well Python is doing a float division on this line:

i = n / 2

Seems innocuous enough, but changing to integer division solves the problem:

i = n // 2

After several hundred values, one of those divisions is giving you an error with the value being some epsilon less than the actual integer value, which then rounds down when you call int(n).

EDIT: After double checking my last point to find the value that fails, I wasn't quite correct. What is actually happening is that while integer division is always accurate thanks to Pythons BigInt implementation, float division isn't since it still uses regular floating point numbers for speed. If your number is large enough then there simply aren't enough bytes to accurately store the number which will result in rounding errors.

The number in question is 19981441939834942. With integer division you get 9990720969917471, while float division yields 9990720969917472.0.

This will be a problem in any language you use (except that most other languages won't allow you to accidentally use float division on integers), so make sure to use integer divisions!

Turksarama
  • 1,136
  • 6
  • 13
  • Thank you! I'd never heard of 'integer division' before, so after I researched it I discovered it's also called 'floor division'. For posterity, here's some info: https://www.pythontutorial.net/advanced-python/python-floor-division/ – BWPanda Aug 04 '21 at 04:32
  • 1
    @BWPanda it's called integer division commonly because in most languages (Including Python prior to 3.0) dividing an integer by an integer always produces another integer! When Python introduced a new operator they needed a new name for it, because it isn't just for integers - it works on floats too! `5.1 // 2.1` will give you `2.0`, even though the more accurate answer is `2.4285714285714284`. – Mark Ransom Aug 04 '21 at 14:34
  • I was having the same issue with the digital root algorithm... integer division solved it where casting to int or using floor() failed for large numbers. – JeffSpicoli Feb 11 '23 at 00:33
2
  • You need to use // instead of /
  • The error happened in the number:19981441939834942,which is 85 index in the sequence.

test code:

x = 19981441939834942
print(19981441939834942 // 2)
print(int(19981441939834942 / 2)) # your code to calculate it.

test result:

9990720969917471 #correct result
9990720969917472 #wrong result

fixed code:

def main(n):
    sequence = []
    while n not in sequence:
        sequence.append(n)

        if n % 2 == 0:
            n = n // 2 # you use `/` then convert to int, which may cause wrong result when n is a huge number.
        else:
            n = (n * 3) + 1
        # n = int(i)
    print("Sequence length: " + str(len(sequence)-1))

n = 2081751768559
main(n)

result:

Sequence length: 1437
leaf_yakitori
  • 2,232
  • 1
  • 9
  • 21