Why is 2^63 negative?

Hi :wave:

Making my first steps into julia, coming from R and python. Following the getting started guide, I noticed the following:

2^62
# 4611686018427387904
2^63 # <--- This is negative?
# -9223372036854775808
2^63 > 1
# false
2^64
# 0 # this is zero, but that makes sense if it is a 64 bit float

I’d be interested to understand why?

For reference, python and R below:

2^63 > 1
# true
2^63 > 1
# [1] TRUE
2 Likes

In Julia, 2 is an integer literal with 64 bits length. 2^63 overflows into the most signifant bit and thus is negative:

julia> bitstring(2)
"0000000000000000000000000000000000000000000000000000000000000010"

julia> bitstring(2^32)
"0000000000000000000000000000000100000000000000000000000000000000"

julia> bitstring(2^63)
"1000000000000000000000000000000000000000000000000000000000000000"

Since julia is using machine integers (which use Twos-Complement), an integer with a leading 1 is negative.

In contrast, python automatically grows the integer type as necessary - this is much slower for general use though, which is why its not done in julia.

8 Likes

Is it possible to have an error when this happens, eg using a “safe” version of Int64?
Would this check cost “a lot”?

2 Likes

Yep, that’s SafeInt64 from SaferIntegers.jl

13 Likes

There is SaferIntegers.jl, if you want checked arithmetic with integers. You can also use functions in Base.Checked directly, like Base.Checked.checked_add, for example.

6 Likes

In R, a few factors differ from Julia:

  • 2 isn’t an integer – it’s a floating point number. You need 2L to get an integer.
  • x^y always produces a floating point number, even for simple cases.
> x <- 2L
> y <- 4L
> typeof(x)
[1] "integer"
> typeof(y)
[1] "integer"
> typeof(x^y)
[1] "double"

There are similar pitfalls to R’s approach that are just subtler than Julia’s pitfalls:

> 2^58 + 1 == 2^58
[1] TRUE
julia> 2^58 + 1 == 2^58
false
13 Likes

Just for completeness: in NumPy, the integer type (np.int64) behaves the same as in Julia.

9 Likes

And we do this for the same reason: this is the behavior of our machines’ integers. It’s the fastest behavior possible. Doing anything else requires extra operations to emulate some different sort of behavior.

14 Likes

For the how much does this cost question, the answer is anywhere from 10% to 20x. A typical cost is usually in the 2x case, but the added complication can easily prevent massive optimizations.

4 Likes

Should we add a link to https://learnxinyminutes.com/docs/julia/ in the PSA: make it easier to help you since there is an explicit example about integer overflow within the first 10 lines, this seems to be a recurrent stumbling point for ex-python users? (cc @Tamas_Papp)

3 Likes

I don’t think that PSA should be a link to various docs other than the manual, or target users from specific programming languages. Once you start doing that, it gets too long (it already is) and even fewer people would read it.

Fair, indeed there is https://docs.julialang.org/en/v1/manual/faq/#faq-integer-arithmetic it’s a bit lengthy for newcomers though…

Maybe there should be another PSA gathering links to frequently asked questions that we could at least point to when the questions resurface

1 Like

Sometimes I wonder if it would be a good idea to make a blog post about stuff like this. Hit some of the classics like 1.0 != 1.0 (in julia this is actually handled very well compared to say C/C++). By showing some of the boundaries of computing issues we get the opportunity to teach some computer science stuffs to people while introducing the language. Could be fun to show off the interop of Julia too, but maybe this is getting off topic.

Edit someone asked me what I mean by 1.0 != 1.0. Unfortunately Cxx isn’t working with Julia 1.4+ but you can get this kind of behaviour with big floats :). So for example:

a = BigFloat(0.1)
BigFloat(1.0) == BigFloat(10.) * a
# therefore 1.0 != 1.0

So in C/C++ you will deal with this issue a bit, its due to numerical precision. Meanwhile - Julia is great because

1.0 == 1.0
#woo hoo

It is impossible to please everyone — if parts of that FAQ (one of the oldest, actually) were omitted, I imagine people would suggest that they are added. At least when they are there, the reader has the option of just skimming through sections.

3 Likes

IMHO the short comments in this thread from @ericphanson, @simeonschaub, @mbauman and @Oscar_Smith concisely cover most of the information that newcomers typically want when first encountering this issue. I agree with @Tamas_Papp that we shouldn’t remove information from the FAQ entry, but maybe it could be reorganized in a way that it starts off with similar concise and to-the-point descriptions, and then goes into more technical detail explaining the background and motivation for these decisions.

I think that there are three layers of answers to the original question in this post:

  1. Why is 2^63 negative? Because Julia uses overflow/wraparound/native arithmetic.

  2. Why does Julia do this? Because it is fast.

  3. What if I don’t like this? That’s fine, Julia is super-extensible. Just use one of the relevant packages (list follows: …) if they implement what you want, or roll your own.

Separating the answer to subheadings along these lines would be nice. (My wording above is informal, a PR should consider rephrasing).

10 Likes

Thanks everyone for the helpful answers, I learned quite a lot from this!

Making my first steps into julia

This was also my first impression of the Julia community, which is really good! :smile:

4 Likes