Table of Contents
Problem
$2^{15} = 32768$ and the sum of its digits is $3 + 2 + 7 + 6 + 8 = 26$. What is the sum of the digits of the number $2^{1000}$?
Foundations
If you've recently read through chapter 13 then you can skip to the solution. There will be nothing new here.
IEEE 754 - Double precision
Uiua uses the double precision floating point standard of IEEE 754. This has the consequence of rounding integers once they get too large. At around $2^{53}$, or $9007199254740992$ things start breaking down.
Let's try all 1s but with one digit more than that. We'll enter that into the Uiua repl.
11111111111111111 # our input
11111111111111112 # repl output
Yup, that rounds up a little. This problem gets worse with larger numbers. What about a 50 digit number?
53503534226472524250874054075591789781264330331690
53503534226472520000000000000000000000000000000000
That sure looks problematic.
Further reading
Double Precision Floating Point - Limitations on Int
Naive BigInt
BigInts are integers that get arbitrarily large without data loss. Let's make our own limited BigInt in Uiua. We know that we're only going to be doing addition so we can simplify by just focusing on that.
What if we represent BigInt as an array of digits? Let's work out what we want.
Representation
It makes sense to have a function that transforms a string of digits into an array of digits.
FromString ← ≡⋕
FromString "18512831951230"
[1 8 5 1 2 8 3 1 9 5 1 2 3 0]
That also means we'd like to bring them back.
ToString ← /$"__"
ToString [1 8 5 1 2 8 3 1 9 5 1 2 3 0]
"18512831951230"Addition
We can't just mush together two arrays though. Plain arrays overflow.
+ [1 2 3 4] [3 4 5 6]
[4 6 8 10]
We need a method, BigAdd, that corrects the overflow.
BigAdd [1 2 3 4] [3 4 5 6]
[4 6 9 0]
We'd want to detect overflows, >9 and then do two things based on that.
- Mark overflow with $1$, rotate and add to the next 10s place.
- Mark overflow with $1$, subtract 10.
Here's a step by step. We'll start by identifying overflows.
⊸>9 [4 6 8 10]
[4 6 8 10]
[0 0 0 1]
We'll rotate the overflow by one so that the next digit is correctly incremented.
↻1 ⊸>9 [4 6 8 10]
[4 6 8 10]
[0 0 1 0]
# And if added
+ ↻1 ⊸>9 [4 6 8 10]
[4 6 9 10]
Now we just need the correction factor.
Instead of correcting right away, we can fork the overflow array with the
rotation and mul ¯10.
⊃(ׯ10|↻1) ⊸>9 [4 6 8 10]
[4 6 8 10]
[0 0 1 0]
[¯0 ¯0 ¯0 ¯10]
Adding these three together results in a correct BigInt addition.
++ ⊃(ׯ10|↻1) ⊸>9 [4 6 8 10]
[4 6 9 0]Edge case - Adding various lengths
The [4 6 8 10] array was generated from two equal length arrays.
What happens if we want to add together differing lengths?
What about $1 + 999$?
+ [9 9 9] [1]
[10 10 10]
Hmm. That's strange, though intended. Recall the tutorial.
If one of the arrays has exactly one row, that array will be repeated for each row of the other array. This includes scalars.
Uiua.org | Tutorial on More Array Manipulation
This means that we should expect an error from $999 + 11$ unless the add
function has a fill context.
+ [9 9 9] [1 1]
"Error: Shapes [3] and [2] are not compatible"
⬚0+ [9 9 9] [1 1]
[10 10 9]
Though... isn't that filling in from the left? We want the fill to come in from
the right since [1 1] should affect the least significant digits first.
We could combat this by simply reversing the representations. Let's update our definitions.
FromString ← ≡⋕⇌
FromString "18512831951230"
[0 3 2 1 5 9 1 3 8 2 1 5 8 1]
Bringing it back is also a matter of reversal.
ToString ← /$"__"⇌
ToString [0 3 2 1 5 9 1 3 8 2 1 5 8 1]
"18512831951230"
Since we've reversed the notation, we also have to remember to rotate
in the other direction as well.
Multiple steps needed
Let's take what we've gathered so far and start defining our BigAdd.
BigAdd ← ++⊃(ׯ10|↻¯1) ⊸>9 ⬚0+
Let's add together $1234$ + $3456$ and see if the overflow is handled such that we get $4690.$
BigAdd FromString "1234" FromString "3456"
[0 9 6 4]
ToString BigAdd FromString "1234" FromString "3456"
"4690"
Alright. But what about something like $1 + 999$?
ToString BigAdd FromString "1" FromString "999"
"9100"
We're clearly missing something.
Let's make a function that checks if any digit is over $9.$
This can be a condition for a do loop that keeps correcting the digits until
none are over the threshold. We'll update the BigAdd to contain such a loop.
AddNotFin ← /↥ >9
BigAdd ← ⍢(++⊃(↻¯1|ׯ10)⊸>9|AddNotFin) ⬚0+
What does $999 + 1$ result in now?
BigAdd FromString "999" FromString "1"
[1 0 0]
Huh. If we pass that to ToString we get back "001".
It seems like the $10$s overflow correction was run multiple times
but the last calculation rotated the $1$ such that it wrapped around.
This makes sense though. The result should be 4 digits long
but the longest array supplied is 3. We're simply missing a digit.
What if we just append a $0$, both(bw join 0), to the input arguments so that
overflows have an empty significant digit to go to?
If we're just adding together two numbers, at a time, the worst case
is all $9$s, for both arguments, which still just requires one extra space.
We can then introduce a check on the other side that removes a zero if it is
the most significant position. We can just check the last digit and switch between
drop ¯1 or id.
Our definitions now look like this.
FromString ← ≡⋕⇌
ToString ← /$"__"⇌
AddNotFin ← /↥ >9
RemZer ← ⨬(∘|↘¯1)=0⊸⊣
BigAdd ← RemZer⍢(+ + ⊃(↻¯1|ׯ10)⊸>9|AddNotFin) ⬚0 + ∩(˜⊂0)
Let's test $1 + 999$, $999 + 1$ and $1234+3456$ again.
ToString BigAdd FromString "1" FromString "999"
"1000"
ToString BigAdd FromString "999" FromString "1"
"1000"
ToString BigAdd FromString "1234" FromString "3456"
"4690"
Great. That looks correct.
Solution
Multiplying by two is the same as adding the number to itself.
Let's try self BigAdd 2 with a repeat.
ToString⍥₁₀˙BigAdd 2
"2048"
pow 10 2
ⁿ 10 2
1024
Well, off by one. Of course, $2^0=1$, isn't it? The initial factor should be $1$ then.
repeat,0 self BigAdd 1
⍥₀˙BigAdd 1
1
Let's then try again, with repeat $1000$ and reduce sum on the digits instead
of ToString.
reduce add repeat,1000 self BigAdd 1
/+ ⍥₁₀₀₀˙BigAdd 1
1366Speed?
Yes, there is speed. 0.006 seconds. I declare this a success.
⊙◌⍜now(/+ ⍥₁₀₀₀˙BigAdd 1)
0.006022453308105469