Unexpected result about integer

I’m a python user doing scientific computing. I’m also interested in julia, and tried it after v1.0 released.
I haven’t start learning julia yet, only tried it as a simple calculator, and get some unexpected result.

Evenviroment:
julia Version 1.0.0 (2018-08-08)
Python 3.6.5 |Anaconda custom (64-bit)
Ubuntu 18.04

julia> 10^23*10^-23
2.003764205206899e-6

julia> 10.0^23*10.0^-23
0.9999999999999999

julia> 10^23
200376420520689664

julia> 10.0^23
1.0e23

julia> 10^1000 + 1
1

julia> 10.0^1000 + 1
Inf

julia> 10^1000 + 1.0
1.0

julia> typeof(1)
Int64

julia> typeof(1.0)
Float64

The reason is simple: the int64 type of julia has a max value of 2^63-1.
I don’t know if this should be considered as a bug, but the result is really unexpected.
A warning or an error would be much better.
Tell me some thing went wrong is better than return the misleading result without a warning.

Compare with Python:
the int type of python seems can be unlimited large.
the float type has a max value of 1.7976931348623157e+308.
When the value is out of range, it will return a OverflowError.

>>> 10**23*10**-23
0.9999999999999999

>>> 10.0**23*10.0**-23
0.9999999999999999

>>> 10**23
100000000000000000000000

>>> 10.0**23
1e+23

>>> 10**1000 + 1
10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001

>>> 10.0**1000 + 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: (34, 'Numerical result out of range')

>>> 10**1000 + 1.0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: int too large to convert to float

>>> type(1)
<class 'int'>

>>> type(1.0)
<class 'float'>

>>> import sys
>>> sys.float_info.max
1.7976931348623157e+308

A couple of remarks:

4 Likes
julia> BigInt(10^1000) + 1
1

julia> BigFloat(10.0^1000) + 1
Inf

julia> BigInt(10)^BigInt(1000) + 1
10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001

julia> BigFloat(10.0)^BigInt(1000) + 1
1.000000000000000000000000000000000000000000000000000000000000000000000000000004e+1000

julia> BigInt(10)^BigInt(23)*BigInt(10)^BigInt(-23)
ERROR: DomainError with -23:
`y` cannot be negative.
Stacktrace:
 [1] bigint_pow(::BigInt, ::BigInt) at ./gmp.jl:504
 [2] ^(::BigInt, ::BigInt) at ./gmp.jl:525
 [3] top-level scope at none:0

julia> BigInt(10)^BigInt(23)*BigInt(10)^-BigInt(23)
ERROR: DomainError with -23:
`y` cannot be negative.
Stacktrace:
 [1] bigint_pow(::BigInt, ::BigInt) at ./gmp.jl:504
 [2] ^(::BigInt, ::BigInt) at ./gmp.jl:525
 [3] top-level scope at none:0

julia> BigInt(10)^BigInt(23)*BigInt(10)^BigFloat(-23)
1.0

I need to put every single number in BigInt() or BigFloat() to get the right ans.
With SaferIntegers.jl, I also need to put every single number in SafeInt().
This seems kinda silly to me.

Not necessarily every number:

julia> 10^BigInt(23)*10^BigFloat(-23)
1.0
julia> SafeInt8(127) * 2
ERROR: OverflowError: 127 * 2 overflowed for type Int8

And I wouldn’t say that the explicitness is silly. In the worst case you can call it inconvenient but only if you are using Julia not as a programming language but as a glorified calculator in which you have to enter a myriad of number literals.

A nice blog post which might help you understand the default: Computers are Machines · John Myles White

2 Likes

I think this is a bit more convenient, and it is also faster:

big(10)^23 * big(10)^-23

or

b = big(10)
b^23 * b^-23

Compare:

julia> @btime 10^big(23)
  341.462 ns (7 allocations: 152 bytes)
100000000000000000000000

julia> @btime big(10)^23
  263.409 ns (5 allocations: 112 bytes)
100000000000000000000000

But, oops, it is less accurate. Hmmm:

julia> big(10)^23 * big(10)^-23
1.000000000000000000000000000000000000000000000000000000000000000000000000000052

julia> 10^BigInt(23)*10^BigFloat(-23)
1.0

Actually, there is some weirdness going on here:

julia> big(10)^(-24)
1.000000000000000000000000000000000000000000000000000000000000000000000000000057e-24

julia> expo = -24
-24

julia> big(10)^expo
ERROR: DomainError with -24:
`y` cannot be negative.
Stacktrace:
 [1] bigint_pow(::BigInt, ::Int64) at ./gmp.jl:504
 [2] ^(::BigInt, ::Int64) at ./gmp.jl:527
 [3] top-level scope at none:0

It must have something to do with that literals are handled differently from variables, but that one was a surprise to me.

1 Like

You are right, this looks weird. It appears to be a regression introduced in 0.7.

julia> VERSION
v"0.6.4"

julia> big(10)^(-23)
ERROR: DomainError:
Stacktrace:
 [1] bigint_pow(::BigInt, ::Int64) at ./gmp.jl:461
 [2] literal_pow(::Base.#^, ::BigInt, ::Type{Val{-23}}) at ./intfuncs.jl:208

julia> expo = -23
-23

julia> big(10)^expo
ERROR: DomainError:
Stacktrace:
 [1] bigint_pow(::BigInt, ::Int64) at ./gmp.jl:461
 [2] ^(::BigInt, ::Int64) at ./gmp.jl:482

vs.

julia> VERSION
v"0.7.0"

julia> big(10)^(-23)
1.000000000000000000000000000000000000000000000000000000000000000000000000000045e-23

julia> expo = -23
-23

julia> big(10)^expo
ERROR: DomainError with -23:
`y` cannot be negative.
Stacktrace:
 [1] bigint_pow(::BigInt, ::Int64) at ./gmp.jl:504
 [2] ^(::BigInt, ::Int64) at ./gmp.jl:527
 [3] top-level scope at none:0

You could also write (see decimal point in second exponent to force BigFloat):

10^big"23"*10^big"-23."

I agree. But we could probably create some macros similar to this:

> using MacroTools: postwalk
> macro bf(a) postwalk(x -> x isa Integer ? BigFloat(x) : x, a) end;
> macro bi(a) postwalk(x -> x isa Integer ? BigInt(x) : x, a) end;

> @bf 10^23 * 10^-23
1.0

> @bi begin 
              println(10^100 + 1)
              println(10^1000 + 1 - 10^1000 - 1)
      end
10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
0

Be aware that simply convert to BigFloat isn’t “safe”:

> @bf 10^1000 + 1 - 10^1000 - 1
-1.0
julia> 10^1000
0

julia> 10^BigInt(1000)
10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

julia> 10^BigFloat(1000)
1.000000000000000000000000000000000000000000000000000000000000000000000000000004e+1000

julia> 10^BigInt(1000) + 1 - 10^BigInt(1000) - 1
0

julia> 10^BigFloat(1000) + 1 - 10^BigFloat(1000) - 1
-1.0

So, even with BigInt/BigFloat() I may still get wrong ans without being aware of it.
And Python has the same problem. #TIL :+1:

>>> 10**1000 + 1 - 10**1000 - 1
0

>>> 10.**1000 + 1 - 10.**1000 - 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: (34, 'Numerical result out of range')

>>> 10**100 + 1 - 10**100 - 1
0

>>> 10.0**100 + 1 - 10.0**100 - 1
-1.0

But what’s going on here:

julia> 10^BigInt(100) + 1 - 10^BigInt(100) - 1.0
0.0

julia> 10^BigInt(100) + 1.0 - 10^BigInt(100) - 1
-1.0

julia> 10^BigInt(110) + 1.0 - 10^BigInt(110) - 1
-1.0

julia> 10^BigInt(111) + 1.0 - 10^BigInt(111) - 1
-2.596148429267413814265248164610049e+33

julia> 10^BigInt(308) + 1.0 - 10^BigInt(308) - 1
-4.278797250636008849512039341305132266417330874820046369744080218757492603003989e+230

julia> 10^BigInt(309) + 1.0 - 10^BigInt(309) - 1
5.036311303168244761381837589469883065123985227880133297090232009398813113114232e+231

julia> 10^BigInt(1000) + 1.0 - 10^BigInt(1000) - 1
4.357748021848539487284672161416420590604097564749035815958799690306706944059397e+922

v.s. Python

>>> 10**100 + 1 - 10**100 - 1.0
0.0

>>> 10**100 + 1.0 - 10**100 - 1
-1.0

>>> 10**110 + 1.0 - 10**110 - 1
-1.0

>>> 10**111 + 1.0 - 10**111 - 1
-1.0

>>> 10**308 + 1.0 - 10**308 - 1
-1.0

>>> 10**309 + 1.0 - 10**309 - 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: int too large to convert to float

>>> 10**1000 + 1.0 - 10**1000 - 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: int too large to convert to float

You are using BigFloat which has limited precision:
10^BigFloat(1000) and 10^BigFloat(1000)+1 are the same BigFloat number so
(10^BigFloat(1000) + 1 ) - 10^BigFloat(1000) is zero,
then
((10^BigFloat(1000) + 1 ) - 10^BigFloat(1000)) - 1 is -1

You would need 1000 decimals of precision for this computation:

julia> log2(10^BigFloat(1000))
3.32192809486e+03

julia> setprecision(3322)
3322

julia> 10^BigFloat(1000) + 1 - 10^BigFloat(1000) - 1
0.0

Edit: also python does not have a native big float type, so it will fail to convert a large integer to a float, whereas julia converts BigInt to BigFloat which explains the python error.

Those errors are within the default precision of BigFloat, this will go away if you use larger precision.

I am still trying to figure this one out. Even though they are within the default relative precision of BigFloat which you can check with the eps method:

julia> eps(big"1e111")
1.0384593717069655257060992658440192e+34

julia> eps(big"1e308")
1.552518092300708935148979488462502555256886017116696611139052038026050952686377e+231

julia> eps(big"1e309")
1.242014473840567148119183590770002044205508813693357288911241630420840762149102e+232

julia> eps(big"1e1000")
9.077509367765559473217972708288397454198398713067023807283572112948755829682985e+922

I would still expect from the order of operations and promotions to get -1 for all of them.
What is going on here:

julia> BigFloat(10^BigInt(309))-(10^BigInt(309))
5.036311303168244761381837589469883065123985227880133297090232009398813113114232e+231

julia> typeof(10^BigInt(309))
BigInt

julia> promote_type(BigFloat,BigInt)
BigFloat

julia> a=BigFloat(10^BigInt(309))
1.000000000000000000000000000000000000000000000000000000000000000000000000000005e+309

julia> b=10^BigInt(309)
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

julia> @which a+b
+(x::BigFloat, c::BigInt) in Base.MPFR at mpfr.jl:349

It is using a MPFR method (most specific dispatch) to operate on mixed types and not going through promotions, so this explains the observations. As long as it is within the expected precision this is good but it was surprising to me.

1 Like