Well, the problem is: when what you expect is 0., any relative difference is “fairly wide”.
What happens here is a cancellation, i.e. the subtraction of two numbers that are very close to one another, resulting in a large increase of the relative error (but substantially smaller increase of the absolute error).
StepRange objects store their start point (0, in this case), length (50 in this case) and step size (100/49 in this case). And the endpoint is re-computed from there. In this case, with infinite precision arithmetic it would be:
julia> 49 * (100//49)
100//1
i.e. exactly what we expect, no error at all. If the step size was computed with double-precision arithmetic, we’d have something like
julia> 49*BigFloat(100/49)
100.000000000000002220446049250313080847263336181640625
julia> 49*BigFloat(100/49)-100.
2.220446049250313080847263336181640625e-15
i.e. an absolute error in the order of 1e-15. But Julia goes an extra step along the way to avoid FP errors, and doubles the working precision when computing the range step:
julia> a = range(0, 100, length=50)
0.0:2.0408163265306123:100.0
julia> a.step
Base.TwicePrecision{Float64}(2.040816326530603, 9.28055818135641e-15)
julia> 49*a.step
Base.TwicePrecision{Float64}(100.0, -5.048709793414476e-29)
julia> 49*a.step - 100.
Base.TwicePrecision{Float64}(-5.048709793414476e-29, 0.0)
In short, the 5e-29 you’re measuring here should not be seen as the (infinite) relative error of a computation returning 0, but rather as an absolute error (rather small if you consider the magnitude of all intermediate results involved).
If you want to avoid such problems with inaccurate end points in ranges, you could perhaps switch to using a LinRange
(which stores both end points of the range) instead of a StepRange
(which stores the start point in combination with the step size):
julia> a = LinRange(0, 100, 50)
50-element LinRange{Float64}:
0.0,2.04082,4.08163,6.12245,8.16327,10.2041,12.2449,14.2857,16.3265,18.3673,20.4082,22.449,24.4898,26.5306,28.5714,30.6122,32.6531,34.6939,36.7347,38.7755,40.8163,42.8571,44.898,46.9388,…,53.0612,55.102,57.1429,59.1837,61.2245,63.2653,65.3061,67.3469,69.3878,71.4286,73.4694,75.5102,77.551,79.5918,81.6327,83.6735,85.7143,87.7551,89.7959,91.8367,93.8776,95.9184,97.9592,100.0
julia> b = 100.
100.0
julia> c = a .- b
50-element LinRange{Float64}:
-100.0,-97.9592,-95.9184,-93.8776,-91.8367,-89.7959,-87.7551,-85.7143,-83.6735,-81.6327,-79.5918,-77.551,-75.5102,-73.4694,-71.4286,-69.3878,-67.3469,-65.3061,-63.2653,-61.2245,-59.1837,-57.1429,…,-40.8163,-38.7755,-36.7347,-34.6939,-32.6531,-30.6122,-28.5714,-26.5306,-24.4898,-22.449,-20.4082,-18.3673,-16.3265,-14.2857,-12.2449,-10.2041,-8.16327,-6.12245,-4.08163,-2.04082,0.0
julia> c[end]
0.0