diff --git a/src/kernels.jl b/src/kernels.jl index 9ed22a6..8951beb 100644 --- a/src/kernels.jl +++ b/src/kernels.jl @@ -579,6 +579,20 @@ end # BinaryBracket. # =========================================================================== +# Truncate a guess coordinate `f` to an `Int` offset. The caller has already +# clamped `f` to `[0, nm1)` (last) / `(0, nm1]` (first), so on the hardware +# floats the fast `unsafe_trunc` is in-range and skips a redundant check. +# Other ordered-`Real` ratio types — `Rational`, AD `Dual`, `BigFloat`, … — +# do not all define `unsafe_trunc`, so fall back to the checked +# `floor(Int, ·)` / `ceil(Int, ·)`, which every such type supports and which +# is exact for the in-range `f`. The hardware-float union is spelled out +# rather than via the non-public `Base.IEEEFloat` alias. +const _HardwareFloat = Union{Float16, Float32, Float64} +@inline _uniform_floor_index(f::_HardwareFloat) = unsafe_trunc(Int, floor(f)) +@inline _uniform_floor_index(f) = floor(Int, f) +@inline _uniform_ceil_index(f::_HardwareFloat) = unsafe_trunc(Int, ceil(f)) +@inline _uniform_ceil_index(f) = ceil(Int, f) + @inline function _kernel_last_uniform_step_props( props::SearchProperties, v::AbstractVector, x, order::Base.Order.Ordering, ) @@ -605,7 +619,7 @@ end elseif f >= nm1 lastindex(v) else - firstindex(v) + unsafe_trunc(Int, floor(f)) + firstindex(v) + _uniform_floor_index(f) end # Walk to the true cell. For exactly-uniform data this takes at most # one step (float roundoff); it also keeps the result correct when @@ -640,7 +654,7 @@ end elseif f > nm1 lastindex(v) + 1 else - firstindex(v) + unsafe_trunc(Int, ceil(f)) + firstindex(v) + _uniform_ceil_index(f) end @inbounds while i > firstindex(v) && !Base.Order.lt(order, v[i - 1], x) i -= 1 diff --git a/test/core_tests.jl b/test/core_tests.jl index 3366964..1490589 100644 --- a/test/core_tests.jl +++ b/test/core_tests.jl @@ -1398,3 +1398,33 @@ end @test F.findfirstsortedequal(x, v) == ref end end + +@safetestset "UniformStep kernel across non-primitive Real eltypes" begin + using FindFirstFunctions + using FindFirstFunctions: SearchProperties, Auto, searchsorted_last, searchsorted_first + + # `Rational` ratio types don't define `unsafe_trunc(Int, ·)`, so a uniform + # `Rational` vector — which `Auto` resolves to the closed-form + # `KIND_UNIFORM_STEP` path — must fall back to `floor/ceil(Int, ·)` rather + # than erroring. `BigFloat` exercises the non-`IEEEFloat` AbstractFloat + # branch of the same helper. + uniform_vectors = ( + Rational{Int}[i // 4 for i in 0:4:160], + Rational{BigInt}[big(i) // 4 for i in 0:4:160], + BigFloat[BigFloat(i) for i in range(0.0, 10.0; length = 40)], + ) + for v in uniform_vectors + @test SearchProperties(v).is_uniform + auto = Auto(v) + @test auto isa Auto + for i in eachindex(v) + @test searchsorted_last(auto, v, v[i]) == searchsortedlast(v, v[i]) + @test searchsorted_first(auto, v, v[i]) == searchsortedfirst(v, v[i]) + end + # Between-knot and out-of-range queries. + for q in (v[1] - one(eltype(v)), (v[3] + v[4]) / 2, v[end] + one(eltype(v))) + @test searchsorted_last(auto, v, q) == searchsortedlast(v, q) + @test searchsorted_first(auto, v, q) == searchsortedfirst(v, q) + end + end +end