Base: new function `isdivisible`
Base does not provide a function for checking divisibility:
"""
isdivisible(m, n)
Return true if `m` is divisible by `n`.
"""
isdivisible(m::Integer, n::Integer)
It can be implemented as iszero(rem(m, n)) but that's not the optimal implementation for all types from Base (it allocates for a BigInt).
AbstractAlgebra.jl from Nemo implements this exact function: https://github.com/Nemocas/AbstractAlgebra.jl/blob/396fdddbc1d1620a7b2866e7340e39b14228176e/src/julia/Integer.jl#L121-L151 I would like to have it in Base.
(Discussed on Discourse: https://discourse.julialang.org/t/checking-divisibility-efficiently/121418)
FWIW I agree with @oscardssmith's comment on Discourse that IntegerMathUtils seems like a good home for that function. I would be interested to know whether any code in Base or the stdlibs could be meaningfully simplified by having this function available. If so, I'd consider that a positive signal for inclusion in Base. However, I suspect the answer is no (though I could well be wrong).
Understood. There are some divisibility checks in Base.
https://github.com/JuliaLang/julia/blob/d36417b8230f7f56359f8607495ec97247bcab50/base/array.jl#L2455 https://github.com/JuliaLang/julia/blob/d36417b8230f7f56359f8607495ec97247bcab50/base/arrayshow.jl#L225 https://github.com/JuliaLang/julia/blob/d36417b8230f7f56359f8607495ec97247bcab50/base/int.jl#L117 https://github.com/JuliaLang/julia/blob/d36417b8230f7f56359f8607495ec97247bcab50/base/int.jl#L137 https://github.com/JuliaLang/julia/blob/d36417b8230f7f56359f8607495ec97247bcab50/base/range.jl#L1209 https://github.com/JuliaLang/julia/blob/d36417b8230f7f56359f8607495ec97247bcab50/base/reinterpretarray.jl#L444
Some are in stdlibs (e.g., checking leap year in Dates).
I wouldn't say this is exactly "meaningfully simplified" (after all, how much can you simplify a divisibility check?):
- if sizeof(T) % sizeof(S) == 0
+ if isdivisible(sizeof(T), sizeof(S))
It is more explicit, maybe. I would say it is similar to replacing x % 2 == 0 with iseven(x).
I am mostly interested in it for the BigInt type for which x % n allocates, and so x % n == 0 is not cheap, whereas it is cheap for Ints.
I am mostly interested in it for the BigInt type for which
x % nallocates, and sox % n == 0is not cheap, whereas it is cheap for Ints.
not to derail this issue, but this statement makes me wonder if, with a fast isdivisible function, shouldn't % in fact get a fast path isdivisible(x, n) && return 0 for BigInt ?
As I understand it, rem(x::BigInt, n) returns a BigInt, so you would have to return big(0) to keep the method type-stable, which would again allocate.
BigInt are officially immutable though (despite the fact that they are mutable in practice) so one could return big"0" or any other shared obj and allocate only exactly once per program execution
I do think it would be worth considering whether % should take the type of the 2nd argument rather than the promotion type. GMP has optimized methods for BigInt%Int which would recover the performance for most cases.
I do think it would be worth considering whether % should take the type of the 2nd argument rather than the promotion type.
In that case, divrem would potentially give you a heterogeneous tuple, which feels... weird. But that consideration is pretty orthogonal to the proposed feature here.
I wouldn't say this is exactly "meaningfully simplified" (after all, how much can you simplify a divisibility check?)
I actually find the examples and the iseven analogy fairly compelling. I'd be on board with adding it to Base, for whatever that's worth.
I understand that the general idea is to keep Base as small as possible, but this seems like a small addition (we need just a few methods IMHO) with clearly defined meaning, so it is unlikely to ever change, meaning the maintenance burden should be close to none.
Anyway, I guess we should wait for other people's opinion? So far I haven't convinced many 😆
A closely related function which should be considered at the same time is LinearAlgebra.exactdiv. This function is currently used in
det_bareiss, but is important more generally for writing some linear algebra over a ring. The general meaning is that
exactdiv(a,b) should return c when in the ring where they lie we have a=b*c (in a field it is the same as /). This makes det_bareiss work over the integers, but more generally, work for example in polynomial rings, as soon as exactdiv is defined. Actually, it would be very useful that exactdiv returns a failure when exact division does not occur, like returning nothing (maybe more useful than raising an error). Anyway I think exactdiv and isdivisible should be considered at the same time with the idea that they could be expanded to any ring.
Isn't that what ÷ / div is supposed to do? That is, for integers a, b, c, if isdivisible(a, b), then a ÷ b (or div(a, b)) returns c such that a = b * c. I see that exactdiv in LinearAlgebra is even defined using div: https://github.com/JuliaLang/julia/blob/f1990e2e3d8d31087b23288f640ba16909f7ec7b/stdlib/LinearAlgebra/src/generic.jl#L1781
By the way, in case this function actually finds its way into Base – what should be the result of isdivisible(0, 0)? True, because 0 = 0 * x for any x? False, because nothing can be divided by zero? An error, because that's what iszero(rem(0, 0)) would do?
Isn't that what
÷/divis supposed to do?
@barucden div(5.0, 2.0) == 2, but 5.0 / 2.0 == 2.5 != 2. So presumably this exactdiv would be useful for generic programming.
I sometimes also needed a % b == 0 for a big int a and a machine int b. I think adding __gmpz_divisible_ui_p from @barucden 's discourse post to base/gmp.jl can be a nice thing in itself.
Though, in my opinion, for readability a % b == 0 can be preferable to isdivisible(a, b). I somehow get the order of arguments to isdivisible(a, b) wrong in half of the cases in other languages.
exactdiv differs from div in that
- In a ring, it is
divwhen divisibility occurs - In a ring, it would be good that it reports non-divisibiity (it is debatable if this should be by erroring or returning
nothing) - In a field, it is just
/
AbstractAlgebra.jl from Nemo implements this exact function
The implementation has since been improved, xref https://github.com/Nemocas/AbstractAlgebra.jl/pull/1871:
https://github.com/Nemocas/AbstractAlgebra.jl/blob/ca2faf2111c1894cd4bd1fdf26c1f35010cb9647/src/julia/Integer.jl#L121-L151
Just to say, one could also have rem(x::BigInt, y::T) where {T <: Integer} return a value of type T. That would still be type stable but be more efficient than what is currently being done.
Related: #53677 adds ispositive and isnegative. These functions are similar to isdivisible in the sense that the implementation can be very simple but not optimal for some types (like BigInt).