Skip to content

Add missing Base methods - #82

Draft
PatrickHaecker wants to merge 10 commits into
JuliaMath:masterfrom
PatrickHaecker:base_methods
Draft

Add missing Base methods#82
PatrickHaecker wants to merge 10 commits into
JuliaMath:masterfrom
PatrickHaecker:base_methods

Conversation

@PatrickHaecker

@PatrickHaecker PatrickHaecker commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

This PR builds on #81, so only the last five six commits belong to this PR. Once #81 lands we can rebase this onto master to shrink the diff. I recommend reviewing commit by commit.

Everything here is purely additive: each method below is a MethodError today.

  1. ComplexInfinity gains abs, sign, conj and negation, where the <:Integer methods exist because the generic angle arithmetic would widen ComplexInfinity{Bool} to ComplexInfinity{Int64}.
  2. isinteger plus round, floor, ceil and trunc, which also fixes ∞ ∉ 1:5 because a range asks isinteger before it compares.
  3. /, \ and //, of which \ needs no method of its own because Base defines it as y / x.
  4. rem, % and divrem, where the three divrem methods are needed because Base's Integer-only divrem computes a - div(a,b)*b instead of calling rem, which an InfiniteCardinal cannot evaluate.
  5. isapprox, because Base compares only after promoting, which fails for an infinity and a number.
  6. harmonize with changes from Division with ∞ #68.

The tests check an infinite tolerance against the float tolerance for every pair from a value list, skipping the one case where Base itself is wrong: isapprox(0, 0; rtol=Inf) is false because its Integer method omits the x == y short-circuit and so evaluates max(0, Inf*0).

@codecov

codecov Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (13f9e1f) to head (109f7b2).

Additional details and impacted files
@@            Coverage Diff            @@
##            master       #82   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            6         6           
  Lines          266       301   +35     
=========================================
+ Hits           266       301   +35     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@dlfivefifty

Copy link
Copy Markdown
Member

Please do your PRs based on the master branch: it's very hard to see whats PR specific when the PR includes other PRs like the isinf one, which delays review/merging

@PatrickHaecker

Copy link
Copy Markdown
Contributor Author

Please do your PRs based on the master branch: it's very hard to see whats PR specific when the PR includes other PRs like the isinf one, which delays review/merging

Fair. I try to avoid it, but if a PR depends on changes in another PR I prefer it over doing a complicated rebase and two things being developed and tested separately which will non-trivially interact with each other.

I try to always state which commits are relevant and doing the review via the "Commits" tab works very well for me, because I try to cluster the changes around suitable commits either way.

Anyway, feel free to ignore PRs which build on top of other PRs until these other PRs are merged. It's just that I want to work on them while I have the problem and the code base in my mind (I guess context window is the term today) and then publish them to not forget about them. The additional advantage is that others can already use it and that they see that they don't need to work on a solution, because there already is one.

Patrick Häcker added 4 commits September 3, 2026 17:13
`Base` promotes every `Real` to `BigFloat`, so `Base._promote` handed the arithmetic
two `BigFloat`s and `__add`, `__mul` and `_infpow` no longer matched: `big(2.0) + ∞`,
`big(2.0) * ∞` and `(+∞)^big(2.0)` were all `MethodError`s.

`BigInt` was unaffected, the `Integer` rule catching it first, and so were `ℵ₀` and
`ComplexInfinity`, neither of which promotes to a float.
`NaN + ∞` gave `∞`, `NaN * ∞` gave `+∞`, `div(NaN, ∞)` gave `0.0` and `(+∞)^NaN` gave
`0.0`, where the same expressions over the floats all give `NaN`. An argument that was
not an infinity was treated as negligible, so a `NaN` marking a failed computation
silently became a plausible infinity.

Each entry point now returns its `NaN` argument unchanged, which keeps the precision as
well: `NaN32 * ∞ === NaN32`. `isnan` answers for every `Number` and folds to `false` for
the types that carry no `NaN`, so nothing changes for them. A float argument widens the
inferred return type by one union member and still allocates nothing.
For a float parameter it returned the field itself, so `signbit(ComplexInfinity(0.5))`
was `0.5` and any caller branching on it hit a `TypeError`. It now answers whether the
infinity points along the negative real axis, for every parameter type, which is what
the `Bool` and `Integer` methods already did and what `Base` guarantees.

One method replaces the three, as `mod(signbit, 2) == 1` covers them all. The two
places that wanted the whole angle rather than its sign take the field directly.
The three bugs found so far were all the same shape: an infinity behaving differently
from `Inf` in a case nobody had enumerated. The table asks each comparison and each of
`max` and `min` for the same answer as the matching float infinity, over a list of
values that includes both zeros, both `NaN` precisions, the subnormal and the largest
finite float.
@PatrickHaecker
PatrickHaecker marked this pull request as draft September 3, 2026 15:26
Patrick Häcker added 5 commits September 4, 2026 05:37
All four were `MethodError`s for an angle that is not a multiple of π: negation existed
only for an integer factor, and the other three not at all.

Negation and conjugation rotate and reflect the angle, reduced so that both stay
involutions; `abs` is `∞` whichever way the infinity points; `sign` is the unit vector,
`cispi` giving it exactly on the axes. The integer factor keeps its own methods, which
already answered in `Int` and are what `AllRealInfinities` relies on.
Both were `MethodError`s, which also made `∞ in 1:5` fail, a range asking `isinteger`
before it compares. `Inf` is not an integer and rounding leaves it alone, so an infinity
answers the same way and returns itself.

`InfiniteCardinal` is left out of both: it is an `Integer`, for which `Base` already
answers `true` and returns the value unchanged.
`∞ / 2` and `2 / ∞` were promotion errors, though `inv` was already there to build them
from: division is multiplication by the inverse, which brings the sign and the `NaN`
handling of `*` with it. `\` needs nothing of its own, `Base` defining it as `y / x`.

`∞ / ∞` answers `NotANumber`, as `div(∞, ∞)` and `mod(∞, ∞)` already do, rather than the
`NaN` of the floats. `2 / ∞` inherits the `Int` zero of `inv(∞)` where the floats give
`0.0`, which fixing `inv` will settle in one place. `Rational` and `Complex` need the same
explicit pairs in `ambiguities.jl` as the other operators.
`3 % ∞` was a promotion error, though `mod` and `div` were both already there. `rem` keeps
the sign of the dividend, so unlike `mod` it needs no bound: `-3 % ∞` is `-3`, where
`mod(-3, ∞)` is unbounded and says so. `divrem` follows from the two.

The other direction answers `NotANumber`, as `mod(∞, x)` and `div(∞, ∞)` do. `Rational`
and `BigInt` need the explicit pairs in `ambiguities.jl`, an `InfiniteCardinal` being an
`Integer` that `Base` has its own methods for.
`∞ ≈ Inf` threw, `Base` promoting its arguments before it compares them and an infinity
having no common type with a number. Nothing is near an infinity but an equal one, which
is what the floats say too, so approximate equality is exact equality and the keywords
have nothing to loosen.
@PatrickHaecker

Copy link
Copy Markdown
Contributor Author

After #81 has landed and after the corresponding rebase, this PR should also be ready. Test coverage is now again at 100%. I'll keep it at draft state to indicate that the rebase is missing.

@PatrickHaecker

Copy link
Copy Markdown
Contributor Author

Sorry, I have just realized that #68 already contains some of the changes done here. I try to harmonize them, but I am not yet sure what the best way is.

Gaps that JuliaMath#68 covers and this branch did not, plus the types they missed.

`round(x, ::RoundingMode)` was a MethodError, and `round(x; digits)` fell
through to `Base` and returned `Inf` rather than the infinity, disagreeing with
the plain `round(x)` next to it. `isinteger` and the four rounding functions
also answered for `Infinity` and `RealInfinity` alone, so a `ComplexInfinity`
raised a MethodError where `Base` answers `false` and the value itself for the
matching `Complex`, and `ℵ₀` took a rounding mode but not the keywords.

`float(::ComplexInfinity)` was a MethodError, the real infinities having got
theirs from the `AbstractFloat` conversion. JuliaMath#68 proposes `exp(im*angle(x))*Inf`,
which is unsound: `0 * Inf` is a `NaN`, so `float(ComplexInfinity())` gives
`Inf + NaN*im`, and the imaginary and negative real axes come back as diagonals.
`cospi`/`sinpi` are exact at the half-integers, so building the parts from them
keeps the axes exact. Two saturating parts can express only eight rays, so an
angle off them lands on the nearest one, which the test pins.
@PatrickHaecker
PatrickHaecker marked this pull request as ready for review September 4, 2026 07:31
@PatrickHaecker

Copy link
Copy Markdown
Contributor Author

Ok, I think this is a reasonable PR now with, for all I know, all changes from #68 included which fit into this PRs theme. The rest of #68 fits into other PRs which I have on my agenda.

@PatrickHaecker
PatrickHaecker marked this pull request as draft September 4, 2026 15:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants