Bug in Julia rational array inplace multiplication

On AMD64,

julia> x=[1//1];x.*=1/5
1-element Array{Rational{Int64},1}:
 3602879701896397//18014398509481984

but on i386 works correctly. See travis build for this repo for behavior on different platforms.

I’m seeing that answer on all my x64 machines.

Intel/Linux:

julia> x=[1//1];x.*=1/5
1-element Array{Rational{Int64},1}:
 3602879701896397//18014398509481984

julia> versioninfo()
Julia Version 1.4.0
Commit b8e9a9ecc6 (2020-03-21 16:36 UTC)
Platform Info:
  OS: Linux (x86_64-pc-linux-gnu)
  CPU: Intel(R) Core(TM) i7-3820 CPU @ 3.60GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-8.0.1 (ORCJIT, sandybridge)

Then on an AMD/Windows:

julia> x=[1//1];x.*=1/5
1-element Array{Rational{Int64},1}:
 3602879701896397//18014398509481984

julia> versioninfo()
Julia Version 1.4.0
Commit b8e9a9ecc6 (2020-03-21 16:36 UTC)
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: AMD Ryzen Threadripper 1950X 16-Core Processor
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-8.0.1 (ORCJIT, znver1)

It’s not related to in-place multiplication. It’s because 1/5 is not identical to 0.2:

julia> 1/5 == 1//5
false

julia> big(1/5)
0.200000000000000011102230246251565404236316680908203125

julia> convert(Rational{Int}, 1/5)
3602879701896397//18014398509481984
3 Likes

On any architecture,

julia> x=[Int32(1)//Int32(1)];x.*=1/5
1-element Array{Rational{Int32},1}:
 1//5

julia> x=[Int64(1)//Int64(1)];x.*=1/5
1-element Array{Rational{Int64},1}:
 3602879701896397//18014398509481984

To expand on what @DNF said, what’s happening is

julia> Rational(1//1 * (1/5))
3602879701896397//18014398509481984

which happens to be correct (although somewhat unexpected if you are not aware of how floating point works). Cf

1 Like

It’s not related to in-place multiplication anyhow:

julia> Rational{Int32}(0.2)
1//5

julia> Rational{Int64}(0.2)
3602879701896397//18014398509481984

Why don’t you simply use rational numbers when multiplying?

julia> x=[1//1];x.*=1//5
1-element Array{Rational{Int64},1}:
 1//5

Relying on conversion of floats to rationals seems pretty brittle.

1 Like

I see, thanks for setting me straight.

Just to nuance it a bit, it is indirectly related to in-place multiplication, because setindex! into an Array{T} will attempt to convert to T:

julia> a64 = [1//1];

julia> a64[1] = 0.2;

julia> a64
1-element Array{Rational{Int64},1}:
 3602879701896397//18014398509481984

julia> a32 = [Int32(1)//Int32(1)];

julia> a32[1] = 0.2;

julia> a32
1-element Array{Rational{Int32},1}:
 1//5

For Rational{Int32} there are no values between 0.2 and 1//5, but for Rational{Int64} there is.

Amusingly, for Float32 it’s a different story:

julia> a32[1] = 0.2f0;  # this is a Float32 literal

julia> a32
1-element Array{Rational{Int32},1}:
 13421773//67108864

Since 0.2f0 is further away from 1//5 than 0.2 is, 13421773//67108864 is able to sneak in between them.

Edit: It actually seems like the rational approximations are exact. I wonder if I understand this correctly:

julia> big(3602879701896397)/big(18014398509481984)
0.200000000000000011102230246251565404236316680908203125

julia> big(0.2e0)
0.200000000000000011102230246251565404236316680908203125

julia> big(13421773)/big(67108864)
0.20000000298023223876953125

julia> big(0.2f0)
0.20000000298023223876953125