Add cmath.isnormal() and cmath.issubnormal() (original) (raw)
python 3.15 adds math.isnormal() and math.issubnormal(). I was wondering if we should add cmath.isnormal()
and cmath.issubnormal()
too.
There are 2 possible implementations:
- .NET:
def isnormal(z):
return math.isnormal(z.real) and (z.imag == 0 or math.isnormal(z.imag))
def issubnormal(z):
return math.issubnormal(z.real) or math.issubnormal(z.imag)
- Swift:
def isnormal(z):
return isfinite(z) and (math.isnormal(z.real) or math.isnormal(z.imag))
def issubnormal(z):
return math.issubnormal(z.real) and math.issubnormal(z.imag)
If you have complex numbers with subnormal components, you lose performance and accuracy:
- performance:
$ python -m timeit -s "a = 2+0j; b = 5e-308+5e-308j" "a * b"
5000000 loops, best of 5: 49.1 nsec per loop
$ python -m timeit -s "a = 2+0j; b = 5e-308+5e-324j" "a * b"
5000000 loops, best of 5: 88.3 nsec per loop
$ python -m timeit -s "a = 2+0j; b = 5e-324+5e-324j" "a * b"
2000000 loops, best of 5: 124 nsec per loop
- accuracy:
>>> (5e-308+5e-308j) * 1e308
(5+5j)
>>> (5e-308+5e-324j) * 1e308
(5+4.9406564584124655e-16j)
>>> (5e-324+5e-324j) * 1e308
(4.9406564584124655e-16+4.9406564584124655e-16j)
Previous discussion: Have math.isnormal() and, perhaps, math.issubnormal()? · Issue #132908 · python/cpython · GitHub
pf_moore (Paul Moore) June 7, 2025, 1:25pm 2
I don’t have any personal need for these functions, but it seems to me that if there’s no standard definition for what they should do on complex numbers, it’s better to let the user write their own than to pick a definition which might not match what the user is expecting…
skirpichev (Sergey B Kirpichev) June 7, 2025, 1:41pm 3
My 2c.
As we have more than two definitions — that means it’s not about mathematics. (In fact, we could invent even more: e.g. permit zeros in either component of the “normal” complex number with the second component — being a normal float.)
These helpers will be just “short-cuts”, that now are possible, using math’s functions on components of the complex number. The questions are:
- are they useful like isnan/isinf?
- which one definition we should chose?
I think isnormal/issubnormal notions don’t make sense in usual complex arithmetic (pair of IEEE floating-point numbers, etc). Maybe in some positional numeral system with imaginary base like 2j
.
MegaIng (Cornelius Krupp) June 7, 2025, 1:42pm 4
Are there real world examples of people using this kind of function? In those examples, which definition is useful?
The fact that there are competing definitions could indicate that this is something tricky to get right, so it might be useful to provide in the stdlib, assuming that one definition is clearly more correct than the other.
Nineteendo (Nice Zombies) June 7, 2025, 2:03pm 5
That’s tricky to figure out, checking for this is not very common. I mostly brought this up for being able to switch from math
to cmath
and because this wasn’t discussed yet.
Wombat (Zeke W) June 7, 2025, 4:37pm 6
Please, no.
A user can already write, issubnormal(abs(z))
and have well-defined obvious code. But if cmath adds these functions, a reader has to guess how it it interpreted, perhaps issubnormal(z.real) or issubnormal(z.imag)
. Note, this is the same reason that complex numbers are not orderable; we could invent an ordering but that doesn’t make it obvious or useful.
Also, the case for adding math.issubnormal()
was already weak. It wasn’t done because our users needed it; it was done because C did it. Mostly, these functions will just clutter the math library but not be used (do you have a needed, not invented, use case?). Let’s not double down by also extending this to cmath.
Nineteendo (Nice Zombies) June 7, 2025, 5:02pm 7
When would you want just the length not to be subnormal though? You already lose precision and accuracy with a single subnormal component.
Wombat (Zeke W) June 7, 2025, 11:03pm 8
Can you point to any real world examples of where a project needed this? Have any complex number libraries implemented this?
ISTM that this thread was inspired by “math has this so cmath should too” rather than any demonstrated need. That is why I think this would just be clutter.
skirpichev (Sergey B Kirpichev) June 8, 2025, 2:27am 9
Yep. Another one, this time “obvious” definition.
Actually, it was. The issue thread lists some examples.
It’s a good motivation by itself.
Nineteendo (Nice Zombies) June 8, 2025, 5:58am 10
Given the number of people that agree with you, I don’t think we should add this function.
Thanks to everyone for chiming in.
tim.one (Tim Peters) June 8, 2025, 7:36am 11
As a practical matter, testing for subnormals is very rare for apps using float64
(or wider). The need is far more acute when using float32
, because their dynamic range is so much narrower and they have so much less precision to begin with.
Now Python is well served by supplying float.subnormal()
(etc) anyway, because they’re defined by standard C and so very widely implemented. Even if they are essentially useless in real life . There’s practical use for them when working with
float32
, and C always supplies all floating-point math functions for all widths, for “consistency”.
But best I can tell there is no respected standard that even tries to define what they “should mean” for a complex type. Python should be a follower here, not a leader,
That said, .NET’s definition makes more sense to me than Swift’s. “Subnormal” means “I’ve lost full precision, but not yet enough to underflow to 0” - not necessarily fatal but an alarm bell. For a complex type full precision has been lost if either component has lost precision. That’s an alarm bell.
So my preferences:
- Don’t add them unless/until C does.
- If we “have to” add them, follow .NET rather than Swift.
storchaka (Serhiy Storchaka) June 8, 2025, 8:24am 12
The .NET implementation has fatal flaws:
isnormal()
is not symmetric.isnormal(complex(x, y))
!=isnormal(complex(y, x))
.issubnormal()
can return true for infinity and NaN, whileisnormal()
always return false for them.
The Swift implementation is at least more consistent. BTW, the real Swift implementation of issubnormal()
contains explicit condition that the argument is not complex zero. It is only needed if isnormal()
for floats in Swift differs from isnormal()
in C.
The other consistent implementation is:
def isnormal(z):
return math.isnormal(z.real) and math.isnormal(z.imag)
def issubnormal(z):
return isfinite(z) and (math.issubnormal(z.real) or math.issubnormal(z.imag))
But we have no good reasons to prefer one or other. We do not even have good reasons to reject infinity and NaN, or to not make a special case for zero, so there are much more possible implementations.
skirpichev (Sergey B Kirpichev) June 8, 2025, 8:35am 13
Net implementation looks slightly different than announced in this thread:
public static bool IsNormal(Complex value)
{
// much as IsFinite requires both part to be finite, we require both
// part to be "normal" (finite, non-zero, and non-subnormal) to be true
return double.IsNormal(value.m_real)
&& ((value.m_imaginary == 0.0) || double.IsNormal(value.m_imaginary));
}
Nineteendo (Nice Zombies) June 8, 2025, 10:09am 14
It allows the imaginary component to be zero to handle floats like expected:
>>> cmath.isnormal(1e-308)
True
nedbat (Ned Batchelder) June 8, 2025, 10:23am 15
I have only a vague idea of what isnormal
and issubnormal
mean, and reading the docs gives two very brief and circular definitions. If someone wants to, it would be great to update the docs to be a little more helpful, even by linking to some external authority about what these functions are for.
skirpichev (Sergey B Kirpichev) June 8, 2025, 1:35pm 16
Are you talking only about table entries? I appreciate better descriptions, but documentation texts don’t look circular for me:
isnormal(x): Return
True
if x is a normal number, that is a finite nonzero number that is not a subnormal (see issubnormal()). ReturnFalse
otherwise.
issubnormal(x): Return
True
if x is a subnormal number, that is a finite nonzero number with a magnitude smaller than the smallest positive normal number, see sys.float_info.min. ReturnFalse
otherwise.
I think one cross-reference is fine.
nedbat (Ned Batchelder) June 8, 2025, 3:52pm 17
Perhaps it’s hard to see how these paragraphs read to someone who doesn’t know what a “normal” number is. isnormal
is defined as something that isn’t subnormal. issubnormal
refers to normal numbers. The reference is to sys.float_info.min
which says “The minimum representable positive normalized float.”
Where is the actual definition of what a normalized float is? Sure, I can use a search engine to look up the terms, but it could be helpful to briefly explain what it is and why I might want to know if a number is normal or not.
To someone curious about what is in the math module, reading the current descriptions sound like, “if you have to ask what it means, you don’t need it.”
skirpichev (Sergey B Kirpichev) June 8, 2025, 4:36pm 18
Sorry, I’m not a native English speaker and maybe you can rephrase the issubnormal() description better.
Though, I don’t see how it refers to the normal number notion. Rather, it refers to “the smallest positive normal number, see sys.float_info.min”. Perhaps, instead of “see” I should use “i.e.”. But the intention was: a reference to emphasized term (which was explained after comma), not to “normal number”.
tim.one (Tim Peters) June 8, 2025, 4:41pm 19
Which, alas, is largely the truth! It can’t be explained usefully without knowing “a lot” about how floating-point numbers are represented, and no newbie has that background to build on. We could say, e.g., that “normal” means it’s “a finite non-zero float stored with maximum possible machine precision”, but then that relies on ever more “if you have to ask ,” mysteries.
Reference docs aren’t meant to be tutorials, and explaining basic concepts of fp representation would be as out of place as, e.g., bloating the docs with explanations about what math.gamma()
does. 'If you have to ask …" applies there too.