Add missing Base methods - #82
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
7a439dd to
37c9c4a
Compare
|
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 |
37c9c4a to
b4e7c2c
Compare
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. |
`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.
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.
b4e7c2c to
c0eb0f2
Compare
|
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. |
|
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.
This PR builds on #81, so only the last
fivesix commits belong to this PR. Once #81 lands we can rebase this ontomasterto shrink the diff. I recommend reviewing commit by commit.Everything here is purely additive: each method below is a
MethodErrortoday.ComplexInfinitygainsabs,sign,conjand negation, where the<:Integermethods exist because the generic angle arithmetic would widenComplexInfinity{Bool}toComplexInfinity{Int64}.isintegerplusround,floor,ceilandtrunc, which also fixes∞ ∉ 1:5because a range asksisintegerbefore it compares./,\and//, of which\needs no method of its own becauseBasedefines it asy / x.rem,%anddivrem, where the threedivremmethods are needed becauseBase'sInteger-onlydivremcomputesa - div(a,b)*binstead of callingrem, which anInfiniteCardinalcannot evaluate.isapprox, becauseBasecompares only after promoting, which fails for an infinity and a number.The tests check an infinite tolerance against the float tolerance for every pair from a value list, skipping the one case where
Baseitself is wrong:isapprox(0, 0; rtol=Inf)isfalsebecause itsIntegermethod omits thex == yshort-circuit and so evaluatesmax(0, Inf*0).