Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 7 additions & 24 deletions src/hist.jl
Original file line number Diff line number Diff line change
Expand Up @@ -197,14 +197,6 @@ mutable struct Histogram{T<:Real,N,E} <: AbstractHistogram{T,N,E}
closed == :right || closed == :left || error("closed must :left or :right")
isdensity && !(T <: AbstractFloat) && error("Density histogram must have float-type weights")
_edges_nbins(edges) == size(weights) || error("Histogram edge vectors must be 1 longer than corresponding weight dimensions")
# We do not handle -0.0 in ranges correctly in `binindex` for performance
# Constructing ranges starting or ending with -0.0 is very hard,
# and ranges containing -0.0 elsewhere virtually impossible,
# but check this just in case as it is cheap
foreach(edges) do e
e isa AbstractRange && any(isequal(-0.0), e) &&
throw(ArgumentError("ranges containing -0.0 not allowed in edges"))
end
new{T,N,E}(edges,weights,closed,isdensity)
end
end
Expand Down Expand Up @@ -240,25 +232,16 @@ binindex(h::AbstractHistogram{T,1}, x::Real) where {T} = binindex(h, (x,))[1]
binindex(h::Histogram{T,N}, xs::NTuple{N,Real}) where {T,N} =
map((edge, x) -> _edge_binindex(edge, h.closed, x), h.edges, xs)

_normalize_zero(x::AbstractFloat) = isequal(x, -0.0) ? zero(x) : x
_normalize_zero(x::Any) = x

# Always treat -0.0 like 0.0
# Compare with `<` rather than the default `isless`: `isless(-0.0, 0.0)` is true, so -0.0
# would be binned differently from 0.0, whereas `-0.0 < 0.0` is false and the two are
# treated as equal. NaN compares false with everything under `<`, so it ends up outside
# the edges (bin index 0 or `length(edge)`) and is dropped by `push!` as before. `<` is also
# cheaper than `isless` and keeps the arithmetic fast path for ranges.
@inline function _edge_binindex(edge::AbstractVector, closed::Symbol, x::Real)
if closed === :right
return searchsortedfirst(edge, _normalize_zero(x), by=_normalize_zero) - 1
else
return searchsortedlast(edge, _normalize_zero(x), by=_normalize_zero)
end
end
# Passing by=_normalize_zero for ranges would have a large performance hit
# as it would force using the AbstractVector fallback
# This is not worth it given that it is very difficult to construct a range containing -0.0
@inline function _edge_binindex(edge::AbstractRange, closed::Symbol, x::Real)
if closed === :right
return searchsortedfirst(edge, _normalize_zero(x)) - 1
return searchsortedfirst(edge, x, lt = <) - 1
else
return searchsortedlast(edge, _normalize_zero(x))
return searchsortedlast(edge, x, lt = <)
end
end

Expand Down
16 changes: 14 additions & 2 deletions test/hist.jl
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,20 @@ end
fit(Histogram, [0.0, 1.0], -0.5:0.5:0.0, closed=:right) ==
fit(Histogram, [-0.0, 1.0], -0.5:0.5:0.0, closed=:right)

@test_throws ArgumentError fit(Histogram, [-0.5], LinRange(-1.0, -0.0, 3))
@test_throws ArgumentError fit(Histogram, [-0.5], UnitRange(-0.0, 1.0))
# ranges containing -0.0 behave like the corresponding ranges with 0.0
@test fit(Histogram, [-0.5, 0.0], LinRange(-1.0, -0.0, 3)) ==
fit(Histogram, [-0.5, 0.0], LinRange(-1.0, 0.0, 3))
@test fit(Histogram, [-0.5, 0.0], LinRange(-1.0, -0.0, 3), closed=:right) ==
fit(Histogram, [-0.5, -0.0], LinRange(-1.0, 0.0, 3), closed=:right)
@test fit(Histogram, [0.0, 0.5], UnitRange(-0.0, 1.0)) ==
fit(Histogram, [-0.0, 0.5], UnitRange(0.0, 1.0))
@test fit(Histogram, [0.0, 0.5], UnitRange(-0.0, 1.0), closed=:right) ==
fit(Histogram, [-0.0, 0.5], UnitRange(0.0, 1.0), closed=:right)
# NaN is dropped
@test fit(Histogram, [NaN, 0.5], 0.0:0.5:1.0).weights == [0, 1]
@test fit(Histogram, [NaN, 0.5], 0.0:0.5:1.0, closed=:right).weights == [1, 0]
@test fit(Histogram, [NaN, 0.5], [0.0, 0.5, 1.0]).weights == [0, 1]
@test fit(Histogram, [NaN, 0.5], [0.0, 0.5, 1.0], closed=:right).weights == [1, 0]
end

end # @testset "StatsBase.Histogram"