# Why aren't expressions such as `isinteger` simplified in case that the result is inferrable from the arguments?

``````julia> @code_typed (n -> isinteger(n+0.5))(2)
CodeInfo(
1 ─ %1 = Base.sitofp(Float64, n)::Float64
│   %3 = Base.trunc_llvm(%2)::Float64
│   %4 = Base.sub_float(%2, %3)::Float64
│   %5 = Base.eq_float(%4, 0.0)::Bool
│   %6 = Base.and_int(%5, true)::Bool
│   %7 = Base.and_int(%6, true)::Bool
└──      return %7
) => Bool
``````

Given that `n` is an integer, and the constant `0.5`, it should be possible to infer that the `n+0.5` isn’t an integer. I wonder this simplification isn’t carried out here, although the compiler does propagate constants if `n` is a known at compile-time as well?

``````julia> @code_typed (() -> (n = 1; isinteger(n+0.5)))()
CodeInfo(
1 ─     return false
) => Bool
``````

Ah well,

``````julia> isinteger(typemax(Int) + 0.5)
true
``````

Although,

``````julia> isinteger(typemax(Int32) + 0.5)
false

julia> isinteger(typemin(Int32) + 0.5)
false
``````

but this is still not inferred

``````julia> @code_typed (n -> isinteger(n+0.5))(Int32(1))
CodeInfo(
1 ─ %1 = Base.sitofp(Float64, n)::Float64
│   %3 = Base.trunc_llvm(%2)::Float64
│   %4 = Base.sub_float(%2, %3)::Float64
│   %5 = Base.eq_float(%4, 0.0)::Bool
│   %6 = Base.and_int(%5, true)::Bool
│   %7 = Base.and_int(%6, true)::Bool
└──      return %7
) => Bool
``````
3 Likes

There are some corner cases where the value of `isinteger` can be predicted, but it’s a pretty tricky interaction between the `eps(x)` graph for the float type, the range of the integer type, and the specific float constant that’s being added. Let’s say we have this function:

``````f(x::AbstractFloat, n::Integer) = isinteger(x + n)
``````

and we say `F = typeof(x)` and `T = typeof(n)`. When could we constant fold based on the value of `x`? It’s a pretty complex set of rules that LLVM doesn’t know.

6 Likes

Can it truly be known that `n+0.5` is not an integer?

``````julia> a+b = round(Int, a)*round(Int, b)
+ (generic function with 1 method)

julia> 1.5+2.3
4

julia> isinteger(1.5+2.3)
true
`````` I’m referring to `Base.+(n, 0.5)` for an integer `n`

Mathematically yea, but it fails when a value is big enough because floats stop being able to represent non-integral values.

1 Like ``````julia> Base.:+(a::Int, b::Float64) = a+round(Int, b)

julia> n=2
2

julia> isinteger(n+0.5)
true
``````

This is type-piracy, which I’m assuming isn’t happening. In any case, the point not about methods being overwritten, it’s about the method as defined in Base not being able to infer the result. I’m not talking about a general case where I don’t know the method that’s called, given the arguments.

1 Like

Sorry, you’re right; even with my type piracy, the types are inferred and constants propagated. My input unfortunately degraded the conversation’s SNR.

``````julia> @code_typed (() -> (n = 1; isinteger(n+0.5)))()
CodeInfo(
1 ─     return false
) => Bool

julia> Base.:+(a::Int, b::Float64) = a+round(Int, b)

julia> @code_typed (() -> (n = 1; isinteger(n+0.5)))()
CodeInfo(
1 ─     return true
) => Bool
``````

Whereas when `n` is an argument to the function, rather than being locally declared (fresh Julia process):

``````julia> @code_typed ((n::Int) -> (isinteger(n::Int+0.5)))(1::Int)
CodeInfo(
1 ─ %1 = Base.sitofp(Float64, n)::Float64
│   %3 = Base.trunc_llvm(%2)::Float64
│   %4 = Base.sub_float(%2, %3)::Float64
│   %5 = Base.eq_float(%4, 0.0)::Bool
│   %6 = Base.and_int(%5, true)::Bool
│   %7 = Base.and_int(%6, true)::Bool
└──      return %7
) => Bool

julia> Base.:+(a::Int, b::Float64) = a+round(Int, b)

julia> @code_typed ((n) -> (isinteger(n+0.5)))(1)
CodeInfo(
1 ─     return true
) => Bool
``````

even with all the type annotations, it doesn’t work the same because