Skip to content

Commit db55bcb

Browse files
committed
Make LinSpace generic and endpoint-preserving. Fixes #14420.
1 parent ff9a949 commit db55bcb

8 files changed

+142
-91
lines changed

base/abstractarray.jl

+1-3
Original file line numberDiff line numberDiff line change
@@ -787,9 +787,7 @@ map{T<:Real}(::Type{T}, r::StepRange) = T(r.start):T(r.step):T(last(r))
787787
map{T<:Real}(::Type{T}, r::UnitRange) = T(r.start):T(last(r))
788788
map{T<:AbstractFloat}(::Type{T}, r::FloatRange) = FloatRange(T(r.start), T(r.step), r.len, T(r.divisor))
789789
function map{T<:AbstractFloat}(::Type{T}, r::LinSpace)
790-
new_len = T(r.len)
791-
new_len == r.len || error("$r: too long for $T")
792-
LinSpace(T(r.start), T(r.stop), new_len, T(r.divisor))
790+
LinSpace(T(r.start), T(r.stop), length(r))
793791
end
794792

795793
## unsafe/pointer conversions ##

base/abstractarraymath.jl

+4
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ imag{T<:Real}(x::AbstractArray{T}) = zero(x)
9292
+{T<:Number}(x::AbstractArray{T}) = x
9393
*{T<:Number}(x::AbstractArray{T,2}) = x
9494

95+
function lerpi(j::Integer, d::Integer, A::AbstractArray, B::AbstractArray)
96+
broadcast((a,b) -> lerpi(j, d, a, b), A, B)
97+
end
98+
9599
# index A[:,:,...,i,:,:,...] where "i" is in dimension "d"
96100

97101
"""

base/float.jl

+1-3
Original file line numberDiff line numberDiff line change
@@ -768,9 +768,7 @@ for fn in (:float,)
768768
$fn(r::UnitRange) = $fn(r.start):$fn(last(r))
769769
$fn(r::FloatRange) = FloatRange($fn(r.start), $fn(r.step), r.len, $fn(r.divisor))
770770
function $fn(r::LinSpace)
771-
new_len = $fn(r.len)
772-
new_len == r.len || error(string(r, ": too long for ", $fn))
773-
LinSpace($fn(r.start), $fn(r.stop), new_len, $fn(r.divisor))
771+
LinSpace($fn(r.start), $fn(r.stop), length(r))
774772
end
775773
end
776774
end

base/mpfr.jl

+5
Original file line numberDiff line numberDiff line change
@@ -965,4 +965,9 @@ function Base.deepcopy_internal(x::BigFloat, stackdict::ObjectIdDict)
965965
return y
966966
end
967967

968+
function lerpi(j::Integer, d::Integer, a::BigFloat, b::BigFloat)
969+
t = BigFloat(j)/d
970+
fma(t, b, fma(-t, a, a))
971+
end
972+
968973
end #module

base/operators.jl

+2-8
Original file line numberDiff line numberDiff line change
@@ -991,14 +991,8 @@ for f in (:+, :-)
991991
len = r1.len
992992
(len == r2.len ||
993993
throw(DimensionMismatch("argument dimensions must match")))
994-
divisor1, divisor2 = r1.divisor, r2.divisor
995-
if divisor1 == divisor2
996-
LinSpace{T}($f(r1.start, r2.start), $f(r1.stop, r2.stop),
997-
len, divisor1)
998-
else
999-
linspace(convert(T, $f(first(r1), first(r2))),
1000-
convert(T, $f(last(r1), last(r2))), len)
1001-
end
994+
linspace(convert(T, $f(first(r1), first(r2))),
995+
convert(T, $f(last(r1), last(r2))), len)
1002996
end
1003997

1004998
$f(r1::Union{FloatRange, OrdinalRange, LinSpace},

base/range.jl

+90-77
Original file line numberDiff line numberDiff line change
@@ -209,68 +209,29 @@ range(a::AbstractFloat, st::Real, len::Integer) = FloatRange(a, float(st), len,
209209

210210
## linspace and logspace
211211

212-
immutable LinSpace{T<:AbstractFloat} <: Range{T}
212+
immutable LinSpace{T} <: Range{T}
213213
start::T
214214
stop::T
215-
len::T
216-
divisor::T
217-
end
218-
219-
function linspace{T<:AbstractFloat}(start::T, stop::T, len::T)
220-
len == round(len) || throw(InexactError())
221-
0 <= len || error("linspace($start, $stop, $len): negative length")
222-
if len == 0
223-
n = convert(T, 2)
224-
if isinf(n*start) || isinf(n*stop)
225-
start /= n; stop /= n; n = one(T)
215+
len::Int
216+
lendiv::Int
217+
218+
function LinSpace(start,stop,len)
219+
len >= 0 || error("linspace($start, $stop, $len): negative length")
220+
if len == 1
221+
start == stop || error("linspace($start, $stop, $len): endpoints differ")
222+
return new(start, stop, 1, 1)
226223
end
227-
return LinSpace(-start, -stop, -one(T), n)
224+
new(start,stop,len,max(len-1,1))
228225
end
229-
if len == 1
230-
start == stop || error("linspace($start, $stop, $len): endpoints differ")
231-
return LinSpace(-start, -start, zero(T), one(T))
232-
end
233-
n = convert(T, len - 1)
234-
len - n == 1 || error("linspace($start, $stop, $len): too long for $T")
235-
a0, b = rat(start)
236-
a = convert(T,a0)
237-
if a/convert(T,b) == start
238-
c0, d = rat(stop)
239-
c = convert(T,c0)
240-
if c/convert(T,d) == stop
241-
e = lcm(b,d)
242-
a *= div(e,b)
243-
c *= div(e,d)
244-
s = convert(T,n*e)
245-
if isinf(a*n) || isinf(c*n)
246-
s, p = frexp(s)
247-
p2 = oftype(s,2)^p
248-
a /= p2; c /= p2
249-
end
250-
if a*n/s == start && c*n/s == stop
251-
return LinSpace(a, c, len, s)
252-
end
253-
end
254-
end
255-
a, c, s = start, stop, n
256-
if isinf(a*n) || isinf(c*n)
257-
s, p = frexp(s)
258-
p2 = oftype(s,2)^p
259-
a /= p2; c /= p2
260-
end
261-
if a*n/s == start && c*n/s == stop
262-
return LinSpace(a, c, len, s)
263-
end
264-
return LinSpace(start, stop, len, n)
265226
end
266-
function linspace{T<:AbstractFloat}(start::T, stop::T, len::Real)
267-
T_len = convert(T, len)
268-
T_len == len || throw(InexactError())
269-
linspace(start, stop, T_len)
227+
228+
function LinSpace(start, stop, len::Integer)
229+
T = typeof((stop-start)/len)
230+
LinSpace{T}(start, stop, len)
270231
end
271232

272233
"""
273-
linspace(start::Real, stop::Real, n::Real=50)
234+
linspace(start, stop, n=50)
274235
275236
Construct a range of `n` linearly spaced elements from `start` to `stop`.
276237
@@ -280,8 +241,7 @@ julia> linspace(1.3,2.9,9)
280241
1.3,1.5,1.7,1.9,2.1,2.3,2.5,2.7,2.9
281242
```
282243
"""
283-
linspace(start::Real, stop::Real, len::Real=50) =
284-
linspace(promote(AbstractFloat(start), AbstractFloat(stop))..., len)
244+
linspace(start, stop, len::Real=50) = LinSpace(start, stop, Int(len))
285245

286246
function show(io::IO, r::LinSpace)
287247
print(io, "linspace(")
@@ -398,7 +358,7 @@ julia> step(linspace(2.5,10.9,85))
398358
step(r::StepRange) = r.step
399359
step(r::AbstractUnitRange) = 1
400360
step(r::FloatRange) = r.step/r.divisor
401-
step{T}(r::LinSpace{T}) = ifelse(r.len <= 0, convert(T,NaN), (r.stop-r.start)/r.divisor)
361+
step(r::LinSpace) = (last(r)-first(r))/r.lendiv
402362

403363
unsafe_length(r::Range) = length(r) # generic fallback
404364

@@ -412,7 +372,7 @@ unsafe_length(r::OneTo) = r.stop
412372
length(r::AbstractUnitRange) = unsafe_length(r)
413373
length(r::OneTo) = unsafe_length(r)
414374
length(r::FloatRange) = Integer(r.len)
415-
length(r::LinSpace) = Integer(r.len + signbit(r.len - 1))
375+
length(r::LinSpace) = r.len
416376

417377
function length{T<:Union{Int,UInt,Int64,UInt64}}(r::StepRange{T})
418378
isempty(r) && return zero(T)
@@ -452,11 +412,11 @@ end
452412
first{T}(r::OrdinalRange{T}) = convert(T, r.start)
453413
first{T}(r::OneTo{T}) = one(T)
454414
first{T}(r::FloatRange{T}) = convert(T, r.start/r.divisor)
455-
first{T}(r::LinSpace{T}) = convert(T, (r.len-1)*r.start/r.divisor)
415+
first(r::LinSpace) = r.start
456416

457417
last{T}(r::OrdinalRange{T}) = convert(T, r.stop)
458418
last{T}(r::FloatRange{T}) = convert(T, (r.start + (r.len-1)*r.step)/r.divisor)
459-
last{T}(r::LinSpace{T}) = convert(T, (r.len-1)*r.stop/r.divisor)
419+
last(r::LinSpace) = r.stop
460420

461421
minimum(r::AbstractUnitRange) = isempty(r) ? throw(ArgumentError("range must be non-empty")) : first(r)
462422
maximum(r::AbstractUnitRange) = isempty(r) ? throw(ArgumentError("range must be non-empty")) : last(r)
@@ -479,8 +439,10 @@ next{T}(r::FloatRange{T}, i::Int) =
479439

480440
start(r::LinSpace) = 1
481441
done(r::LinSpace, i::Int) = length(r) < i
482-
next{T}(r::LinSpace{T}, i::Int) =
483-
(convert(T, ((r.len-i)*r.start + (i-1)*r.stop)/r.divisor), i+1)
442+
function next(r::LinSpace, i::Int)
443+
@_inline_meta
444+
unsafe_getindex(r, i), i+1
445+
end
484446

485447
start(r::StepRange) = oftype(r.start + r.step, r.start)
486448
next{T}(r::StepRange{T}, i) = (convert(T,i), i+r.step)
@@ -538,10 +500,25 @@ function getindex{T}(r::FloatRange{T}, i::Integer)
538500
convert(T, (r.start + (i-1)*r.step)/r.divisor)
539501
end
540502

541-
function getindex{T}(r::LinSpace{T}, i::Integer)
503+
function getindex(r::LinSpace, i::Integer)
542504
@_inline_meta
543505
@boundscheck checkbounds(r, i)
544-
convert(T, ((r.len-i)*r.start + (i-1)*r.stop)/r.divisor)
506+
unsafe_getindex(r, i)
507+
end
508+
509+
# This is separate to make it useful even when running with --check-bounds=yes
510+
function unsafe_getindex(r::LinSpace, i::Integer)
511+
d = r.lendiv
512+
j, a, b = ifelse(2i >= length(r), (i-1, r.start, r.stop), (length(r)-i, r.stop, r.start))
513+
lerpi(j, d, a, b)
514+
end
515+
516+
# High-precision interpolation. Accurate for t ∈ [0.5,1], so that 1-t is exact.
517+
function lerpi{T}(j::Integer, d::Integer, a::T, b::T)
518+
@_inline_meta
519+
t = j/d
520+
# computes (1-t)*a + t*b
521+
T(fma(t, b, fma(-t, a, a)))
545522
end
546523

547524
getindex(r::Range, ::Colon) = copy(r)
@@ -583,11 +560,11 @@ end
583560
function getindex{T}(r::LinSpace{T}, s::OrdinalRange)
584561
@_inline_meta
585562
@boundscheck checkbounds(r, s)
586-
sl::T = length(s)
563+
sl = length(s)
587564
ifirst = first(s)
588565
ilast = last(s)
589-
vfirst::T = ((r.len - ifirst) * r.start + (ifirst - 1) * r.stop) / r.divisor
590-
vlast::T = ((r.len - ilast) * r.start + (ilast - 1) * r.stop) / r.divisor
566+
vfirst = unsafe_getindex(r, ifirst)
567+
vlast = unsafe_getindex(r, ilast)
591568
return linspace(vfirst, vlast, sl)
592569
end
593570

@@ -741,18 +718,18 @@ end
741718

742719
-(r::OrdinalRange) = range(-first(r), -step(r), length(r))
743720
-(r::FloatRange) = FloatRange(-r.start, -r.step, r.len, r.divisor)
744-
-(r::LinSpace) = LinSpace(-r.start, -r.stop, r.len, r.divisor)
721+
-(r::LinSpace) = LinSpace(-r.start, -r.stop, r.len)
745722

746723
+(x::Real, r::AbstractUnitRange) = range(x + first(r), length(r))
747724
+(x::Real, r::Range) = (x+first(r)):step(r):(x+last(r))
748725
#+(x::Real, r::StepRange) = range(x + r.start, r.step, length(r))
749726
+(x::Real, r::FloatRange) = FloatRange(r.divisor*x + r.start, r.step, r.len, r.divisor)
750-
function +{T}(x::Real, r::LinSpace{T})
751-
x2 = x * r.divisor / (r.len - 1)
752-
LinSpace(x2 + r.start, x2 + r.stop, r.len, r.divisor)
727+
function +(x::Real, r::LinSpace)
728+
LinSpace(x + r.start, x + r.stop, r.len)
729+
end
730+
function +(x::Number, r::LinSpace)
731+
LinSpace(x + r.start, x + r.stop, r.len)
753732
end
754-
+(r::Range, x::Real) = x + r
755-
#+(r::FloatRange, x::Real) = x + r
756733

757734
-(x::Real, r::Range) = (x-first(r)):-step(r):(x-last(r))
758735
-(x::Real, r::FloatRange) = FloatRange(r.divisor*x - r.start, -r.step, r.len, r.divisor)
@@ -778,6 +755,42 @@ end
778755
/(r::OrdinalRange, x::Real) = range(first(r)/x, step(r)/x, length(r))
779756
/(r::FloatRange, x::Real) = FloatRange(r.start/x, r.step/x, r.len, r.divisor)
780757
/(r::LinSpace, x::Real) = LinSpace(r.start / x, r.stop / x, r.len, r.divisor)
758+
=======
759+
.-(x::Real, r::Range) = (x-first(r)):-step(r):(x-last(r))
760+
.-(x::Real, r::FloatRange) = FloatRange(r.divisor*x - r.start, -r.step, r.len, r.divisor)
761+
function .-(x::Real, r::LinSpace)
762+
LinSpace(x - r.start, x - r.stop, r.len)
763+
end
764+
function .-(x::Number, r::LinSpace)
765+
LinSpace(x - r.start, x - r.stop, r.len)
766+
end
767+
function .-{T}(x::Ref{T}, r::LinSpace{T})
768+
LinSpace(x - r.start, x - r.stop, r.len)
769+
end
770+
.-(r::AbstractUnitRange, x::Real) = range(first(r)-x, length(r))
771+
.-(r::StepRange , x::Real) = range(r.start-x, r.step, length(r))
772+
.-(r::FloatRange, x::Real) = FloatRange(r.start - r.divisor*x, r.step, r.len, r.divisor)
773+
function .-(r::LinSpace, x::Real)
774+
LinSpace(r.start - x, r.stop - x, r.len)
775+
end
776+
function .-(r::LinSpace, x::Number)
777+
LinSpace(r.start - x, r.stop - x, r.len)
778+
end
779+
function .-{T}(r::LinSpace{T}, x::Ref{T})
780+
LinSpace(r.start - x, r.stop - x, r.len)
781+
end
782+
783+
.*(x::Real, r::OrdinalRange) = range(x*first(r), x*step(r), length(r))
784+
.*(x::Real, r::FloatRange) = FloatRange(x*r.start, x*r.step, r.len, r.divisor)
785+
.*(x::Real, r::LinSpace) = LinSpace(x * r.start, x * r.stop, r.len)
786+
.*(r::Range, x::Real) = x .* r
787+
.*(r::FloatRange, x::Real) = x .* r
788+
.*(r::LinSpace, x::Real) = x .* r
789+
790+
./(r::OrdinalRange, x::Real) = range(first(r)/x, step(r)/x, length(r))
791+
./(r::FloatRange, x::Real) = FloatRange(r.start/x, r.step/x, r.len, r.divisor)
792+
./(r::LinSpace, x::Real) = LinSpace(r.start / x, r.stop / x, r.len)
793+
>>>>>>> 6f27d2b... Make LinSpace generic and endpoint-preserving. Fixes #14420.
781794

782795
promote_rule{T1,T2}(::Type{UnitRange{T1}},::Type{UnitRange{T2}}) =
783796
UnitRange{promote_type(T1,T2)}
@@ -822,20 +835,20 @@ promote_rule{T1,T2}(::Type{LinSpace{T1}},::Type{LinSpace{T2}}) =
822835
LinSpace{promote_type(T1,T2)}
823836
convert{T<:AbstractFloat}(::Type{LinSpace{T}}, r::LinSpace{T}) = r
824837
convert{T<:AbstractFloat}(::Type{LinSpace{T}}, r::LinSpace) =
825-
LinSpace{T}(r.start, r.stop, r.len, r.divisor)
838+
LinSpace{T}(r.start, r.stop, r.len)
826839

827840
promote_rule{F,OR<:OrdinalRange}(::Type{LinSpace{F}}, ::Type{OR}) =
828841
LinSpace{promote_type(F,eltype(OR))}
829842
convert{T<:AbstractFloat}(::Type{LinSpace{T}}, r::OrdinalRange) =
830-
linspace(convert(T, first(r)), convert(T, last(r)), convert(T, length(r)))
843+
linspace(convert(T, first(r)), convert(T, last(r)), length(r))
831844
convert{T}(::Type{LinSpace}, r::OrdinalRange{T}) =
832845
convert(LinSpace{typeof(float(first(r)))}, r)
833846

834847
# Promote FloatRange to LinSpace
835848
promote_rule{F,OR<:FloatRange}(::Type{LinSpace{F}}, ::Type{OR}) =
836849
LinSpace{promote_type(F,eltype(OR))}
837850
convert{T<:AbstractFloat}(::Type{LinSpace{T}}, r::FloatRange) =
838-
linspace(convert(T, first(r)), convert(T, last(r)), convert(T, length(r)))
851+
linspace(convert(T, first(r)), convert(T, last(r)), length(r))
839852
convert{T<:AbstractFloat}(::Type{LinSpace}, r::FloatRange{T}) =
840853
convert(LinSpace{T}, r)
841854

@@ -877,7 +890,7 @@ collect(r::Range) = vcat(r)
877890

878891
reverse(r::OrdinalRange) = colon(last(r), -step(r), first(r))
879892
reverse(r::FloatRange) = FloatRange(r.start + (r.len-1)*r.step, -r.step, r.len, r.divisor)
880-
reverse(r::LinSpace) = LinSpace(r.stop, r.start, r.len, r.divisor)
893+
reverse(r::LinSpace) = LinSpace(r.stop, r.start, length(r))
881894

882895
## sorting ##
883896

base/rational.jl

+4
Original file line numberDiff line numberDiff line change
@@ -398,3 +398,7 @@ end
398398
function ^{T<:Rational}(z::Complex{T}, n::Integer)
399399
n >= 0 ? power_by_squaring(z,n) : power_by_squaring(inv(z),-n)
400400
end
401+
402+
function lerpi(j::Integer, d::Integer, a::Rational, b::Rational)
403+
((d-j)*a)/d + (j*b)/d
404+
end

test/ranges.jl

+35
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# This file is a part of Julia. License is MIT: http://julialang.org/license
22

3+
# Test whether r[i] has full precision
4+
function test_linspace{T<:AbstractFloat}(r::LinSpace{T})
5+
isempty(r) && return nothing
6+
n = length(r)
7+
f, l = first(r), last(r)
8+
a, b, d = BigFloat(f), BigFloat(l), max(n-1,1)
9+
Δ = max(eps(f), eps(l))
10+
for i = 1:n
11+
c = b*(BigFloat(i-1)/d) + a*(BigFloat(d-i+1)/d)
12+
@test abs(r[i]-T(c)) <= Δ
13+
end
14+
nothing
15+
end
16+
317
# ranges
418
@test size(10:1:0) == (0,)
519
@test length(1:.2:2) == 6
@@ -786,3 +800,24 @@ io = IOBuffer()
786800
show(io, r)
787801
str = String(take!(io))
788802
@test str == "Base.OneTo(3)"
803+
804+
# linspace of other types
805+
r = linspace(0, 3//10, 4)
806+
@test eltype(r) == Rational{Int}
807+
@test r[2] === 1//10
808+
809+
a, b = 1.0, nextfloat(1.0)
810+
ba, bb = BigFloat(a), BigFloat(b)
811+
r = linspace(ba, bb, 3)
812+
@test eltype(r) == BigFloat
813+
@test r[1] == a && r[3] == b
814+
@test r[2] == (ba+bb)/2
815+
816+
a, b = rand(10), rand(10)
817+
ba, bb = big(a), big(b)
818+
r = linspace(a, b, 5)
819+
@test r[1] == a && r[5] == b
820+
for i = 2:4
821+
x = ((5-i)//4)*ba + ((i-1)//4)*bb
822+
@test r[i] == Float64.(x)
823+
end

0 commit comments

Comments
 (0)