Reworking ComplexInfinity - #87
Open
PatrickHaecker wants to merge 5 commits into
Open
PatrickHaecker wants to merge 5 commits into
PatrickHaecker wants to merge 5 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #87 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 6 6
Lines 336 352 +16
=========================================
+ Hits 336 352 +16 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
PatrickHaecker
force-pushed
the
complex_direction
branch
from
September 6, 2026 15:58
3cefec7 to
8794edd
Compare
Closed
PatrickHaecker
force-pushed
the
complex_direction
branch
2 times, most recently
from
September 7, 2026 03:48
a5f3669 to
421714b
Compare
PatrickHaecker
force-pushed
the
complex_direction
branch
2 times, most recently
from
September 23, 2026 09:55
3e63d0d to
4991b0d
Compare
This was referenced Sep 23, 2026
Open
added 3 commits
September 25, 2026 10:46
A direction is an angle modulo a full turn, so the field now counts turns in units of 2^-64 and wraps where the circle does. Every `UInt64` names a direction and every direction has exactly one count, which the old half-turn field did not manage: half turns of 0.5 and 2.5 pointed the same way yet compared unequal and hashed apart. Wrapping also makes the group operation a machine add, and the resolution is uniform instead of thinning out towards a full turn. The count is what the constructor takes. An angle has to be rounded to reach it, so that step is now named at the call site with the `halfturns` keyword, and the old `ComplexInfinity(0.5)` is a `MethodError` rather than a silent reinterpretation. Most code needs neither form, since multiplying by `∞` takes the direction from the other operand: `im*∞` and `(1+im)*∞`. The element type carried no information about the value, only about how the direction had been spelled, so it is gone. It had been standing in for "this infinity lies on the real axis", and that was never what it meant: a zero angle written as a float pointed along the positive real axis just as `ComplexInfinity()` does, yet only the latter could be ordered or divided. The union types that used the parameter as a proxy now leave a `ComplexInfinity` out entirely, so the complex plane carries no order and takes no integer operation, exactly as `Base` treats a `Complex`. The count has 64 bits and an angle in a `Float64` has 53, so the two are kept apart wherever the difference shows. Equality and `hash` read the count, never the angle. `angle` reports on `Base`'s branch of `(-π, π]`. And `show` gives the readable `cispi(h)∞` only where that reads back, and the count itself otherwise, so every printed form evaluates to the value it came from.
Dropping the element type removed the only way to ask a `ComplexInfinity` whether it points along the real axis, and that question was worth keeping: it just belongs to the value rather than to the type. `isreal` takes it over, and `RealInfinity` converts a direction that lies on the axis and throws an `InexactError` otherwise, which is what `Real` does with a `Complex`. So an order or an integer operation is still reachable, by naming the conversion, and an infinity off the axis throws instead of quietly behaving as if the imaginary part were not there.
`Base` returns `NaN` where two reals have no result and `NaN + NaN*im` where either operand is complex, and this package only did half of that. Anything computed *from* a `NotANumber` already came back complex against a complex operand, but every place that *produced* one returned the real value, so `NaN * ComplexInfinity()` and `NotANumber() * ComplexInfinity()` disagreed. `_undefined` picks the value from the two operands and now stands wherever an undefined result is made: a `NaN` argument, a zero times an infinity, two infinities divided, and two infinities added in different directions. Only five of those sites can see a complex operand at all; for the rest the choice is decided at compile time and costs nothing.
PatrickHaecker
force-pushed
the
complex_direction
branch
from
September 25, 2026 08:52
4991b0d to
3411c1b
Compare
PatrickHaecker
marked this pull request as ready for review
September 25, 2026 08:57
Contributor
Author
|
Thanks, @dlfivefifty. So I rebased #87 on |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ok, this is my take on fixing the problems around
ComplexInfinityby storing the angle in anUInt64. With this, they should be accurate whenever the users wants them to be accurate. They should be bijective, so no more multiple internal representations which mean the same. No more different quantizations depending on where you are on the complex circle.So overall they should now behave as similar to
Baseas possible. However, it's not all perfect. TheangleFloat64interface which is important inBaseis not a natural fit to this representation and the conversion is not really what I would call elegant.Update:
Running through the issues and actually using the package showed up to be very useful. I fixed some inconsistencies and simplified the printing.
Details from the 🤖:
Makes a direction and a value the same thing. Two
ComplexInfinitys pointing the same way were not equal and did not hash alike:The representation
The field is a
UInt64counting turns in units of2^-64, wrapping where the circle does. Every count names exactly one direction, the group operation is a machine add, and the resolution is uniform around the circle.Most code needs no constructor, since multiplying by
∞takes the direction from the other operand:halfturnsand thex*∞forms go throughangle, so they round. They are exact on the axes and diagonals, butexp(im*π/8)*∞lands 256 counts past a sixteenth turn. Pass theUInt64where another direction has to be exact, and read it back withreinterpret(UInt64, x).Breaking
ComplexInfinity(0.5)exp(0.5*im*π)∞MethodError, usehalfturns = 0.5ComplexInfinity{Float64}ComplexInfinity, no parameter5 < ComplexInfinity()trueMethodErrordiv(ComplexInfinity(), 5)exp(false*im*π)∞MethodErrorangle(ComplexInfinity(halfturns = 1.5))4.71238898038469-1.5707963267948966angle(∞),angle(ℵ₀)00.0im*∞exp(0.5*im*π)∞0 + ∞*im∞ + im*∞NotANumber()∞ + ∞*imNaN * ComplexInfinity()NotANumber()NotANumber() + NotANumber()*imanglenow reports onBase's branch(-π, π]and always returns a float. Ordering and the integer operations are gone, because the complex plane has neither, exactly as forComplex.The type parameter is gone
It described how the direction had been spelled, never the value. Whether an infinity lies on the real axis is now asked of the value:
Consistent with
Base'sComplexOn the axes and diagonals each part is infinite or exactly zero, so addition works part by part as it does for
Complex. That makescomplex(x, y) == x + im*yhold with infinite parts too:Any other two different directions give an undefined result. These eight directions print as their parts, the way
Baseprints0.0 + Inf*im. Other directions print ascispi(h)∞where that reads back exactly, and as the count otherwise, so every printed form evaluates to the value it came from.Worth a look
Equality and
hashread the count, never the angle. The count has 64 bits and aFloat64angle has 53, so directions a float cannot tell apart must still compare unequal. Ordinary arithmetic reaches them:ComplexInfinity(halfturns = 0.5) * ComplexInfinity(halfturns = 2.0^-63).