diff --git a/base/range.jl b/base/range.jl index e47d34b21e75c..554b9148d1abe 100644 --- a/base/range.jl +++ b/base/range.jl @@ -35,6 +35,15 @@ immutable Range1{T<:Real} <: Ranges{T} end Range1{T}(start::T, len::Integer) = Range1{T}(start, len) +immutable FloatRange{T<:FloatingPoint} <: Ranges{T} + start::T + step::T + divisor::T + len::T +end +FloatRange(a::FloatingPoint, s::FloatingPoint, d::FloatingPoint, l::Real) = + FloatRange{promote_type(typeof(a),typeof(s),typeof(d))}(a,s,d,l) + function colon{T<:Integer}(start::T, step::T, stop::T) step != 0 || error("step cannot be zero in colon syntax") Range{T}(start, step, max(0, 1 + fld(stop-start, step))) @@ -104,17 +113,63 @@ end colon(start::Real, step::Real, stop::Real) = colon(promote(start, step, stop)...) colon(start::Real, stop::Real) = colon(promote(start, stop)...) +# float rationalization helper +function rat(x) + y = x + a = d = 1 + b = c = 0 + m = typemax(Int) >> 1 + while max(abs(a),abs(b)) <= m + f = itrunc(y) + y -= f + a, c = f*a + c, a + b, d = f*b + d, b + (y == 0 || oftype(x,a)/oftype(x,b) == x) && return a, b + y = inv(y) + end + return c, d +end + +# float range "lifting" helper +function frange{T<:FloatingPoint}(start::T, step::T, stop::T) + r = (stop-start)/step + n = round(r) + lo = prevfloat((prevfloat(stop)-nextfloat(start))/n) + hi = nextfloat((nextfloat(stop)-prevfloat(start))/n) + if lo <= step <= hi + a, b = rat(start) + a = convert(T,a) + if a/convert(T,b) == start + c, d = rat(step) + c = convert(T,c) + if c/convert(T,d) == step + e = lcm(b,d) + a *= div(e,b) + c *= div(e,d) + e = convert(T,e) + if (a+n*c)/e == stop + return a, c, e, n+1 + end + end + end + end + start, step, one(step), floor(r)+1 +end + similar(r::Ranges, T::Type, dims::Dims) = Array(T, dims) length(r::Ranges) = integer(r.len) size(r::Ranges) = (length(r),) isempty(r::Ranges) = r.len==0 first(r::Ranges) = r.start +first(r::FloatRange) = r.start/r.divisor last{T}(r::Range1{T}) = oftype(T, r.start + r.len-1) last{T}(r::Range{T}) = oftype(T, r.start + (r.len-1)*r.step) +last{T}(r::FloatRange{T}) = oftype(T, (r.start + (r.len-1)*r.step)/r.divisor) step(r::Range) = r.step step(r::Range1) = one(r.start) +step(r::FloatRange) = r.step/r.divisor minimum(r::Range1) = isempty(r) ? error("range must be non-empty") : first(r) maximum(r::Range1) = isempty(r) ? error("range must be non-empty") : last(r) @@ -133,6 +188,10 @@ function getindex{T}(r::Ranges{T}, i::Integer) 1 <= i <= r.len || error(BoundsError) oftype(T, r.start + (i-1)*step(r)) end +function getindex{T}(r::FloatRange{T}, i::Integer) + 1 <= i <= r.len || error(BoundsError) + oftype(T, (r.start + (i-1)*r.step)/r.divisor) +end function getindex(r::Range1, s::Range1{Int}) if s.len > 0 @@ -165,6 +224,7 @@ show(io::IO, r::Range1) = print(io, repr(first(r)), ':', repr(last(r))) start(r::Ranges) = 0 next{T}(r::Range{T}, i) = (oftype(T, r.start + i*step(r)), i+1) next{T}(r::Range1{T}, i) = (oftype(T, r.start + i), i+1) +next{T}(r::FloatRange{T}, i) = (oftype(T, (r.start + i*r.step)/r.divisor), i+1) done(r::Ranges, i) = (length(r) <= i) # though these look very similar to the above, for some reason LLVM generates @@ -368,6 +428,7 @@ function vcat{T}(rs::Ranges{T}...) end reverse(r::Ranges) = Range(last(r), -step(r), r.len) +reverse(r::FloatRange) = FloatRange(last(r), -r.step, r.divisor, r.len) ## sorting ## diff --git a/base/sysimg.jl b/base/sysimg.jl index 0f50460ade6ed..c7ab107906e77 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -119,6 +119,13 @@ include("cartesian.jl") using .Cartesian include("multidimensional.jl") +# FIXME: #5885 +colon{T<:FloatingPoint}(start::T, step::T, stop::T) = + step == 0 ? error("step cannot be zero in colon syntax") : + start == stop ? FloatRange{T}(start,step,1,1) : + (0 < step) != (start < stop) ? FloatRange{T}(start,step,1,0) : + FloatRange{T}(frange(start,step,stop)...) + # core math functions include("floatfuncs.jl") include("math.jl") diff --git a/test/ranges.jl b/test/ranges.jl index e9b72251b1df9..1870f4e8fe4e3 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -165,3 +165,48 @@ end @test all(((5:-1:1) + [1:5]) .== 6) @test all(([1:5] - (1:5)) .== 0) @test all(((1:5) - [1:5]) .== 0) + +# 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.0:1.0:5.5 == [0:10:55]./10 +@test 0.0:-1.0:0.5 == [] +@test 0.0:1.0:0.5 == [0.0] + +@test prevfloat(0.1):0.1:0.3 == [prevfloat(0.1), 0.2, 0.3] +@test nextfloat(0.1):0.1:0.3 == [nextfloat(0.1), 0.2] +@test prevfloat(0.0):0.1:0.3 == [prevfloat(0.0), 0.1, 0.2] +@test nextfloat(0.0):0.1:0.3 == [nextfloat(0.0), 0.1, 0.2] +@test 0.1:0.1:prevfloat(0.3) == [0.1, 0.2] +@test 0.1:0.1:nextfloat(0.3) == [0.1, 0.2, nextfloat(0.3)] +@test 0.0:0.1:prevfloat(0.3) == [0.0, 0.1, 0.2] +@test 0.0:0.1:nextfloat(0.3) == [0.0, 0.1, 0.2, nextfloat(0.3)] +@test 0.1:prevfloat(0.1):0.3 == [0.1, 0.2, 0.3] +@test 0.1:nextfloat(0.1):0.3 == [0.1, 0.2] +@test 0.0:prevfloat(0.1):0.3 == [0.0, prevfloat(0.1), prevfloat(0.2), 0.3] +@test 0.0:nextfloat(0.1):0.3 == [0.0, nextfloat(0.1), nextfloat(0.2)] + +for T = (Float32, Float64,),# BigFloat), + a = -5:25, s = [-5:-1;1:25], d = 1:25, n = -1:15 + den = convert(T,d) + start = convert(T,a)/den + step = convert(T,s)/den + stop = convert(T,(a+(n-1)*s))/den + @test [start:step:stop] == T[a:s:a+(n-1)*s]./den +end