For loops issue

Running the following code in julia REPL

x = 1.1; h = 0.1;
x_list = [i for i in (x-2h):h:(x+2h)]

Gives the following output

0.9000000000000001
1.0000000000000002
1.1
1.2000000000000002

I’m expecting the output would be the following

0.9
1.0
1.1
1.2
1.3

julia> 1.1 - 2*0.1
0.9000000000000001

This is not Julia-specific, it’s due to the fact that standard floating-point arithmetic doesn’t exactly represent decimal values like 1.1 and 0.1. For example, in Python 3:

>>> 1.1 - 2*0.1
0.9000000000000001

See:

3 Likes

my particular problem is
The output print 4 elements
0.9000000000000001
1.0000000000000002
1.1
1.2000000000000002
I’m expecting 5 elements
0.9
1.0
1.1
1.2
1.3

Thanks

Try

collect(LinRange(0.9,1.3,5))
2 Likes

It works, thank you!

This is an intrinsic problem with floating-point ranges, unfortunately — due to roundoff errors perturbing the endpoints, it can change the number of points by 1.

The solution is to explicitly specify the desired length, with range(x-2h, step=h, length=5), or range(x-2h, x+2h, length=5), or LinRange(x-2h, x+2h, 5). (range and LinRange compute their elements in slightly different ways.)

9 Likes

Or range(0.9, 1.3, 5). You should also consider, @Allan_Olave, whether to just drop the collect and leave it as a range object.

3 Likes

Another option is using StepRangeLen directly with the offset argument. This should be more precise than LinRange. (For x = 1.1, h = 0.1, it produces the same values as LinRange. But in other cases, it might not.)

julia> StepRangeLen(x, h, 5, 3) |> collect
5-element Vector{Float64}:
 0.9000000000000001
 1.0
 1.1
 1.2000000000000002
 1.3

julia> LinRange(x-2h, x+2h, 5) |> collect
5-element Vector{Float64}:
 0.9000000000000001
 1.0
 1.1
 1.2000000000000002
 1.3

StepRangeLen also preserves the exact step while LinRange does not:

julia> StepRangeLen(x, h, 5, 3) |> step
0.1

julia> LinRange(x-2h, x+2h, 5) |> step
0.09999999999999998
2 Likes

But as far as I know, this is what range does, and that would be the ‘user interface’ for StepRangeLen, instead of calling the constructor directly.

range creates a StepRangeLen (in this case), but without using offset. This forces you to use x-2h or x+2h as the reference value, with possible loss of precision:

julia> range(x-2h, step=h, length=5) |> collect
5-element Vector{Float64}:
 0.9000000000000001
 1.0000000000000002
 1.1
 1.2000000000000002
 1.3000000000000003

julia> StepRangeLen(x, h, 5, 3) |> collect
5-element Vector{Float64}:
 0.9000000000000001
 1.0
 1.1
 1.2000000000000002
 1.3

(Note that the second and fifth elements are different.)

2 Likes

This is close in spirit to your original formulation but does the range with integers to ensure five elements. And you don’t need much knowledge about Julia’s range types.

julia> x = 1.1; h = 0.1;

julia> xlist = x .+ (-2:1:2)*h
0.9000000000000001:0.1:1.3

julia> collect(xlist)
5-element Vector{Float64}:
 0.9000000000000001
 1.0
 1.1
 1.2000000000000002
 1.3
6 Likes

Try this

range(x-2h, x+2h, 5)

This results in the same values in this specific case (x=1.1, h=0.1), but can still be less precise for other values:

julia> x = 0.7777952957598123; h = 0.9328657194781406;

julia> x ∈ range(x-2h, x+2h, 5) # shouldn’t this range contain x?
false

julia> x ∈ StepRangeLen(x, h, 5, 3)
true

julia> range(x-2h, x+2h, 5) |> collect
5-element Vector{Float64}:
 -1.087936143196469
 -0.15507042371832835
  0.7777952957598122   # this isn’t the original x …
  1.7106610152379527
  2.6435267347160933

julia> StepRangeLen(x,h,5,3) |> collect
5-element Vector{Float64}:
 -1.087936143196469
 -0.15507042371832824
  0.7777952957598123   # … but this is!
  1.7106610152379529
  2.6435267347160933
1 Like

That is strange, because I thought this was exactly the sort of thing range was designed to do correctly.

I would argue it is doing it correctly, it’s as precise as it can be with the information it is given.

range(x-2h, x+2h, 5) doesn’t know about the original x and h, it only sees the results of the computations x-2h and x+2h, both of which are not exact (due to floating-point arithmetic). Therefore, range(x-2h, x+2h, 5) does not necessarily create a range with step h, or one that contains x.

Of course, this loss of precision may not be important, so using range is perfectly fine. I just wanted to mention StepRangeLen with offset as probably the most precise option for a given x and h.

2 Likes

x .+ (-2:2)*h contains x exactly, another advantage.

4 Likes