Skip to content

Commit 5143ab4

Browse files
committed
Make LinSpace generic and endpoint-preserving. Fixes #14420.
1 parent 5a2553f commit 5143ab4

File tree

8 files changed

+166
-132
lines changed

8 files changed

+166
-132
lines changed

base/abstractarray.jl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -783,9 +783,7 @@ map{T<:Real}(::Type{T}, r::StepRange) = T(r.start):T(r.step):T(last(r))
783783
map{T<:Real}(::Type{T}, r::UnitRange) = T(r.start):T(last(r))
784784
map{T<:AbstractFloat}(::Type{T}, r::FloatRange) = FloatRange(T(r.start), T(r.step), r.len, T(r.divisor))
785785
function map{T<:AbstractFloat}(::Type{T}, r::LinSpace)
786-
new_len = T(r.len)
787-
new_len == r.len || error("$r: too long for $T")
788-
LinSpace(T(r.start), T(r.stop), new_len, T(r.divisor))
786+
LinSpace(T(r.start), T(r.stop), length(r))
789787
end
790788

791789
## unsafe/pointer conversions ##

base/abstractarraymath.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ imag{T<:Real}(x::AbstractArray{T}) = zero(x)
100100

101101
\(A::Number, B::AbstractArray) = B ./ A
102102

103+
function lerpi(j::Integer, d::Integer, A::AbstractArray, B::AbstractArray)
104+
broadcast((a,b) -> lerpi(j, d, a, b), A, B)
105+
end
106+
107+
103108
# index A[:,:,...,i,:,:,...] where "i" is in dimension "d"
104109

105110
"""

base/float.jl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -722,9 +722,7 @@ for fn in (:float,:big)
722722
$fn(r::UnitRange) = $fn(r.start):$fn(last(r))
723723
$fn(r::FloatRange) = FloatRange($fn(r.start), $fn(r.step), r.len, $fn(r.divisor))
724724
function $fn(r::LinSpace)
725-
new_len = $fn(r.len)
726-
new_len == r.len || error(string(r, ": too long for ", $fn))
727-
LinSpace($fn(r.start), $fn(r.stop), new_len, $fn(r.divisor))
725+
LinSpace($fn(r.start), $fn(r.stop), length(r))
728726
end
729727
end
730728
end

base/mpfr.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -922,4 +922,9 @@ function Base.deepcopy_internal(x::BigFloat, stackdict::ObjectIdDict)
922922
return y
923923
end
924924

925+
function lerpi(j::Integer, d::Integer, a::BigFloat, b::BigFloat)
926+
t = BigFloat(j)/d
927+
fma(t, b, fma(-t, a, a))
928+
end
929+
925930
end #module

base/operators.jl

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -901,14 +901,8 @@ for f in (:+, :-)
901901
len = r1.len
902902
(len == r2.len ||
903903
throw(DimensionMismatch("argument dimensions must match")))
904-
divisor1, divisor2 = r1.divisor, r2.divisor
905-
if divisor1 == divisor2
906-
LinSpace{T}($f(r1.start, r2.start), $f(r1.stop, r2.stop),
907-
len, divisor1)
908-
else
909-
linspace(convert(T, $f(first(r1), first(r2))),
910-
convert(T, $f(last(r1), last(r2))), len)
911-
end
904+
linspace(convert(T, $f(first(r1), first(r2))),
905+
convert(T, $f(last(r1), last(r2))), len)
912906
end
913907

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

base/range.jl

Lines changed: 73 additions & 81 deletions
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)
@@ -450,11 +410,11 @@ end
450410
first{T}(r::OrdinalRange{T}) = convert(T, r.start)
451411
first{T}(r::OneTo{T}) = one(T)
452412
first{T}(r::FloatRange{T}) = convert(T, r.start/r.divisor)
453-
first{T}(r::LinSpace{T}) = convert(T, (r.len-1)*r.start/r.divisor)
413+
first(r::LinSpace) = r.start
454414

455415
last{T}(r::OrdinalRange{T}) = convert(T, r.stop)
456416
last{T}(r::FloatRange{T}) = convert(T, (r.start + (r.len-1)*r.step)/r.divisor)
457-
last{T}(r::LinSpace{T}) = convert(T, (r.len-1)*r.stop/r.divisor)
417+
last(r::LinSpace) = r.stop
458418

459419
minimum(r::AbstractUnitRange) = isempty(r) ? throw(ArgumentError("range must be non-empty")) : first(r)
460420
maximum(r::AbstractUnitRange) = isempty(r) ? throw(ArgumentError("range must be non-empty")) : last(r)
@@ -477,8 +437,10 @@ next{T}(r::FloatRange{T}, i::Int) =
477437

478438
start(r::LinSpace) = 1
479439
done(r::LinSpace, i::Int) = length(r) < i
480-
next{T}(r::LinSpace{T}, i::Int) =
481-
(convert(T, ((r.len-i)*r.start + (i-1)*r.stop)/r.divisor), i+1)
440+
function next(r::LinSpace, i::Int)
441+
@_inline_meta
442+
unsafe_getindex(r, i), i+1
443+
end
482444

483445
start(r::StepRange) = oftype(r.start + r.step, r.start)
484446
next{T}(r::StepRange{T}, i) = (convert(T,i), i+r.step)
@@ -536,10 +498,25 @@ function getindex{T}(r::FloatRange{T}, i::Integer)
536498
convert(T, (r.start + (i-1)*r.step)/r.divisor)
537499
end
538500

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

545522
getindex(r::Range, ::Colon) = copy(r)
@@ -581,11 +558,11 @@ end
581558
function getindex{T}(r::LinSpace{T}, s::OrdinalRange)
582559
@_inline_meta
583560
@boundscheck checkbounds(r, s)
584-
sl::T = length(s)
561+
sl = length(s)
585562
ifirst = first(s)
586563
ilast = last(s)
587-
vfirst::T = ((r.len - ifirst) * r.start + (ifirst - 1) * r.stop) / r.divisor
588-
vlast::T = ((r.len - ilast) * r.start + (ilast - 1) * r.stop) / r.divisor
564+
vfirst = unsafe_getindex(r, ifirst)
565+
vlast = unsafe_getindex(r, ilast)
589566
return linspace(vfirst, vlast, sl)
590567
end
591568

@@ -739,43 +716,58 @@ end
739716

740717
-(r::OrdinalRange) = range(-first(r), -step(r), length(r))
741718
-(r::FloatRange) = FloatRange(-r.start, -r.step, r.len, r.divisor)
742-
-(r::LinSpace) = LinSpace(-r.start, -r.stop, r.len, r.divisor)
719+
-(r::LinSpace) = LinSpace(-r.start, -r.stop, r.len)
743720

744721
.+(x::Real, r::AbstractUnitRange) = range(x + first(r), length(r))
745722
.+(x::Real, r::Range) = (x+first(r)):step(r):(x+last(r))
746723
#.+(x::Real, r::StepRange) = range(x + r.start, r.step, length(r))
747724
.+(x::Real, r::FloatRange) = FloatRange(r.divisor*x + r.start, r.step, r.len, r.divisor)
748-
function .+{T}(x::Real, r::LinSpace{T})
749-
x2 = x * r.divisor / (r.len - 1)
750-
LinSpace(x2 + r.start, x2 + r.stop, r.len, r.divisor)
725+
function .+(x::Real, r::LinSpace)
726+
LinSpace(x + r.start, x + r.stop, r.len)
727+
end
728+
function .+(x::Number, r::LinSpace)
729+
LinSpace(x + r.start, x + r.stop, r.len)
730+
end
731+
function .+{T}(x::Ref{T}, r::LinSpace{T})
732+
LinSpace(x + r.start, x + r.stop, r.len)
751733
end
752734
.+(r::Range, x::Real) = x + r
753735
#.+(r::FloatRange, x::Real) = x + r
754736

755737
.-(x::Real, r::Range) = (x-first(r)):-step(r):(x-last(r))
756738
.-(x::Real, r::FloatRange) = FloatRange(r.divisor*x - r.start, -r.step, r.len, r.divisor)
757739
function .-(x::Real, r::LinSpace)
758-
x2 = x * r.divisor / (r.len - 1)
759-
LinSpace(x2 - r.start, x2 - r.stop, r.len, r.divisor)
740+
LinSpace(x - r.start, x - r.stop, r.len)
741+
end
742+
function .-(x::Number, r::LinSpace)
743+
LinSpace(x - r.start, x - r.stop, r.len)
744+
end
745+
function .-{T}(x::Ref{T}, r::LinSpace{T})
746+
LinSpace(x - r.start, x - r.stop, r.len)
760747
end
761748
.-(r::AbstractUnitRange, x::Real) = range(first(r)-x, length(r))
762749
.-(r::StepRange , x::Real) = range(r.start-x, r.step, length(r))
763750
.-(r::FloatRange, x::Real) = FloatRange(r.start - r.divisor*x, r.step, r.len, r.divisor)
764751
function .-(r::LinSpace, x::Real)
765-
x2 = x * r.divisor / (r.len - 1)
766-
LinSpace(r.start - x2, r.stop - x2, r.len, r.divisor)
752+
LinSpace(r.start - x, r.stop - x, r.len)
753+
end
754+
function .-(r::LinSpace, x::Number)
755+
LinSpace(r.start - x, r.stop - x, r.len)
756+
end
757+
function .-{T}(r::LinSpace{T}, x::Ref{T})
758+
LinSpace(r.start - x, r.stop - x, r.len)
767759
end
768760

769761
.*(x::Real, r::OrdinalRange) = range(x*first(r), x*step(r), length(r))
770762
.*(x::Real, r::FloatRange) = FloatRange(x*r.start, x*r.step, r.len, r.divisor)
771-
.*(x::Real, r::LinSpace) = LinSpace(x * r.start, x * r.stop, r.len, r.divisor)
763+
.*(x::Real, r::LinSpace) = LinSpace(x * r.start, x * r.stop, r.len)
772764
.*(r::Range, x::Real) = x .* r
773765
.*(r::FloatRange, x::Real) = x .* r
774766
.*(r::LinSpace, x::Real) = x .* r
775767

776768
./(r::OrdinalRange, x::Real) = range(first(r)/x, step(r)/x, length(r))
777769
./(r::FloatRange, x::Real) = FloatRange(r.start/x, r.step/x, r.len, r.divisor)
778-
./(r::LinSpace, x::Real) = LinSpace(r.start / x, r.stop / x, r.len, r.divisor)
770+
./(r::LinSpace, x::Real) = LinSpace(r.start / x, r.stop / x, r.len)
779771

780772
promote_rule{T1,T2}(::Type{UnitRange{T1}},::Type{UnitRange{T2}}) =
781773
UnitRange{promote_type(T1,T2)}
@@ -820,20 +812,20 @@ promote_rule{T1,T2}(::Type{LinSpace{T1}},::Type{LinSpace{T2}}) =
820812
LinSpace{promote_type(T1,T2)}
821813
convert{T<:AbstractFloat}(::Type{LinSpace{T}}, r::LinSpace{T}) = r
822814
convert{T<:AbstractFloat}(::Type{LinSpace{T}}, r::LinSpace) =
823-
LinSpace{T}(r.start, r.stop, r.len, r.divisor)
815+
LinSpace{T}(r.start, r.stop, r.len)
824816

825817
promote_rule{F,OR<:OrdinalRange}(::Type{LinSpace{F}}, ::Type{OR}) =
826818
LinSpace{promote_type(F,eltype(OR))}
827819
convert{T<:AbstractFloat}(::Type{LinSpace{T}}, r::OrdinalRange) =
828-
linspace(convert(T, first(r)), convert(T, last(r)), convert(T, length(r)))
820+
linspace(convert(T, first(r)), convert(T, last(r)), length(r))
829821
convert{T}(::Type{LinSpace}, r::OrdinalRange{T}) =
830822
convert(LinSpace{typeof(float(first(r)))}, r)
831823

832824
# Promote FloatRange to LinSpace
833825
promote_rule{F,OR<:FloatRange}(::Type{LinSpace{F}}, ::Type{OR}) =
834826
LinSpace{promote_type(F,eltype(OR))}
835827
convert{T<:AbstractFloat}(::Type{LinSpace{T}}, r::FloatRange) =
836-
linspace(convert(T, first(r)), convert(T, last(r)), convert(T, length(r)))
828+
linspace(convert(T, first(r)), convert(T, last(r)), length(r))
837829
convert{T<:AbstractFloat}(::Type{LinSpace}, r::FloatRange{T}) =
838830
convert(LinSpace{T}, r)
839831

@@ -878,7 +870,7 @@ collect(r::Range) = vcat(r)
878870

879871
reverse(r::OrdinalRange) = colon(last(r), -step(r), first(r))
880872
reverse(r::FloatRange) = FloatRange(r.start + (r.len-1)*r.step, -r.step, r.len, r.divisor)
881-
reverse(r::LinSpace) = LinSpace(r.stop, r.start, r.len, r.divisor)
873+
reverse(r::LinSpace) = LinSpace(r.stop, r.start, length(r))
882874

883875
## sorting ##
884876

base/rational.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,3 +385,7 @@ end
385385
function ^{T<:Rational}(z::Complex{T}, n::Integer)
386386
n >= 0 ? power_by_squaring(z,n) : power_by_squaring(inv(z),-n)
387387
end
388+
389+
function lerpi(j::Integer, d::Integer, a::Rational, b::Rational)
390+
((d-j)*a)/d + (j*b)/d
391+
end

0 commit comments

Comments
 (0)