Skip to content

Commit 6274ac3

Browse files
committed
Ranges: use Float64 rather than TwicePrecision for Float16, Float32
1 parent f4aa9ce commit 6274ac3

File tree

4 files changed

+99
-61
lines changed

4 files changed

+99
-61
lines changed

base/float.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -885,7 +885,8 @@ end
885885

886886
float(r::StepRange) = float(r.start):float(r.step):float(last(r))
887887
float(r::UnitRange) = float(r.start):float(last(r))
888-
float(r::StepRangeLen) = StepRangeLen(float(r.ref), float(r.step), length(r), r.offset)
888+
float(r::StepRangeLen{T}) where {T} =
889+
StepRangeLen{typeof(float(T(r.ref)))}(float(r.ref), float(r.step), length(r), r.offset)
889890
function float(r::LinSpace)
890891
LinSpace(float(r.start), float(r.stop), length(r))
891892
end

base/range.jl

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ end
205205

206206
StepRangeLen(ref::R, step::S, len::Integer, offset::Integer = 1) where {R,S} =
207207
StepRangeLen{typeof(ref+0*step),R,S}(ref, step, len, offset)
208+
StepRangeLen{T}(ref::R, step::S, len::Integer, offset::Integer = 1) where {T,R,S} =
209+
StepRangeLen{T,R,S}(ref, step, len, offset)
208210

209211
## linspace and logspace
210212

@@ -370,9 +372,12 @@ julia> step(linspace(2.5,10.9,85))
370372
"""
371373
step(r::StepRange) = r.step
372374
step(r::AbstractUnitRange) = 1
373-
step(r::StepRangeLen) = r.step
375+
step(r::StepRangeLen{T}) where {T} = T(r.step)
374376
step(r::LinSpace) = (last(r)-first(r))/r.lendiv
375377

378+
step_hp(r::StepRangeLen) = r.step
379+
step_hp(r::Range) = step(r)
380+
376381
unsafe_length(r::Range) = length(r) # generic fallback
377382

378383
function unsafe_length(r::StepRange)
@@ -455,10 +460,9 @@ done(r::StepRange, i) = isempty(r) | (i < min(r.start, r.stop)) | (i > max(r.sta
455460
done(r::StepRange, i::Integer) =
456461
isempty(r) | (i == oftype(i, r.stop) + r.step)
457462

458-
# see also twiceprecision.jl
459-
start(r::StepRangeLen) = (unsafe_getindex(r, 1), 1)
460-
next(r::StepRangeLen{T}, s) where {T} = s[1], (T(s[1]+r.step), s[2]+1)
461-
done(r::StepRangeLen, s) = s[2] > length(r)
463+
start(r::StepRangeLen) = 1
464+
next(r::StepRangeLen{T}, i) where {T} = unsafe_getindex(r, i), i+1
465+
done(r::StepRangeLen, i) = i > length(r)
462466

463467
start(r::UnitRange{T}) where {T} = oftype(r.start + oneunit(T), r.start)
464468
next(r::AbstractUnitRange{T}, i) where {T} = (convert(T, i), i + oneunit(T))
@@ -496,7 +500,7 @@ end
496500

497501
function getindex(v::Range{T}, i::Integer) where T
498502
@_inline_meta
499-
ret = convert(T, first(v) + (i - 1)*step(v))
503+
ret = convert(T, first(v) + (i - 1)*step_hp(v))
500504
ok = ifelse(step(v) > zero(step(v)),
501505
(ret <= v.stop) & (ret >= v.start),
502506
(ret <= v.start) & (ret >= v.stop))
@@ -516,6 +520,11 @@ function unsafe_getindex(r::StepRangeLen{T}, i::Integer) where T
516520
T(r.ref + u*r.step)
517521
end
518522

523+
function _getindex_hiprec(r::StepRangeLen, i::Integer) # without rounding by T
524+
u = i - r.offset
525+
r.ref + u*r.step
526+
end
527+
519528
function unsafe_getindex(r::LinSpace, i::Integer)
520529
lerpi.(i-1, r.lendiv, r.start, r.stop)
521530
end
@@ -556,11 +565,14 @@ function getindex(r::StepRange, s::Range{<:Integer})
556565
range(st, step(r)*step(s), length(s))
557566
end
558567

559-
function getindex(r::StepRangeLen, s::OrdinalRange{<:Integer})
568+
function getindex(r::StepRangeLen{T}, s::OrdinalRange{<:Integer}) where {T}
560569
@_inline_meta
561570
@boundscheck checkbounds(r, s)
562-
vfirst = unsafe_getindex(r, first(s))
563-
return StepRangeLen(vfirst, r.step*step(s), length(s))
571+
# Find closest approach to offset by s
572+
ind = linearindices(s)
573+
offset = max(min(1 + round(Int, (r.offset - first(s))/step(s)), last(ind)), first(ind))
574+
ref = _getindex_hiprec(r, first(s) + (offset-1)*step(s))
575+
return StepRangeLen{T}(ref, r.step*step(s), length(s), offset)
564576
end
565577

566578
function getindex(r::LinSpace, s::OrdinalRange{<:Integer})
@@ -725,16 +737,17 @@ end
725737
## linear operations on ranges ##
726738

727739
-(r::OrdinalRange) = range(-first(r), -step(r), length(r))
728-
-(r::StepRangeLen) = StepRangeLen(-r.ref, -r.step, length(r), r.offset)
740+
-(r::StepRangeLen{T,R,S}) where {T,R,S} =
741+
StepRangeLen{T,R,S}(-r.ref, -r.step, length(r), r.offset)
729742
-(r::LinSpace) = LinSpace(-r.start, -r.stop, length(r))
730743

731744
+(x::Real, r::AbstractUnitRange) = range(x + first(r), length(r))
732745
# For #18336 we need to prevent promotion of the step type:
733746
+(x::Number, r::AbstractUnitRange) = range(x + first(r), step(r), length(r))
734747
+(x::Number, r::Range) = (x+first(r)):step(r):(x+last(r))
735-
function +(x::Number, r::StepRangeLen)
748+
function +(x::Number, r::StepRangeLen{T}) where T
736749
newref = x + r.ref
737-
StepRangeLen{eltype(newref),typeof(newref),typeof(r.step)}(newref, r.step, length(r), r.offset)
750+
StepRangeLen{typeof(T(r.ref) + x)}(newref, r.step, length(r), r.offset)
738751
end
739752
function +(x::Number, r::LinSpace)
740753
LinSpace(x + r.start, x + r.stop, r.len)
@@ -750,15 +763,18 @@ end
750763
-(r::Range, x::Number) = +(-x, r)
751764

752765
*(x::Number, r::Range) = range(x*first(r), x*step(r), length(r))
753-
*(x::Number, r::StepRangeLen) = StepRangeLen(x*r.ref, x*r.step, length(r), r.offset)
766+
*(x::Number, r::StepRangeLen{T}) where {T} =
767+
StepRangeLen{typeof(x*T(r.ref))}(x*r.ref, x*r.step, length(r), r.offset)
754768
*(x::Number, r::LinSpace) = LinSpace(x * r.start, x * r.stop, r.len)
755769
# separate in case of noncommutative multiplication
756770
*(r::Range, x::Number) = range(first(r)*x, step(r)*x, length(r))
757-
*(r::StepRangeLen, x::Number) = StepRangeLen(r.ref*x, r.step*x, length(r), r.offset)
771+
*(r::StepRangeLen{T}, x::Number) where {T} =
772+
StepRangeLen{typeof(T(r.ref)*x)}(r.ref*x, r.step*x, length(r), r.offset)
758773
*(r::LinSpace, x::Number) = LinSpace(r.start * x, r.stop * x, r.len)
759774

760775
/(r::Range, x::Number) = range(first(r)/x, step(r)/x, length(r))
761-
/(r::StepRangeLen, x::Number) = StepRangeLen(r.ref/x, r.step/x, length(r), r.offset)
776+
/(r::StepRangeLen{T}, x::Number) where {T} =
777+
StepRangeLen{typeof(T(r.ref)/x)}(r.ref/x, r.step/x, length(r), r.offset)
762778
/(r::LinSpace, x::Number) = LinSpace(r.start / x, r.stop / x, r.len)
763779

764780
/(x::Number, r::Range) = [ x/y for y=r ]

base/twiceprecision.jl

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -308,23 +308,57 @@ end
308308

309309
## StepRangeLen
310310

311-
# If using TwicePrecision numbers, deliberately force user to specify offset
312-
StepRangeLen(ref::TwicePrecision{T}, step::TwicePrecision{T}, len::Integer, offset::Integer) where {T} =
311+
# Use TwicePrecision only for Float64; use Float64 for T<:Union{Float16,Float32}
312+
# Ratio-of-integers constructors
313+
function steprangelen_hp(::Type{Float64}, ref::Tuple{Integer,Integer},
314+
step::Tuple{Integer,Integer}, nb::Integer,
315+
len::Integer, offset::Integer)
316+
StepRangeLen(TwicePrecision{Float64}(ref),
317+
TwicePrecision{Float64}(step, nb), Int(len), offset)
318+
end
319+
320+
function steprangelen_hp(::Type{T}, ref::Tuple{Integer,Integer},
321+
step::Tuple{Integer,Integer}, nb::Integer,
322+
len::Integer, offset::Integer) where {T<:IEEEFloat}
323+
StepRangeLen{T}(ref[1]/ref[2], step[1]/step[2], Int(len), offset)
324+
end
325+
326+
# AbstractFloat constructors (can supply a single number or a 2-tuple
327+
const F_or_FF = Union{AbstractFloat, Tuple{AbstractFloat,AbstractFloat}}
328+
asF64(x::AbstractFloat) = Float64(x)
329+
asF64(x::Tuple{AbstractFloat,AbstractFloat}) = Float64(x[1]) + Float64(x[2])
330+
331+
function steprangelen_hp(::Type{Float64}, ref::F_or_FF,
332+
step::F_or_FF, nb::Integer,
333+
len::Integer, offset::Integer)
334+
StepRangeLen(TwicePrecision{Float64}(ref...),
335+
twiceprecision(TwicePrecision{Float64}(step...), nb), Int(len), offset)
336+
end
337+
338+
function steprangelen_hp(::Type{T}, ref::F_or_FF,
339+
step::F_or_FF, nb::Integer,
340+
len::Integer, offset::Integer) where {T<:IEEEFloat}
341+
StepRangeLen{T}(asF64(ref),
342+
asF64(step), Int(len), offset)
343+
end
344+
345+
346+
347+
StepRangeLen(ref::TwicePrecision{T}, step::TwicePrecision{T},
348+
len::Integer, offset::Integer=1) where {T} =
313349
StepRangeLen{T,TwicePrecision{T},TwicePrecision{T}}(ref, step, len, offset)
314350

315351
# Construct range for rational start=start_n/den, step=step_n/den
316352
function floatrange(::Type{T}, start_n::Integer, step_n::Integer, len::Integer, den::Integer) where T
317353
if len < 2
318-
return StepRangeLen(TwicePrecision{T}((start_n, den)),
319-
TwicePrecision{T}((step_n, den)), Int(len), 1)
354+
return steprangelen_hp(T, (start_n, den), (step_n, den), 0, Int(len), 1)
320355
end
321356
# index of smallest-magnitude value
322357
imin = clamp(round(Int, -start_n/step_n+1), 1, Int(len))
323358
# Compute smallest-magnitude element to 2x precision
324359
ref_n = start_n+(imin-1)*step_n # this shouldn't overflow, so don't check
325360
nb = nbitslen(T, len, imin)
326-
StepRangeLen(TwicePrecision{T}((ref_n, den)),
327-
TwicePrecision{T}((step_n, den), nb), Int(len), imin)
361+
steprangelen_hp(T, (ref_n, den), (step_n, den), nb, Int(len), imin)
328362
end
329363

330364
function floatrange(a::AbstractFloat, st::AbstractFloat, len::Real, divisor::AbstractFloat)
@@ -339,8 +373,7 @@ function floatrange(a::AbstractFloat, st::AbstractFloat, len::Real, divisor::Abs
339373
end
340374
# Fallback (misses the opportunity to set offset different from 1,
341375
# but otherwise this is still high-precision)
342-
StepRangeLen(TwicePrecision{T}((a,divisor)),
343-
TwicePrecision{T}((st,divisor), nbitslen(T, len, 1)), Int(len), 1)
376+
steprangelen_hp(T, (a,divisor), (st,divisor), nbitslen(T, len, 1), Int(len), 1)
344377
end
345378

346379
function colon(start::T, step::T, stop::T) where T<:Union{Float16,Float32,Float64}
@@ -381,8 +414,7 @@ function colon(start::T, step::T, stop::T) where T<:Union{Float16,Float32,Float6
381414
# if we've overshot the end, subtract one:
382415
len -= (start < stop < stop′) + (start > stop > stop′)
383416
end
384-
385-
StepRangeLen(TwicePrecision(start, zero(T)), twiceprecision(step, nbitslen(T, len, 1)), len)
417+
steprangelen_hp(T, start, step, 0, len, 1)
386418
end
387419

388420
function range(a::T, st::T, len::Integer) where T<:Union{Float16,Float32,Float64}
@@ -399,16 +431,7 @@ function range(a::T, st::T, len::Integer) where T<:Union{Float16,Float32,Float64
399431
return floatrange(T, start_n, step_n, len, den)
400432
end
401433
end
402-
StepRangeLen(TwicePrecision(a, zero(T)), TwicePrecision(st, zero(T)), len)
403-
end
404-
405-
step(r::StepRangeLen{T,R,S}) where {T,R,S<:TwicePrecision} = convert(eltype(S), r.step)
406-
407-
start(r::StepRangeLen{<:Any,<:TwicePrecision,<:TwicePrecision}) = 1
408-
done(r::StepRangeLen{<:Any,<:TwicePrecision,<:TwicePrecision}, i::Int) = length(r) < i
409-
function next(r::StepRangeLen{<:Any,<:TwicePrecision,<:TwicePrecision}, i::Int)
410-
@_inline_meta
411-
unsafe_getindex(r, i), i+1
434+
steprangelen_hp(T, a, st, 0, len, 1)
412435
end
413436

414437
# This assumes that r.step has already been split so that (0:len-1)*r.step.hi is exact
@@ -542,7 +565,7 @@ end
542565
function linspace(start::T, stop::T, len::Integer) where {T<:IEEEFloat}
543566
len < 2 && return _linspace1(T, start, stop, len)
544567
if start == stop
545-
return StepRangeLen(TwicePrecision(start,zero(T)), TwicePrecision(zero(T),zero(T)), len)
568+
return steprangelen_hp(T, start, zero(T), 0, len, 1)
546569
end
547570
# Attempt to find exact rational approximations
548571
start_n, start_d = rat(start)
@@ -587,8 +610,7 @@ function _linspace(start::T, stop::T, len::Integer) where {T<:IEEEFloat}
587610
if len == 2 && !isfinite(step)
588611
# For very large endpoints where step overflows, exploit the
589612
# split-representation to handle the overflow
590-
return StepRangeLen(TwicePrecision(start, zero(T)),
591-
TwicePrecision(-start, stop), 2)
613+
return steprangelen_hp(T, start, (-start, stop), 0, 2, 1)
592614
end
593615
# 2x calculations to get high precision endpoint matching while also
594616
# preventing overflow in ref_hi+(i-offset)*step_hi
@@ -601,23 +623,22 @@ function _linspace(start::T, stop::T, len::Integer) where {T<:IEEEFloat}
601623
a, b = (start - x1_hi) - x1_lo, (stop - x2_hi) - x2_lo
602624
step_lo = (b - a)/(len - 1)
603625
ref_lo = a - (1 - imin)*step_lo
604-
StepRangeLen(TwicePrecision(ref, ref_lo), TwicePrecision(step_hi, step_lo), Int(len), imin)
626+
steprangelen_hp(T, (ref, ref_lo), (step_hi, step_lo), 0, Int(len), imin)
605627
end
606628

607629
# linspace for rational numbers, start = start_n/den, stop = stop_n/den
608630
# Note this returns a StepRangeLen
609631
function linspace(::Type{T}, start_n::Integer, stop_n::Integer, len::Integer, den::Integer) where T
610632
len < 2 && return _linspace1(T, start_n/den, stop_n/den, len)
611-
start_n == stop_n && return StepRangeLen(TwicePrecision{T}((start_n, den)), zero(TwicePrecision{T}), len)
633+
start_n == stop_n && return steprangelen_hp(T, (start_n, den), (zero(start_n), den), 0, len)
612634
tmin = -start_n/(Float64(stop_n) - Float64(start_n))
613635
imin = round(Int, tmin*(len-1)+1)
614636
imin = clamp(imin, 1, Int(len))
615637
ref_num = Int128(len-imin) * start_n + Int128(imin-1) * stop_n
616638
ref_denom = Int128(len-1) * den
617-
ref = TwicePrecision{T}((ref_num, ref_denom))
618-
step_full = TwicePrecision{T}((Int128(stop_n) - Int128(start_n), ref_denom))
619-
step = twiceprecision(step_full, nbitslen(T, len, imin))
620-
StepRangeLen(ref, step, Int(len), imin)
639+
ref = (ref_num, ref_denom)
640+
step_full = (Int128(stop_n) - Int128(start_n), ref_denom)
641+
steprangelen_hp(T, ref, step_full, nbitslen(T, len, imin), Int(len), imin)
621642
end
622643

623644
# For len < 2

test/ranges.jl

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -521,13 +521,13 @@ end
521521
for T = (Float32, Float64,),# BigFloat),
522522
a = -5:25, s = [-5:-1;1:25;], d = 1:25, n = -1:15
523523
denom = convert(T,d)
524-
start = convert(T,a)/denom
525-
step = convert(T,s)/denom
524+
strt = convert(T,a)/denom
525+
Δ = convert(T,s)/denom
526526
stop = convert(T,(a+(n-1)*s))/denom
527527
vals = T[a:s:a+(n-1)*s;]./denom
528-
r = start:step:stop
528+
r = strt:Δ:stop
529529
@test [r;] == vals
530-
@test [linspace(start, stop, length(r));] == vals
530+
@test [linspace(strt, stop, length(r));] == vals
531531
# issue #7420
532532
n = length(r)
533533
@test [r[1:n];] == [r;]
@@ -546,24 +546,24 @@ end
546546

547547
function range_fuzztests(::Type{T}, niter, nrange) where {T}
548548
for i = 1:niter, n in nrange
549-
start, Δ = randn(T), randn(T)
549+
strt, Δ = randn(T), randn(T)
550550
Δ == 0 && continue
551-
stop = start + (n-1)*Δ
552-
# `n` is not necessarily unique s.t. `start + (n-1)*Δ == stop`
553-
# so test that `length(start:Δ:stop)` satisfies this identity
554-
# and is the closest value to `(stop-start)/Δ` to do so
551+
stop = strt + (n-1)*Δ
552+
# `n` is not necessarily unique s.t. `strt + (n-1)*Δ == stop`
553+
# so test that `length(strt:Δ:stop)` satisfies this identity
554+
# and is the closest value to `(stop-strt)/Δ` to do so
555555
lo = hi = n
556-
while start + (lo-1)*Δ == stop; lo -= 1; end
557-
while start + (hi-1)*Δ == stop; hi += 1; end
558-
m = clamp(round(Int, (stop-start)/Δ) + 1, lo+1, hi-1)
559-
r = start:Δ:stop
556+
while strt + (lo-1)*Δ == stop; lo -= 1; end
557+
while strt + (hi-1)*Δ == stop; hi += 1; end
558+
m = clamp(round(Int, (stop-strt)/Δ) + 1, lo+1, hi-1)
559+
r = strt:Δ:stop
560560
@test m == length(r)
561-
@test start == first(r)
561+
@test strt == first(r)
562562
@test Δ == step(r)
563563
@test_skip stop == last(r)
564-
l = linspace(start,stop,n)
564+
l = linspace(strt,stop,n)
565565
@test n == length(l)
566-
@test start == first(l)
566+
@test strt == first(l)
567567
@test stop == last(l)
568568
end
569569
end

0 commit comments

Comments
 (0)