Question

I have come up with the following method for summing the digits in number. It seems to work for numbers below about 10^23, but doesn't work for higher numbers. Why does it not work for higher numbers?

x=0
for i in range(0, 5000):
      x+=((number/10**i)//1)%10
print(x)
Was it helpful?

Solution

Leaving aside that this is a very inefficient way to sum the digits, and assuming you're using Python 3, add a conditional print to the loop:

for i in range(0, 5000):
    piece = ((number/10**i)//1)%10
    if piece:
        print(i, piece)
    x+=((number/10**i)//1)%10

Then you'll be able to see where it's going wrong. Starting with number = 10**24, I get output:

0 4.0
1 2.0
24 1.0
7.0

You're not expecting those intermediate results, right? Now you only have to figure out why you're getting them ;-)

The short course is that you're doing floating-point computations when you should be doing integer computations. You get off track immediately (when i is 0):

>>> 10**24/10**0
1e+24
>>> _ // 1
1e+24
>>> _ % 10
4.0

Why is that? Because 10**24 isn't exactly representable as a binary floating-point number:

>>> from decimal import Decimal
>>> Decimal(1e24)
Decimal('999999999999999983222784')

So the exact value of the approximation stored for 1e24 is 999999999999999983222784, and that indeed leaves a remainder of 4 when divided by 10.

To fix this, just stick to integer operations:

number = 10**24
x=0
for i in range(0, 5000):
    x += number//10**i % 10
print(x)

That prints 1. Much more efficient is to do, e.g.,

print(sum(int(ch) for ch in str(number)))

OTHER TIPS

This doesn't particularly answer the question but whenever you're looking at the decimal digits in a number you should use the decimal module:

from decimal import Decimal
sum(Decimal(number).as_tuple().digits)

Some advantages over the naïve sum(int(c) for c in str(int(abs(number)))):

sum(int(c) for c in str(int(abs(1.32))))
#>>> 1

sum(Decimal(1.5).as_tuple().digits)
#>>> 6

Note that

Decimal(1.3)
#>>> Decimal('1.3000000000000000444089209850062616169452667236328125')

because 1.3 is a floating-point number. You'd want

Decimal('1.3')
#>>> Decimal('1.3')

for an exact decimal. This might confuse your results for floating points.

A way to avoid this is to use Decimal(str(number)) instead of Decimal(number), which will give you a more obvious but less technically correct answer.


Also:

%~> python -m timeit -s "from decimal import Decimal" -s "number = 123456789**10" "sum(int(c) for c in str(int(abs(number))))"
10000 loops, best of 3: 70.4 usec per loop
%~> python -m timeit -s "from decimal import Decimal" -s "number = 123456789**10" "sum(Decimal(number).as_tuple().digits)"
100000 loops, best of 3: 11.8 usec per loop

But the real reason is that it's a just conceptually simpler not to think about the string but about the decimal representation.

This looks like Python 3. The "//1" is simply truncation to an integer value, and you don't need that at all if you use // for the first division:

x=0
for i in range(0, 5000):
    x += (number // 10**i)%10
print(x)

As for 10^24, that is equal to 18, so the sum is 1+8=9. (^ is C-style bitwise exclusive or for integer arguments.)

Try 10**24 if you wanted 1 with 24 zeroes following it.

About your loop boundary. To get the number of digits in the integer part of a number, add "math" to your imports and:

ndigits = 1 + int(math.log10(max(1, abs(n))))

If you know you don't have negative numbers, you can leave out the abs(). If you know you don't have numbers less than 1, you can leave out the max() and the abs().

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top