From d6866bec5762cab228d253b542cc126e160d70fc Mon Sep 17 00:00:00 2001 From: Stefan Karpinski Date: Wed, 7 Jan 2015 11:00:36 -0500 Subject: [PATCH 01/13] linspace: "lift" linspace the way float ranges are [close #9637] --- base/array.jl | 25 ++++++++++++++++++++++++- test/ranges.jl | 4 +++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/base/array.jl b/base/array.jl index 55f464964ab1b..56926ba21d1c8 100644 --- a/base/array.jl +++ b/base/array.jl @@ -258,13 +258,36 @@ function linspace(start::Real, stop::Real, n::Integer) end n -= 1 S = promote_type(T, Float64) - for i=0:n + for i = 0:n a[i+1] = start*(convert(S, (n-i))/n) + stop*(convert(S, i)/n) end a end linspace(start::Real, stop::Real) = linspace(start, stop, 100) +function linspace{T<:FloatingPoint}(start::T, stop::T, n::Int) + n == 1 && return [start] + n -= 1 + a0, b = rat(start) + a = convert(T,a0) + if a/convert(T,b) == start + c0, d = rat(stop) + c = convert(T,c0) + if c/convert(T,d) == stop + e = lcm(b,d) + a *= div(e,b) + c *= div(e,d) + ne = convert(T,n*e) + if a*n/ne == start && c*n/ne == stop + return [ (a*(n-k) + c*k)/ne for k=0:n ] + end + end + end + return [ start*((n-k)/n) + stop*(k/n) for k=0:n ] +end +linspace(start::FloatingPoint, stop::FloatingPoint, n::Integer) = + linspace(promote(start, stop)..., Int(n)) + logspace(start::Real, stop::Real, n::Integer) = 10.^linspace(start, stop, n) logspace(start::Real, stop::Real) = logspace(start, stop, 50) diff --git a/test/ranges.jl b/test/ranges.jl index 3bc5f301558ef..96f0b6d03f3ba 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -272,8 +272,10 @@ for T = (Float32, Float64,),# BigFloat), start = convert(T,a)/den step = convert(T,s)/den stop = convert(T,(a+(n-1)*s))/den + vals = T[a:s:a+(n-1)*s;]./den r = start:step:stop - @test [r;] == T[a:s:a+(n-1)*s;]./den + @test [r;] == vals + @test linspace(start, stop, n) == vals # issue #7420 n = length(r) @test [r[1:n];] == [r;] From b7df3ad2dd981ffc2e129fa6c73175abecdf60dc Mon Sep 17 00:00:00 2001 From: Stefan Karpinski Date: Wed, 7 Jan 2015 15:30:10 -0500 Subject: [PATCH 02/13] linspace: add "handcrafted" linspace test cases. --- test/ranges.jl | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/test/ranges.jl b/test/ranges.jl index 96f0b6d03f3ba..1982de1fbc594 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -232,22 +232,22 @@ end # tricky floating-point ranges -@test [0.1:0.1:0.3;] == [1:3;]./10 -@test [0.0:0.1:0.3;] == [0:3;]./10 -@test [0.3:-0.1:-0.1;] == [3:-1:-1;]./10 -@test [0.1:-0.1:-0.3;] == [1:-1:-3;]./10 -@test [0.0:0.1:1.0;] == [0:10;]./10 -@test [0.0:-0.1:1.0;] == [] -@test [0.0:0.1:-1.0;] == [] -@test [0.0:-0.1:-1.0;] == [0:-1:-10;]./10 -@test [1.0:1/49:27.0;] == [49:1323;]./49 -@test [0.0:0.7:2.1;] == [0:7:21;]./10 -@test [0.0:1.1:3.3;] == [0:11:33;]./10 -@test [0.1:1.1:3.4;] == [1:11:34;]./10 -@test [0.0:1.3:3.9;] == [0:13:39;]./10 -@test [0.1:1.3:4.0;] == [1:13:40;]./10 -@test [1.1:1.1:3.3;] == [11:11:33;]./10 -@test [0.3:0.1:1.1;] == [3:1:11;]./10 +@test [0.1:0.1:0.3;] == linspace(0.1,0.3,3) == [1:3;]./10 +@test [0.0:0.1:0.3;] == linspace(0.0,0.3,4) == [0:3;]./10 +@test [0.3:-0.1:-0.1;] == linspace(0.3,-0.1,5) == [3:-1:-1;]./10 +@test [0.1:-0.1:-0.3;] == linspace(0.1,-0.3,5) == [1:-1:-3;]./10 +@test [0.0:0.1:1.0;] == linspace(0.0,1.0,11) == [0:10;]./10 +@test [0.0:-0.1:1.0;] == linspace(0.0,1.0,0) == [] +@test [0.0:0.1:-1.0;] == linspace(0.0,-1.0,0) == [] +@test [0.0:-0.1:-1.0;] == linspace(0.0,-1.0,11) == [0:-1:-10;]./10 +@test [1.0:1/49:27.0;] == linspace(1.0,27.0,1275) == [49:1323;]./49 +@test [0.0:0.7:2.1;] == linspace(0.0,2.1,4) == [0:7:21;]./10 +@test [0.0:1.1:3.3;] == linspace(0.0,3.3,4) == [0:11:33;]./10 +@test [0.1:1.1:3.4;] == linspace(0.1,3.4,4) == [1:11:34;]./10 +@test [0.0:1.3:3.9;] == linspace(0.0,3.9,4) == [0:13:39;]./10 +@test [0.1:1.3:4.0;] == linspace(0.1,4.0,4) == [1:13:40;]./10 +@test [1.1:1.1:3.3;] == linspace(1.1,3.3,3) == [11:11:33;]./10 +@test [0.3:0.1:1.1;] == linspace(0.3,1.1,9) == [3:1:11;]./10 @test [0.0:1.0:5.5;] == [0:10:55;]./10 @test [0.0:-1.0:0.5;] == [] From 42db28565c1a44e9eb73eb9b1b61be64d3eb9ac9 Mon Sep 17 00:00:00 2001 From: Stefan Karpinski Date: Tue, 10 Mar 2015 17:15:59 -0400 Subject: [PATCH 03/13] LinSpace: make linspace return a range-like type. --- base/array.jl | 45 -------------------------------- base/deprecated.jl | 2 ++ base/exports.jl | 1 + base/range.jl | 65 ++++++++++++++++++++++++++++++++++++++++------ test/ranges.jl | 40 ++++++++++++++-------------- 5 files changed, 80 insertions(+), 73 deletions(-) diff --git a/base/array.jl b/base/array.jl index 56926ba21d1c8..b61035ac137dc 100644 --- a/base/array.jl +++ b/base/array.jl @@ -246,51 +246,6 @@ function one{T}(x::AbstractMatrix{T}) eye(T, m) end -linspace(start::Integer, stop::Integer, n::Integer) = - linspace(float(start), float(stop), n) -function linspace(start::Real, stop::Real, n::Integer) - (start, stop) = promote(start, stop) - T = typeof(start) - a = Array(T, Int(n)) - if n == 1 - a[1] = start - return a - end - n -= 1 - S = promote_type(T, Float64) - for i = 0:n - a[i+1] = start*(convert(S, (n-i))/n) + stop*(convert(S, i)/n) - end - a -end -linspace(start::Real, stop::Real) = linspace(start, stop, 100) - -function linspace{T<:FloatingPoint}(start::T, stop::T, n::Int) - n == 1 && return [start] - n -= 1 - a0, b = rat(start) - a = convert(T,a0) - if a/convert(T,b) == start - c0, d = rat(stop) - c = convert(T,c0) - if c/convert(T,d) == stop - e = lcm(b,d) - a *= div(e,b) - c *= div(e,d) - ne = convert(T,n*e) - if a*n/ne == start && c*n/ne == stop - return [ (a*(n-k) + c*k)/ne for k=0:n ] - end - end - end - return [ start*((n-k)/n) + stop*(k/n) for k=0:n ] -end -linspace(start::FloatingPoint, stop::FloatingPoint, n::Integer) = - linspace(promote(start, stop)..., Int(n)) - -logspace(start::Real, stop::Real, n::Integer) = 10.^linspace(start, stop, n) -logspace(start::Real, stop::Real) = logspace(start, stop, 50) - ## Conversions ## convert{T,n}(::Type{Array{T}}, x::Array{T,n}) = x diff --git a/base/deprecated.jl b/base/deprecated.jl index 4b01ea608c8ee..d64330fe820e5 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -522,3 +522,5 @@ export float32_isvalid, float64_isvalid @deprecate parseint(s,base) parse(Int, s, base) @deprecate parseint(T::Type, s) parse(T, s) @deprecate parseint(T::Type, s, base) parse(T, s, base) + +@deprecate linrange linspace diff --git a/base/exports.jl b/base/exports.jl index de9a9548cadb9..ae6f6511f15bb 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -64,6 +64,7 @@ export IO, IOBuffer, IOStream, + LinSpace, LocalProcess, LowerTriangular, MathConst, diff --git a/base/range.jl b/base/range.jl index 0f92c8ed42ed3..904b9c82405b2 100644 --- a/base/range.jl +++ b/base/range.jl @@ -163,10 +163,44 @@ range(a::FloatingPoint, st::FloatingPoint, len::Integer) = FloatRange(a,st,len,o range(a::Real, st::FloatingPoint, len::Integer) = FloatRange(float(a), st, len, one(st)) range(a::FloatingPoint, st::Real, len::Integer) = FloatRange(a, float(st), len, one(a)) -linrange(a::Real, b::Real, len::Integer) = - len >= 2 ? range(a, (b-a)/(len-1), len) : - len == 1 && a == b ? range(a, zero((b-a)/(len-1)), 1) : - throw(ArgumentError("invalid range length")) +## linspace and logspace + +immutable LinSpace{T<:FloatingPoint} <: Range{T} + start::T + stop::T + len::Int + divisor::T +end +LinSpace(start::FloatingPoint, stop::FloatingPoint, len::Real, divisor::Real) = + LinSpace{promote_type(typeof(start),typeof(stop),typeof(divisor))}(start,stop,len,divisor) + +function linspace{T<:FloatingPoint}(start::T, stop::T, len::Int) + 0 <= len || error("invalid length: linspace($start, $stop, $len)") + 1 != len || start == stop || error("start != stop with len == 1: linspace($start, $stop, $len)") + n = len - 1 + a0, b = rat(start) + a = convert(T,a0) + if 0 < n && a/convert(T,b) == start + c0, d = rat(stop) + c = convert(T,c0) + if c/convert(T,d) == stop + e = lcm(b,d) + a *= div(e,b) + c *= div(e,d) + ne = convert(T,n*e) + if a*n/ne == start && c*n/ne == stop + return LinSpace(a, c, len, ne) + end + end + end + return LinSpace(start, stop, len, max(1,n)) +end +linspace(start::Real, stop::Real, len::Real) = + linspace(promote(FloatingPoint(start), FloatingPoint(stop))..., Int(len)) + +show(io::IO, r::LinSpace) = print(io, "linspace($(first(r)),$(last(r)),$(length(r)))") + +logspace(start::Real, stop::Real, n::Integer=50) = 10.^linspace(start, stop, n) ## interface implementations @@ -178,11 +212,13 @@ size(r::Range) = (length(r),) isempty(r::StepRange) = (r.start != r.stop) & ((r.step > zero(r.step)) != (r.stop > r.start)) isempty(r::UnitRange) = r.start > r.stop -isempty(r::FloatRange) = length(r)==0 +isempty(r::FloatRange) = length(r) == 0 +isempty(r::LinSpace) = length(r) == 0 step(r::StepRange) = r.step step(r::UnitRange) = 1 step(r::FloatRange) = r.step/r.divisor +step(r::LinSpace) = (r.stop - r.start)/r.divisor function length(r::StepRange) n = Integer(div(r.stop+r.step - r.start, r.step)) @@ -190,6 +226,7 @@ function length(r::StepRange) end length(r::UnitRange) = Integer(r.stop - r.start + 1) length(r::FloatRange) = Integer(r.len) +length(r::LinSpace) = r.len function length{T<:Union(Int,UInt,Int64,UInt64)}(r::StepRange{T}) isempty(r) && return zero(T) @@ -220,11 +257,13 @@ let smallint = (Int === Int64 ? end first{T}(r::OrdinalRange{T}) = convert(T, r.start) -first(r::FloatRange) = r.start/r.divisor +first{T}(r::FloatRange{T}) = convert(T, r.start/r.divisor) +first{T}(r::LinSpace{T}) = convert(T, (r.len-1)*r.start/r.divisor) last{T}(r::StepRange{T}) = r.stop last(r::UnitRange) = r.stop last{T}(r::FloatRange{T}) = convert(T, (r.start + (r.len-1)*r.step)/r.divisor) +last{T}(r::LinSpace{T}) = convert(T, (r.len-1)*r.stop/r.divisor) minimum(r::UnitRange) = isempty(r) ? throw(ArgumentError("range must be non-empty")) : first(r) maximum(r::UnitRange) = isempty(r) ? throw(ArgumentError("range must be non-empty")) : last(r) @@ -241,8 +280,14 @@ copy(r::Range) = r ## iteration start(r::FloatRange) = 0 -next{T}(r::FloatRange{T}, i::Int) = (convert(T, (r.start + i*r.step)/r.divisor), i+1) -done(r::FloatRange, i::Int) = (length(r) <= i) +done(r::FloatRange, i::Int) = length(r) <= i +next{T}(r::FloatRange{T}, i::Int) = + (convert(T, (r.start + i*r.step)/r.divisor), i+1) + +start(r::LinSpace) = 1 +done(r::LinSpace, i::Int) = length(r) < i +next{T}(r::LinSpace{T}, i::Int) = + (convert(T, ((r.len-i)*r.start + (i-1)*r.stop)/r.divisor), i+1) # NOTE: For ordinal ranges, we assume start+step might be from a # lifted domain (e.g. Int8+Int8 => Int); use that for iterating. @@ -268,6 +313,10 @@ function getindex{T}(r::FloatRange{T}, i::Integer) 1 <= i <= length(r) || throw(BoundsError()) convert(T, (r.start + (i-1)*r.step)/r.divisor) end +function getindex{T}(r::LinSpace{T}, i::Integer) + 1 <= i <= length(r) || throw(BoundsError()) + convert(T, ((r.len-i)*r.start + (i-1)*r.stop)/r.divisor) +end function check_indexingrange(s, r) sl = length(s) diff --git a/test/ranges.jl b/test/ranges.jl index 1982de1fbc594..1e64ff00363c4 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -232,22 +232,22 @@ end # tricky floating-point ranges -@test [0.1:0.1:0.3;] == linspace(0.1,0.3,3) == [1:3;]./10 -@test [0.0:0.1:0.3;] == linspace(0.0,0.3,4) == [0:3;]./10 -@test [0.3:-0.1:-0.1;] == linspace(0.3,-0.1,5) == [3:-1:-1;]./10 -@test [0.1:-0.1:-0.3;] == linspace(0.1,-0.3,5) == [1:-1:-3;]./10 -@test [0.0:0.1:1.0;] == linspace(0.0,1.0,11) == [0:10;]./10 -@test [0.0:-0.1:1.0;] == linspace(0.0,1.0,0) == [] -@test [0.0:0.1:-1.0;] == linspace(0.0,-1.0,0) == [] -@test [0.0:-0.1:-1.0;] == linspace(0.0,-1.0,11) == [0:-1:-10;]./10 -@test [1.0:1/49:27.0;] == linspace(1.0,27.0,1275) == [49:1323;]./49 -@test [0.0:0.7:2.1;] == linspace(0.0,2.1,4) == [0:7:21;]./10 -@test [0.0:1.1:3.3;] == linspace(0.0,3.3,4) == [0:11:33;]./10 -@test [0.1:1.1:3.4;] == linspace(0.1,3.4,4) == [1:11:34;]./10 -@test [0.0:1.3:3.9;] == linspace(0.0,3.9,4) == [0:13:39;]./10 -@test [0.1:1.3:4.0;] == linspace(0.1,4.0,4) == [1:13:40;]./10 -@test [1.1:1.1:3.3;] == linspace(1.1,3.3,3) == [11:11:33;]./10 -@test [0.3:0.1:1.1;] == linspace(0.3,1.1,9) == [3:1:11;]./10 +@test [0.1:0.1:0.3;] == [linspace(0.1,0.3,3);] == [1:3;]./10 +@test [0.0:0.1:0.3;] == [linspace(0.0,0.3,4);] == [0:3;]./10 +@test [0.3:-0.1:-0.1;] == [linspace(0.3,-0.1,5);] == [3:-1:-1;]./10 +@test [0.1:-0.1:-0.3;] == [linspace(0.1,-0.3,5);] == [1:-1:-3;]./10 +@test [0.0:0.1:1.0;] == [linspace(0.0,1.0,11);] == [0:10;]./10 +@test [0.0:-0.1:1.0;] == [linspace(0.0,1.0,0);] == [] +@test [0.0:0.1:-1.0;] == [linspace(0.0,-1.0,0);] == [] +@test [0.0:-0.1:-1.0;] == [linspace(0.0,-1.0,11);] == [0:-1:-10;]./10 +@test [1.0:1/49:27.0;] == [linspace(1.0,27.0,1275);] == [49:1323;]./49 +@test [0.0:0.7:2.1;] == [linspace(0.0,2.1,4);] == [0:7:21;]./10 +@test [0.0:1.1:3.3;] == [linspace(0.0,3.3,4);] == [0:11:33;]./10 +@test [0.1:1.1:3.4;] == [linspace(0.1,3.4,4);] == [1:11:34;]./10 +@test [0.0:1.3:3.9;] == [linspace(0.0,3.9,4);] == [0:13:39;]./10 +@test [0.1:1.3:4.0;] == [linspace(0.1,4.0,4);] == [1:13:40;]./10 +@test [1.1:1.1:3.3;] == [linspace(1.1,3.3,3);] == [11:11:33;]./10 +@test [0.3:0.1:1.1;] == [linspace(0.3,1.1,9);] == [3:1:11;]./10 @test [0.0:1.0:5.5;] == [0:10:55;]./10 @test [0.0:-1.0:0.5;] == [] @@ -275,7 +275,7 @@ for T = (Float32, Float64,),# BigFloat), vals = T[a:s:a+(n-1)*s;]./den r = start:step:stop @test [r;] == vals - @test linspace(start, stop, n) == vals + n == 1 || @test [linspace(start, stop, length(r));] == vals # issue #7420 n = length(r) @test [r[1:n];] == [r;] @@ -353,13 +353,13 @@ r = -0.004532318104333742:1.2597349521122731e-5:0.008065031416788989 @test_throws BoundsError r[0:10] @test_throws BoundsError r[1:10000] -r = linrange(1/3,5/7,6) +r = linspace(1/3,5/7,6) @test length(r) == 6 @test r[1] == 1/3 @test abs(r[end] - 5/7) <= eps(5/7) -r = linrange(0.25,0.25,1) +r = linspace(0.25,0.25,1) @test length(r) == 1 -@test_throws Exception linrange(0.25,0.5,1) +@test_throws Exception linspace(0.25,0.5,1) # issue #7426 @test [typemax(Int):1:typemax(Int);] == [typemax(Int)] From 83f862a3c721b0303c21a6020d5ec9c0e545d2db Mon Sep 17 00:00:00 2001 From: Stefan Karpinski Date: Wed, 25 Mar 2015 17:22:08 +0100 Subject: [PATCH 04/13] LinSpace: improve length 0 and 1 corner cases. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The length zero and one cases for LinSpace are really weird – arguably they simply shouldn't be allowed at all. However, I think for usability we really need to support these. This change uses -1 and 0 as special values of the r.len field, encoding, somewhat counterintuitively, real lengths of 0 and 1, respectively. The reason for this is so that the element access computations for the length one case has r.len-1 == -1 and only flips sign, avoiding overflow when r.start is very large. In the length zero case, r.len-1 == -2, so we need to divide by -2 to get correct first and last values back. This is susceptible to overflow, but since these values are kind of nonsense in the zero case, this seems to be a preferrable situation. There are still cases where the (r.len-i)*r.start + (i-1)*r.stop can overflow for longer ranges, and it's unclear to me whether you can handle those correctly with the same LinSpace type. This plus the complexity of handling corner cases like lengths zero and one, makes me wonder if it's really a good idea to make this a type at all. --- base/range.jl | 14 ++++++++++---- test/ranges.jl | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/base/range.jl b/base/range.jl index 904b9c82405b2..d95b557aab875 100644 --- a/base/range.jl +++ b/base/range.jl @@ -175,8 +175,14 @@ LinSpace(start::FloatingPoint, stop::FloatingPoint, len::Real, divisor::Real) = LinSpace{promote_type(typeof(start),typeof(stop),typeof(divisor))}(start,stop,len,divisor) function linspace{T<:FloatingPoint}(start::T, stop::T, len::Int) - 0 <= len || error("invalid length: linspace($start, $stop, $len)") - 1 != len || start == stop || error("start != stop with len == 1: linspace($start, $stop, $len)") + 0 <= len || error("linspace($start, $stop, $len): negative length") + if len == 0 + return LinSpace(-start, -stop, -1, convert(T,2)) + end + if len == 1 + start == stop || error("linspace($start, $stop, $len): endpoints differ") + return LinSpace(-start, -start, 0, one(T)) + end n = len - 1 a0, b = rat(start) a = convert(T,a0) @@ -218,7 +224,7 @@ isempty(r::LinSpace) = length(r) == 0 step(r::StepRange) = r.step step(r::UnitRange) = 1 step(r::FloatRange) = r.step/r.divisor -step(r::LinSpace) = (r.stop - r.start)/r.divisor +step{T}(r::LinSpace{T}) = ifelse(r.len <= 0, convert(T,NaN), (r.stop-r.start)/r.divisor) function length(r::StepRange) n = Integer(div(r.stop+r.step - r.start, r.step)) @@ -226,7 +232,7 @@ function length(r::StepRange) end length(r::UnitRange) = Integer(r.stop - r.start + 1) length(r::FloatRange) = Integer(r.len) -length(r::LinSpace) = r.len +length(r::LinSpace) = r.len + signbit(r.len - 1) function length{T<:Union(Int,UInt,Int64,UInt64)}(r::StepRange{T}) isempty(r) && return zero(T) diff --git a/test/ranges.jl b/test/ranges.jl index 1e64ff00363c4..9caa9e4bf1809 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -275,7 +275,7 @@ for T = (Float32, Float64,),# BigFloat), vals = T[a:s:a+(n-1)*s;]./den r = start:step:stop @test [r;] == vals - n == 1 || @test [linspace(start, stop, length(r));] == vals + @test [linspace(start, stop, length(r));] == vals # issue #7420 n = length(r) @test [r[1:n];] == [r;] From ccc6798beaf6bf09f53f92298c8401febc591990 Mon Sep 17 00:00:00 2001 From: Stefan Karpinski Date: Wed, 25 Mar 2015 18:50:44 +0100 Subject: [PATCH 05/13] linspace: oops, forgot the ridiculous, arbitrary default of 50. --- base/range.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/range.jl b/base/range.jl index d95b557aab875..2224862ac6a5c 100644 --- a/base/range.jl +++ b/base/range.jl @@ -201,7 +201,7 @@ function linspace{T<:FloatingPoint}(start::T, stop::T, len::Int) end return LinSpace(start, stop, len, max(1,n)) end -linspace(start::Real, stop::Real, len::Real) = +linspace(start::Real, stop::Real, len::Real=50) = linspace(promote(FloatingPoint(start), FloatingPoint(stop))..., Int(len)) show(io::IO, r::LinSpace) = print(io, "linspace($(first(r)),$(last(r)),$(length(r)))") From 4fa535c9f95d40e84a86176d598f26aec19678e9 Mon Sep 17 00:00:00 2001 From: Stefan Karpinski Date: Thu, 26 Mar 2015 14:39:08 +0100 Subject: [PATCH 06/13] linspace: "center" computations to handle extreme ranges correctly. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This forces the LinSpace divisor – either n*e or just n – to be in the range [1/2,0), which, in particular, prevents overflow even if the old computation could overflow. Needs more testing of corner cases, but this passes all of the existing tests at least. --- base/range.jl | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/base/range.jl b/base/range.jl index 2224862ac6a5c..a2451ca7c8b0f 100644 --- a/base/range.jl +++ b/base/range.jl @@ -186,20 +186,25 @@ function linspace{T<:FloatingPoint}(start::T, stop::T, len::Int) n = len - 1 a0, b = rat(start) a = convert(T,a0) - if 0 < n && a/convert(T,b) == start + if a/convert(T,b) == start c0, d = rat(stop) c = convert(T,c0) if c/convert(T,d) == stop e = lcm(b,d) a *= div(e,b) c *= div(e,d) - ne = convert(T,n*e) - if a*n/ne == start && c*n/ne == stop - return LinSpace(a, c, len, ne) + s, p = frexp(convert(T,n*e)) + p = one(p) << p + a /= p; c /= p + if a*n/s == start && c*n/s == stop + return LinSpace(a, c, len, s) end end end - return LinSpace(start, stop, len, max(1,n)) + s, p = frexp(convert(T,n)) + p = one(p) << p + start /= p; stop /= p + return LinSpace(start, stop, len, s) end linspace(start::Real, stop::Real, len::Real=50) = linspace(promote(FloatingPoint(start), FloatingPoint(stop))..., Int(len)) From a823c91ec237b3fd382b0111335e3e90faf1ed6d Mon Sep 17 00:00:00 2001 From: Stefan Karpinski Date: Mon, 30 Mar 2015 18:47:56 -0400 Subject: [PATCH 07/13] linspace: handle very large *and* very small endpoints correctly. --- base/range.jl | 18 ++++++++++++------ test/ranges.jl | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/base/range.jl b/base/range.jl index a2451ca7c8b0f..e5d085f6e609e 100644 --- a/base/range.jl +++ b/base/range.jl @@ -193,17 +193,23 @@ function linspace{T<:FloatingPoint}(start::T, stop::T, len::Int) e = lcm(b,d) a *= div(e,b) c *= div(e,d) - s, p = frexp(convert(T,n*e)) - p = one(p) << p - a /= p; c /= p + s = convert(T,n*e) + if isinf(a*n) || isinf(c*n) + s, p = frexp(convert(T,s)) + p = one(p) << p + a /= p; c /= p + end if a*n/s == start && c*n/s == stop return LinSpace(a, c, len, s) end end end - s, p = frexp(convert(T,n)) - p = one(p) << p - start /= p; stop /= p + s = convert(T,n) + if isinf(s*start) || isinf(s*stop) + s, p = frexp(s) + p = one(p) << p + start /= p; stop /= p + end return LinSpace(start, stop, len, s) end linspace(start::Real, stop::Real, len::Real=50) = diff --git a/test/ranges.jl b/test/ranges.jl index 9caa9e4bf1809..0213c68183291 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -286,6 +286,27 @@ for T = (Float32, Float64,),# BigFloat), @test [r[n:-2:1];] == [r;][n:-2:1] end +# very small linspace & ranges +for T = (Float32, Float64) + z = zero(T) + u = eps(z) + @test [linspace(-u,u,2);] == [-u,u] + @test [linspace(-u,u,3);] == [-u,0,u] + @test [linspace(-u,u,4);] == [-u,0,0,u] + @test [linspace(-u,u,4);][2] === -z + @test [linspace(-u,u,4);][3] === z + @test [linspace(u,-u,2);] == [u,-u] + @test [linspace(u,-u,3);] == [u,0,-u] + @test [linspace(u,-u,4);] == [u,0,0,-u] + @test [linspace(u,-u,4);][2] === z + @test [linspace(u,-u,4);][3] === -z + v = [linspace(-u,u,12);] + @test length(v) == 12 + @test issorted(v) && unique(v) == [-u,0,0,u] + @test [-3u:u:3u;] == [linspace(-3u,3u,7);] == [-3:3;].*u + @test [3u:-u:-3u;] == [linspace(3u,-3u,7);] == [3:-1:-3;].*u +end + # near-equal ranges @test 0.0:0.1:1.0 != 0.0f0:0.1f0:1.0f0 From ae1706b3a8ff2ba91349fc46c969cfa3d6c93547 Mon Sep 17 00:00:00 2001 From: Stefan Karpinski Date: Fri, 3 Apr 2015 11:52:37 -0400 Subject: [PATCH 08/13] LinSpace: represent length using floating-point value too. --- base/range.jl | 46 +++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/base/range.jl b/base/range.jl index e5d085f6e609e..e635818213d64 100644 --- a/base/range.jl +++ b/base/range.jl @@ -168,22 +168,26 @@ range(a::FloatingPoint, st::Real, len::Integer) = FloatRange(a, float(st), len, immutable LinSpace{T<:FloatingPoint} <: Range{T} start::T stop::T - len::Int + len::T divisor::T end -LinSpace(start::FloatingPoint, stop::FloatingPoint, len::Real, divisor::Real) = - LinSpace{promote_type(typeof(start),typeof(stop),typeof(divisor))}(start,stop,len,divisor) -function linspace{T<:FloatingPoint}(start::T, stop::T, len::Int) +function linspace{T<:FloatingPoint}(start::T, stop::T, len::T) + len == round(len) || throw(InexactError()) 0 <= len || error("linspace($start, $stop, $len): negative length") if len == 0 - return LinSpace(-start, -stop, -1, convert(T,2)) + n = convert(T, 2) + if isinf(n*start) || isinf(n*stop) + start /= n; stop /= n; n = one(T) + end + return LinSpace(-start, -stop, -one(T), n) end if len == 1 start == stop || error("linspace($start, $stop, $len): endpoints differ") - return LinSpace(-start, -start, 0, one(T)) + return LinSpace(-start, -start, zero(T), one(T)) end - n = len - 1 + n = convert(T, len - 1) + len - n == 1 || error("linspace($start, $stop, $len): too long for $T") a0, b = rat(start) a = convert(T,a0) if a/convert(T,b) == start @@ -195,7 +199,7 @@ function linspace{T<:FloatingPoint}(start::T, stop::T, len::Int) c *= div(e,d) s = convert(T,n*e) if isinf(a*n) || isinf(c*n) - s, p = frexp(convert(T,s)) + s, p = frexp(s) p = one(p) << p a /= p; c /= p end @@ -204,18 +208,30 @@ function linspace{T<:FloatingPoint}(start::T, stop::T, len::Int) end end end - s = convert(T,n) - if isinf(s*start) || isinf(s*stop) - s, p = frexp(s) + if isinf(n*start) || isinf(n*stop) + n, p = frexp(n) p = one(p) << p start /= p; stop /= p end - return LinSpace(start, stop, len, s) + return LinSpace(start, stop, len, n) +end +function linspace{T<:FloatingPoint}(start::T, stop::T, len::Real) + T_len = convert(T, len) + T_len == len || throw(InexactError()) + linspace(start, stop, T_len) end linspace(start::Real, stop::Real, len::Real=50) = - linspace(promote(FloatingPoint(start), FloatingPoint(stop))..., Int(len)) + linspace(promote(FloatingPoint(start), FloatingPoint(stop))..., len) -show(io::IO, r::LinSpace) = print(io, "linspace($(first(r)),$(last(r)),$(length(r)))") +function show(io::IO, r::LinSpace) + print(io, "linspace(") + show(io, first(r)) + print(io, ',') + show(last(r)) + print(io, ',') + show(length(r)) + print(io, ')') +end logspace(start::Real, stop::Real, n::Integer=50) = 10.^linspace(start, stop, n) @@ -243,7 +259,7 @@ function length(r::StepRange) end length(r::UnitRange) = Integer(r.stop - r.start + 1) length(r::FloatRange) = Integer(r.len) -length(r::LinSpace) = r.len + signbit(r.len - 1) +length(r::LinSpace) = Integer(r.len + signbit(r.len - 1)) function length{T<:Union(Int,UInt,Int64,UInt64)}(r::StepRange{T}) isempty(r) && return zero(T) From 62b4aaa7f4a1d628ed73a479f281e8ca52da53c3 Mon Sep 17 00:00:00 2001 From: Stefan Karpinski Date: Fri, 3 Apr 2015 14:07:56 -0400 Subject: [PATCH 09/13] linspace: more extensive testing of extreme linspace cases. --- test/ranges.jl | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/test/ranges.jl b/test/ranges.jl index 0213c68183291..fbf8bed7cff5b 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -286,15 +286,27 @@ for T = (Float32, Float64,),# BigFloat), @test [r[n:-2:1];] == [r;][n:-2:1] end -# very small linspace & ranges +# linspace & ranges with very small endpoints for T = (Float32, Float64) z = zero(T) u = eps(z) + @test first(linspace(u,u,0)) == u + @test last(linspace(u,u,0)) == u + @test first(linspace(-u,u,0)) == -u + @test last(linspace(-u,u,0)) == u + @test [linspace(-u,u,0);] == [] + @test [linspace(-u,-u,1);] == [-u] @test [linspace(-u,u,2);] == [-u,u] @test [linspace(-u,u,3);] == [-u,0,u] @test [linspace(-u,u,4);] == [-u,0,0,u] @test [linspace(-u,u,4);][2] === -z @test [linspace(-u,u,4);][3] === z + @test first(linspace(-u,-u,0)) == -u + @test last(linspace(-u,-u,0)) == -u + @test first(linspace(u,-u,0)) == u + @test last(linspace(u,-u,0)) == -u + @test [linspace(u,-u,0);] == [] + @test [linspace(u,u,1);] == [u] @test [linspace(u,-u,2);] == [u,-u] @test [linspace(u,-u,3);] == [u,0,-u] @test [linspace(u,-u,4);] == [u,0,0,-u] @@ -307,6 +319,36 @@ for T = (Float32, Float64) @test [3u:-u:-3u;] == [linspace(3u,-3u,7);] == [3:-1:-3;].*u end +# linspace with very large endpoints +for T = (Float32, Float64) + a = realmax() + for i = 1:5 + @test [linspace(a,a,1);] == [a] + @test [linspace(-a,-a,1);] == [-a] + b = realmax() + for j = 1:5 + @test [linspace(-a,b,0);] == [] + @test [linspace(-a,b,2);] == [-a,b] + @test [linspace(-a,b,3);] == [-a,(b-a)/2,b] + @test [linspace(a,-b,0);] == [] + @test [linspace(a,-b,2);] == [a,-b] + @test [linspace(a,-b,3);] == [a,(a-b)/2,-b] + for c = maxintfloat(T)-3:maxintfloat(T) + s = linspace(-a,b,c) + @test first(s) == -a + @test last(s) == b + @test length(s) == c + s = linspace(a,-b,c) + @test first(s) == a + @test last(s) == -b + @test length(s) == c + end + b = prevfloat(b) + end + a = prevfloat(a) + end +end + # near-equal ranges @test 0.0:0.1:1.0 != 0.0f0:0.1f0:1.0f0 From f7bff896ad6d022048d4fd84a0539565eb119351 Mon Sep 17 00:00:00 2001 From: Stefan Karpinski Date: Fri, 3 Apr 2015 17:07:03 -0400 Subject: [PATCH 10/13] exports: remove deprecated `linrange` from exports. --- base/exports.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/base/exports.jl b/base/exports.jl index ae6f6511f15bb..0a7430f7b4da8 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -522,7 +522,6 @@ export issorted, last, levicivita, - linrange, linspace, logspace, mapslices, From 7ce35bf029be022467352175839c597fc8b8c526 Mon Sep 17 00:00:00 2001 From: Stefan Karpinski Date: Mon, 6 Apr 2015 19:19:11 -0400 Subject: [PATCH 11/13] linspace: raise errors for cases that cannot be constructed. E.g. linspace(realmin(), realmax(), maxintfloat()) --- base/range.jl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/base/range.jl b/base/range.jl index e635818213d64..c5d1cfedbc9fe 100644 --- a/base/range.jl +++ b/base/range.jl @@ -208,12 +208,16 @@ function linspace{T<:FloatingPoint}(start::T, stop::T, len::T) end end end - if isinf(n*start) || isinf(n*stop) - n, p = frexp(n) + a, c, s = start, stop, n + if isinf(a*n) || isinf(c*n) + s, p = frexp(s) p = one(p) << p - start /= p; stop /= p + a /= p; c /= p end - return LinSpace(start, stop, len, n) + if a*n/s == start && c*n/s == stop + return LinSpace(a, c, len, s) + end + error("linspace($start, $stop, $len): cannot be constructed") end function linspace{T<:FloatingPoint}(start::T, stop::T, len::Real) T_len = convert(T, len) From 2a130b787fb341d10e792876714903c23db9f8d4 Mon Sep 17 00:00:00 2001 From: Stefan Karpinski Date: Mon, 6 Apr 2015 19:31:28 -0400 Subject: [PATCH 12/13] linspace: update documentation, add NEWS entry. --- NEWS.md | 4 ++++ doc/manual/arrays.rst | 2 +- doc/manual/noteworthy-differences.rst | 3 +-- doc/stdlib/arrays.rst | 5 ++--- doc/stdlib/math.rst | 4 ---- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/NEWS.md b/NEWS.md index 6354c385ca161..83a67235fb396 100644 --- a/NEWS.md +++ b/NEWS.md @@ -245,6 +245,8 @@ Library improvements * `code_llvm` now outputs stripped IR without debug info or other attached metadata. Use `code_llvm_raw` for the unstripped output ([#10747]). + * `linspace` now returns a `LinSpace` object which lazily computes linear interpolation of values between the start and stop values. It "lifts" endpoints which are approximately rational in the same manner as the `colon` operator. + Deprecated or removed --------------------- @@ -309,6 +311,8 @@ Deprecated or removed * the --int-literals compiler option is no longer accepted. + * Instead of `linrange` use `linspace`. + Julia v0.3.0 Release Notes ========================== diff --git a/doc/manual/arrays.rst b/doc/manual/arrays.rst index ee93dfed760da..ebcd0637a8be3 100644 --- a/doc/manual/arrays.rst +++ b/doc/manual/arrays.rst @@ -89,7 +89,7 @@ Function Description distributed random values :func:`eye(n) ` ``n``-by-``n`` identity matrix :func:`eye(m, n) ` ``m``-by-``n`` identity matrix -:func:`linspace(start, stop, n) ` vector of ``n`` linearly-spaced elements from ``start`` to ``stop`` +:func:`linspace(start, stop, n) ` range of ``n`` linearly spaced elements from ``start`` to ``stop`` :func:`fill!(A, x) ` fill the array ``A`` with the value ``x`` :func:`fill(x, dims) ` create an array filled with the value ``x`` =================================================== ===================================================================== diff --git a/doc/manual/noteworthy-differences.rst b/doc/manual/noteworthy-differences.rst index 89f0e3da13014..3cba1a2d128db 100644 --- a/doc/manual/noteworthy-differences.rst +++ b/doc/manual/noteworthy-differences.rst @@ -44,8 +44,7 @@ some noteworthy differences that may trip up Julia users accustomed to MATLAB: the syntax ``[a b; c d]`` is used to avoid confusion. In Julia v0.4, the concatenation syntax ``[x, [y, z]]`` is deprecated in favor of ``[x; [y, z]]``. - In Julia, ``a:b`` and ``a:b:c`` construct :obj:`Range` objects. To construct - a full vector like in MATLAB, use :func:`collect(a:b) ` or - :func:`linspace`. + a full vector like in MATLAB, use :func:`collect(a:b) `. - Functions in Julia return values from their last expression or the ``return`` keyword instead of listing the names of variables to return in the function definition (see :ref:`man-return-keyword` for details). diff --git a/doc/stdlib/arrays.rst b/doc/stdlib/arrays.rst index 5b645d3f581d2..09a7f5efe5990 100644 --- a/doc/stdlib/arrays.rst +++ b/doc/stdlib/arrays.rst @@ -169,12 +169,11 @@ Constructors .. function:: linspace(start, stop, n=100) - Construct a vector of ``n`` linearly-spaced elements from ``start`` to ``stop``. - See also: :func:`linrange` that constructs a range object. + Construct a range of ``n`` linearly spaced elements from ``start`` to ``stop``. .. function:: logspace(start, stop, n=50) - Construct a vector of ``n`` logarithmically-spaced numbers from ``10^start`` to ``10^stop``. + Construct a vector of ``n`` logarithmically spaced numbers from ``10^start`` to ``10^stop``. Mathematical operators and functions ------------------------------------ diff --git a/doc/stdlib/math.rst b/doc/stdlib/math.rst index aae27b12a285b..2f73b48c277b7 100644 --- a/doc/stdlib/math.rst +++ b/doc/stdlib/math.rst @@ -189,10 +189,6 @@ Mathematical Operators Construct a range by length, given a starting value and optional step (defaults to 1). -.. function:: linrange(start, end, length) - - Construct a range by length, given a starting and ending value. - .. _==: .. function:: ==(x, y) From aa78037a7890fdcd087a3c9d5f82421d59cbf50a Mon Sep 17 00:00:00 2001 From: Stefan Karpinski Date: Mon, 13 Apr 2015 12:26:18 -0400 Subject: [PATCH 13/13] linspace: fix 32-bit failures The issue was that doing `one(p) << p` where p is Int produces zero on a 32-bit system if p is larger than 32, which it can be for some Float64 arguments. Getting tests to pass for 32-bit also required avoiding taking the length of LinSpace objects whose lengths are 2^31 or longer. This is a bit of a more general problem since we can represent FloatRange and LinSpace objects whose length cannot be represented as an Int on both 64- and 32-bit platforms, although it is, of course, easier to encounter the problem on 32-bit systems. In fact, on 64-bit systems it is not actually possible to construct LinSpace{Float64} objects whose length cannot be represented as an Int. The issue does exist for FloatRange on 64-bit systems, however. One possible solution is to introduce length(T,x) that gives lengths in a particular type and just define length(x) as length(Int,x) as the default behavior. --- base/range.jl | 4 ++-- test/ranges.jl | 14 ++++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/base/range.jl b/base/range.jl index c5d1cfedbc9fe..c81f3ea36ef79 100644 --- a/base/range.jl +++ b/base/range.jl @@ -200,7 +200,7 @@ function linspace{T<:FloatingPoint}(start::T, stop::T, len::T) s = convert(T,n*e) if isinf(a*n) || isinf(c*n) s, p = frexp(s) - p = one(p) << p + p = oftype(s,2)^p a /= p; c /= p end if a*n/s == start && c*n/s == stop @@ -211,7 +211,7 @@ function linspace{T<:FloatingPoint}(start::T, stop::T, len::T) a, c, s = start, stop, n if isinf(a*n) || isinf(c*n) s, p = frexp(s) - p = one(p) << p + p = oftype(s,2)^p a /= p; c /= p end if a*n/s == start && c*n/s == stop diff --git a/test/ranges.jl b/test/ranges.jl index fbf8bed7cff5b..864fc22470dfb 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -335,13 +335,15 @@ for T = (Float32, Float64) @test [linspace(a,-b,3);] == [a,(a-b)/2,-b] for c = maxintfloat(T)-3:maxintfloat(T) s = linspace(-a,b,c) - @test first(s) == -a - @test last(s) == b - @test length(s) == c + @test first(s) == -a + @test last(s) == b + c <= typemax(Int) && @test length(s) == c + @test s.len == c s = linspace(a,-b,c) - @test first(s) == a - @test last(s) == -b - @test length(s) == c + @test first(s) == a + @test last(s) == -b + c <= typemax(Int) && @test length(s) == c + @test s.len == c end b = prevfloat(b) end