From 09349c9d9a5ca05828f8438c89bcb47397d18456 Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Mon, 13 Jul 2026 10:17:30 -0400 Subject: [PATCH 1/2] Fall back from unsafe_trunc in UniformStep kernel for non-float ratio types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The closed-form UniformStep kernels compute an integer index offset with `unsafe_trunc(Int, floor(f))` / `unsafe_trunc(Int, ceil(f))`. `unsafe_trunc` is only defined for hardware floats and `BigFloat`; for a `Rational` ratio type it has no method, so a uniform `Rational` vector — which `Auto` resolves to `KIND_UNIFORM_STEP` — threw a `MethodError` on every search. `f` is already clamped to the in-range interval before truncation, so replace the raw `unsafe_trunc` with a small dispatch helper: keep `unsafe_trunc` on the `Base.IEEEFloat` hot path (in-range, so the range check is redundant) and fall back to the checked `floor(Int, ·)` / `ceil(Int, ·)` for every other ordered `Real` — which those types support and which is exact for the clamped `f`. The IEEEFloat hot path is unchanged. Adds a UniformStep regression testset over uniform `Rational{Int}`, `Rational{BigInt}`, and `BigFloat` vectors, checking search results against `Base`. Co-Authored-By: Chris Rackauckas Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01FvBXVKGujjeaCB3iLwsDeG --- src/kernels.jl | 16 ++++++++++++++-- test/core_tests.jl | 30 ++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/kernels.jl b/src/kernels.jl index 9ed22a6..19b6c89 100644 --- a/src/kernels.jl +++ b/src/kernels.jl @@ -579,6 +579,18 @@ 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`. +@inline _uniform_floor_index(f::Base.IEEEFloat) = unsafe_trunc(Int, floor(f)) +@inline _uniform_floor_index(f) = floor(Int, f) +@inline _uniform_ceil_index(f::Base.IEEEFloat) = 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 +617,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 +652,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 From c189dffa802fd1a9a58c32bcdcc5876705ef9992 Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Mon, 13 Jul 2026 15:56:43 -0400 Subject: [PATCH 2/2] QA: avoid non-public Base.IEEEFloat in UniformStep truncation helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ExplicitImports' `all_qualified_accesses_are_public` check flags `Base.IEEEFloat` — it is neither exported nor declared `public` in Base. Spell out the hardware-float union `Union{Float16, Float32, Float64}` (each of which is public) via a local `const` instead. No behavior change. Co-Authored-By: Chris Rackauckas Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01FvBXVKGujjeaCB3iLwsDeG --- src/kernels.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/kernels.jl b/src/kernels.jl index 19b6c89..8951beb 100644 --- a/src/kernels.jl +++ b/src/kernels.jl @@ -585,10 +585,12 @@ end # 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`. -@inline _uniform_floor_index(f::Base.IEEEFloat) = unsafe_trunc(Int, floor(f)) +# 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::Base.IEEEFloat) = unsafe_trunc(Int, ceil(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(