Skip to content

range(start::Int; step, length) no longer uses TwicePrecision #44292

@jipolanco

Description

@jipolanco

Not sure if this is considered a regression, but the following:

r = range(0; step = 0.2, length = 5)
collect(r)  # [0.0, 0.2, 0.4, 0.6, 0.8]                on Julia 1.7
collect(r)  # [0.0, 0.2, 0.4, 0.6000000000000001, 0.8] on Julia 1.8 / master

returns a StepRangeLen{Float64, Int64, Float64, Int64} on Julia master / 1.8, while it used to return a StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64} on Julia 1.7.

I guess this change of behaviour is in conflict with the statement in the range docs:

Special care is taken to ensure intermediate values are computed rationally. To avoid this induced overhead, see the LinRange constructor.

Note that this doesn't happen when start is a float (more specifically, it must be a Union{Float16,Float32,Float64} and have the same type as step), and therefore changing 0 by 0.0 in the above example works around the issue (so that the range_start_step_length function defined in twiceprecision.jl is called).


This change of behaviour seems to be introduced by #43059, and more precisely in the new definition of the range_start_step_length function copied below. From what I understand, that PR aimed at fixing issues dealing with integer ranges (but I might be wrong about this...). In any case, the branch if stop isa Signed is always skipped if step is a float, in which case stop is also a float.

function range_start_step_length(a, step, len::Integer)
    stop = a + step * (len - oneunit(len))
    if stop isa Signed
        # overflow in recomputing length from stop is okay
        return StepRange{typeof(stop),typeof(step)}(convert(typeof(stop), a), step, stop)
    end
    return StepRangeLen{typeof(stop),typeof(a),typeof(step)}(a, step, len)
end

So, I guess an easy fix would be to add a specialisation such as:

function range_start_step_length(a, step::Union{Float16,Float32,Float64}, len::Integer)
    return range_start_stop_length(oftype(step, a), step, len)
end

which will end up calling the appropriate function from twiceprecision.jl. Is there any reason why this wouldn't be a good idea?

Metadata

Metadata

Assignees

No one assigned

    Labels

    rangesEverything AbstractRangeregressionRegression in behavior compared to a previous version

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions