# Another Arithemetic overflow problem

In the code below you see I explicitly set the shown values to be `i64` 64-bit values.
However, when it get close to the limit value, I notice some of them switching between `i32` and `i64` types. How can this happen? I set them to be (and always stay) `i64` to explicitly to prevent `overflow`.

``````start = Time.monotonic
print("The self-describing numbers are:")
i  = 10i64  # self-describing number must end in 0
pw = 10i64  # power of 10
fd = 1i64   # first digit
sd = 1i64   # second digit
dg = 2i64   # number of digits
mx = 11i64  # maximum for current batch
lim = 9_100_000_001i64 # sum of digits can't be more than 10
while i < lim
if selfDesc(i)
secs = (Time.monotonic - start).total_seconds
print("\n#{i} in #{secs} secs")
end
i += 10
if i > mx
fd += 1
sd -= 1
if sd >= 0
i = fd * pw
else
pw *= 10
dg += 1
i  = pw
fd = 1
sd = dg - 1
end
puts " i = #{i.class}, sd = #{sd.class}, pw = #{pw.class}, mx = #{mx.class}"
mx = i + sd * pw // 10
end
end
``````

I inserted that `puts` statement line before the line: `mx = i + sd * pw // 10`
because that’s where the error message said the over flow occurred.
Here’s the output just before it fails.

`````` i = Int32, sd = Int64, pw = Int64, mx = Int32
i = Int32, sd = Int64, pw = Int64, mx = Int32
i = Int32, sd = Int64, pw = Int64, mx = Int32
i = Int32, sd = Int64, pw = Int64, mx = Int32
i = Int64, sd = Int64, pw = Int64, mx = Int32
i = Int32, sd = Int64, pw = Int64, mx = Int64
``````

I finally figured out how to “fix” it by making all the constants in that inner loop `i64`s.
But why should I have to do this? Why do the number types change to lower types?

``````if i > mx
fd += 1i64
sd -= 1i64
if sd >= 0
i = fd * pw
else
pw *= 10i64
dg += 1i64
i  = pw
fd = 1i64
sd = dg - 1i64
end
#puts " i = #{i.class}, sd = #{sd.class}, pw = #{pw.class}, mx = #{mx.class}"
mx = i + sd * pw // 10
``````

There’s a `fd = 1` in the else branch. That assigns a number of value 1 to `fd` and since no type is specified, it picks the default type `Int32`. Just changing this line to `fd = 1i64` is necessary and fixes the problem.

Btw. you can add type restrictions to local variables, that would have immediately alerted you about the mismatching type (ideally it could probably even autocast assigned literals to Int64).

How do you do that? Please give examples.

Changing the `fd = 1` to `fd = i64` did work.
However, what I ended up doing is changing the order of multiplication, shown below, to have `pw` set the type precedence for the multiplication on that line, so `fd = 1` now works.

``````if i > mx
fd += 1
sd -= 1
if sd >= 0
i = pw * fd    <- pw always i64, sets * type precedence
#i = fd * pw
else
pw *= 10
dg += 1
i  = pw
fd = 1         <- can remain the same
sd = dg - 1
end
#puts " i = #{i.class}, sd = #{sd.class}, pw = #{pw.class}, mx = #{mx.class}"
mx = i + sd * pw // 10
end
``````

There’s needs to be a clear (and documented) way users can set variable types and know all math operations results will produce values of that type.

I was lucky here because I’ve run into this kind of thing enough now to know what to look to change, but I pity the poor soul who has no idea of what’s going on, let alone how to figure out how to fix it. 1 Like

NEVER DO THIS

it’s in the documentation under https://crystal-lang.org/reference/syntax_and_semantics/type_grammar.html then “declaring variables”

``````x = uninitialized Int32

x = 0
puts x

x = "test"
puts x
``````

this produces :

`````` Showing last frame. Use --error-trace for full trace.

In test.cr:8:1

8 | x = "test"
^
Error: type must be Int32, not (Int32 | String)
``````

not sure if there is a different/better way.

No, please don’t use `uninitialized`. That’s unsafe!!

The way to declare the type of a variable is the same as you declare the type of anything:

``````x : Int64 = 1
# do stuff with x
``````

tl;dr: never, ever use `uninitialize`

1 Like

I see that documentation on type restrictions is missing from the reference page on local variables. We’ll need to add that.

The result type of math operations doesn’t depend on the type restriction of the variable they are assigned to. It only depends on the return type of the methods that implements it. And this is actually well documented. For example the API docs for `Int32#*(other : Int64) : Int32`. Per convention, binary operators usually return the type of the first operand (see https://crystal-lang.org/reference/syntax_and_semantics/operators.html#binary-operators).

There’s needs to be a clear (and documented) way users can set variable types and know all math operations results will produce values of that type.

As straigh-shoota said, the type of `a op b` is always `a`. I think that’s a pretty easy thing to remember.

I was lucky here because I’ve run into this kind of thing enough now to know what to look to change, but I pity the poor soul who has no idea of what’s going on, let alone how to figure out how to fix it.

We are aware of the problem. Do you have ideas on how to improve the situation?

4 posts were split to a new topic: Arithmethic overflow should not raise an exception

What if we automatically turn `Int32 | Int64` into Int64? That is, you can’t really have a union of two integer types when one fits perfectly inside another one: you just get the bigger type. Same if you do `Int8 | Int16`, `UInt32 | UInt64`, etc. Then at least when mixing Int64 code with Int32 you’ll always get Int64, so correct code.

I’m going to try this next.

1 Like

Meh, maybe for 2.0. Until 1.0 I won’t experiment any more.

This may seem a naive question, but do you understand what `Go` is doing to be able to run the equivalent code with no extra `type` value declarations? Understanding first how it (et al) deal with this issue (design choices) may provide better clarity to go forward from.

``````package main

import (
"fmt"
"strconv"
"strings"
"time"
)

func selfDesc(n uint64) bool {
if n >= 1e10 {
return false
}
s := strconv.FormatUint(n, 10)
for d, p := range s {
if int(p)-'0' != strings.Count(s, strconv.Itoa(d)) {
return false
}
}
return true
}

func main() {
start := time.Now()
fmt.Println("The self-describing numbers are:")
i := uint64(10)   // self-describing number must end in 0
pw := uint64(10)  // power of 10
fd := uint64(1)   // first digit
sd := uint64(1)   // second digit
dg := uint64(2)   // number of digits
mx := uint64(11)  // maximum for current batch
lim := uint64(9_100_000_001) // sum of digits can't be more than 10
for i < lim {
if selfDesc(i) {
secs := time.Since(start).Seconds()
fmt.Printf("%d (in %.1f secs)\n", i, secs)
}
i += 10
if i > mx {
fd++
sd--
if sd >= 0 {
i = fd * pw
} else {
pw *= 10
dg++
i = pw
fd = 1
sd = dg - 1
}
mx = i + sd*pw/10
}
}
osecs := time.Since(start).Seconds()
fmt.Printf("\nTook %.1f secs overall\n", osecs)
}
``````

Go has no union types.

As far as I know Golang doesn’t allow to change the type of a variable. So when you assign an untyped number literal `1` to a local variable, it seems to be autocased to the type of the varible.

``````fd := uint64(1)
fd = 1
fmt.Println(reflect.TypeOf(fd)) // => uint64
fd = uint(32) // cannot use uint32(1) (type uint32) as type uint64 in assignment
``````

Is autocasting something that you can do too when appropriate, to use the larger type?