Skip to content

Commit 5f03a18

Browse files
authored
Fix integer overflow in isapprox (#50730)
Ensure that `isapprox` gives correct results when comparing an integer with another integer or with a float. For comparison between integers, the fix only works when keeping default values for `rtol` and `norm`, and with `atol < 1`. It is not possible to handle the (atypical) case where `norm !== abs`, but that's OK since the user is responsible for providing a safe function. It would be possible to handle the case where `rtol > 0` or `atol >= 1`, but with complex code which would check for overflow and handle all possible corner cases; it would work only for types defined in Base and would not be extensible by packages. So I'm not sure that's worth it. At least with PR fixes the most common case. Fixes #50380.
1 parent ad0712f commit 5f03a18

File tree

2 files changed

+58
-1
lines changed

2 files changed

+58
-1
lines changed

base/floatfuncs.jl

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,20 @@ true
239239
function isapprox(x::Number, y::Number;
240240
atol::Real=0, rtol::Real=rtoldefault(x,y,atol),
241241
nans::Bool=false, norm::Function=abs)
242-
x == y || (isfinite(x) && isfinite(y) && norm(x-y) <= max(atol, rtol*max(norm(x), norm(y)))) || (nans && isnan(x) && isnan(y))
242+
x′, y′ = promote(x, y) # to avoid integer overflow
243+
x == y ||
244+
(isfinite(x) && isfinite(y) && norm(x-y) <= max(atol, rtol*max(norm(x′), norm(y′)))) ||
245+
(nans && isnan(x) && isnan(y))
246+
end
247+
248+
function isapprox(x::Integer, y::Integer;
249+
atol::Real=0, rtol::Real=rtoldefault(x,y,atol),
250+
nans::Bool=false, norm::Function=abs)
251+
if norm === abs && atol < 1 && rtol == 0
252+
return x == y
253+
else
254+
return norm(x - y) <= max(atol, rtol*max(norm(x), norm(y)))
255+
end
243256
end
244257

245258
"""

test/floatfuncs.jl

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,47 @@ end
209209
struct CustomNumber <: Number end
210210
@test !isnan(CustomNumber())
211211
end
212+
213+
@testset "isapprox and integer overflow" begin
214+
for T in (Int8, Int16, Int32)
215+
T === Int && continue
216+
@test !isapprox(typemin(T), T(0))
217+
@test !isapprox(typemin(T), unsigned(T)(0))
218+
@test !isapprox(typemin(T), 0)
219+
@test !isapprox(typemin(T), T(0), atol=0.99)
220+
@test !isapprox(typemin(T), unsigned(T)(0), atol=0.99)
221+
@test !isapprox(typemin(T), 0, atol=0.99)
222+
@test_broken !isapprox(typemin(T), T(0), atol=1)
223+
@test_broken !isapprox(typemin(T), unsigned(T)(0), atol=1)
224+
@test !isapprox(typemin(T), 0, atol=1)
225+
226+
@test !isapprox(typemin(T)+T(10), T(10))
227+
@test !isapprox(typemin(T)+T(10), unsigned(T)(10))
228+
@test !isapprox(typemin(T)+T(10), 10)
229+
@test !isapprox(typemin(T)+T(10), T(10), atol=0.99)
230+
@test !isapprox(typemin(T)+T(10), unsigned(T)(10), atol=0.99)
231+
@test !isapprox(typemin(T)+T(10), 10, atol=0.99)
232+
@test_broken !isapprox(typemin(T)+T(10), T(10), atol=1)
233+
@test !isapprox(typemin(T)+T(10), unsigned(T)(10), atol=1)
234+
@test !isapprox(typemin(T)+T(10), 10, atol=1)
235+
236+
@test isapprox(typemin(T), 0.0, rtol=1)
237+
end
238+
for T in (Int, Int64, Int128)
239+
@test !isapprox(typemin(T), T(0))
240+
@test !isapprox(typemin(T), unsigned(T)(0))
241+
@test !isapprox(typemin(T), T(0), atol=0.99)
242+
@test !isapprox(typemin(T), unsigned(T)(0), atol=0.99)
243+
@test_broken !isapprox(typemin(T), T(0), atol=1)
244+
@test_broken !isapprox(typemin(T), unsigned(T)(0), atol=1)
245+
246+
@test !isapprox(typemin(T)+T(10), T(10))
247+
@test !isapprox(typemin(T)+T(10), unsigned(T)(10))
248+
@test !isapprox(typemin(T)+T(10), T(10), atol=0.99)
249+
@test !isapprox(typemin(T)+T(10), unsigned(T)(10), atol=0.99)
250+
@test_broken !isapprox(typemin(T)+T(10), T(10), atol=1)
251+
@test !isapprox(typemin(T)+T(10), unsigned(T)(10), atol=1)
252+
253+
@test isapprox(typemin(T), 0.0, rtol=1)
254+
end
255+
end

0 commit comments

Comments
 (0)