PEP 791: imath --- module for integer-specific mathematics functions

Open Issues:

  • module name (imath, intmath, ntheory, integermath, imaths, ???); poll is here
  • possible extensions (some guideline to accept/reject potential module content)

Previous discussions:

12 Likes

From the PEP text, the only motivation I see listed is the complexity of the existing module (which I guess implicitly explains why the new functions aren’t simply added to math). Is this accurate? Or is there some other reason why this would be useful as its own module?

My suspicion right now is that this proposal is ranking purity over pragmatism, but I’m willing to believe there are pragmatic reasons to move functionality to a different place - those reasons need to be in the PEP text though :wink:

Could you add a submodule (e.g. math.integer) as an option? Either selected or rejected I don’t mind, but it is the usual way that we reduce implementation complexity,[1] so it’s a notable omission from the proposal.


  1. When that complexity is described using LOC. ↩︎

8 Likes
  • module name (imath, intmath, ntheory, integermath, imaths, ???)

Given the existence of cmath, I can see how naming it imath makes sense.

Historically, the i prefix has (mostly?) been used for “iterative” things, for example itertools.islice, and itertools.izipon Python 2. But given the (*math) context, I don’t expect that this is likely to become a common source of confusion, especially for those that already know about cmath.

Falling and rising factorials (wiki) could be handy to have. In the (underrated) statistical field of L-moments, they tend to pop up all over the place (see e.g. Distributions - Lmo). And in discrete calculus the falling factorial plays a crucial role in discrete derivatives.

The multinomial coefficient might also make for a nice extension, as well as the Lah numbers, Catalan numbers, and the Stirling numbers of the first and second kind.

4 Likes

I would just like to suggest the name zmath. This would be consistent with set theory notation of \mathbb{C} for complex (cmath), and \mathbb{Z} for integers (zmath). Google, Bing, Duckduckgo all point to integers with the search query z mathematics or z math. The same can’t be said for i math, which promptly surfaces imaginary numbers. There’s a pypi package named ZMATH, which is not reachable.

I don’t feel strongly about this. It is just a suggestion as the question of naming is open.

13 Likes

Sorry, but this looks like it will just make things backwards incompatible with very little benefit.

The math module seems like a fine home for these, since they are for doing (certain types of) math, and new Python users probably have no idea of what C’s libmath even is. In comparison, the cmath module is separate for a very different reason – it provides some of the same functions but implemented for complex numbers (we chose to do it this way rather than just returning a complex from e.g. math.sqrt(-1) since users of math were used to getting an error for that).

Separating things now, at this late stage, just causes pain for users who are upgrading without benefits. And keeping the aliases in math forever is even less ideal.

24 Likes

Previous discussion: Split out integer functions from the math module

1 Like

That’s acceptable to me. But, in return, I never again want to hear the decades-old argument “but we can’t add that to math - that’s for functions on floats!” :frowning:

There are large areas of “just math” that all look the same to newbies, but are very different to experienced practitioners. That’s presumably why we have a statistics module. Number theory in turn has a large number of exact integer-argument functions that have nothing material in common with the float-domain functions.

All currently-working code would continue to work as before. No changes needed.

There is no pure win to be had here. The intent is to stop the conceptual incoherence of the current math module from getting ever worse over time. Of course “coherence” depends on one’s experience. Failing to draw distinctions that are glaringly obvious to the more experienced is a form of incoherence to them.

The number-theory functions don’t belong in math any more than, e.g., it would have made conceptual sense to have added limit_denominator() to math, as a function that only accepts a Fraction argument, instead of (as was done) making it a method of Fraction. That’s all “just math” too. The functions in question here only accept integer arguments, and in a purely OO language would be methods of int.

Luckily, Python will survive either way :smiley:

13 Likes

Just like others I don’t understand the impulse of doing this.

The PEP claims:

over time the module was populated with functions that aren’t related to the C standard or floating-point arithmetics. Now it’s much harder to describe module scope, content and interfaces (returned values or accepted arguments).

Well, it’s not really harder. Just change the docs to point that the module provides mathematical functions (which is obvious from its name, anyway, and much more understandable to the average Python developer than some mythical beast named “the C standard”).

9 Likes

1j**2 on this :slight_smile:

17 Likes

They’re already implemented with some care for bigint speed, although that may not be obvious at first glance:

>>> import math
>>> math.perm(7, 2)
42

So that’s the falling factorial of 7 for 2 terms, or, equivalently, the rising factorial of 6 for 2 terms.

>>> math.log10(math.perm(1000000, 1000))
5999.782997596272

That goes fast! Python will never be speed-competitive with GMP for such things, though.

3 Likes

Random thought: If these functions are all associated to the int type, why not add them as int methods? For example:

>>> (15).isqrt()
3
>>> (4).factorial()
24
>>> (5).choose(3)  # I believe humans tend to pronounce `comb` this way
10

And for what it’s worth, it’s pretty similar to how Rust deals with integer maths:

let n: i32 = 15;
assert_eq!(n.isqrt(), 3);

playground

1 Like

Any and all additions to this collection should probably go into third-party packages hosted on PyPI anyway.

2 Likes

That was done for int.bit_length() and int.bit_count(). There’s no consistent “logic” to this. At heart, Python is not, and does not aspire to be, a purely OO language. There are centuries of literature and prior art that “spell” the high-use integer functions (like gcd(), factorial(), etc) in some form of functional notation. object.method() notation is a new-fangled thing and not necessarily an improvement for usability :wink:.

3 Likes

When this module was first proposed 7 years ago, there were only two integer-related functions in math: factorial() and gcd(). It would be much easier to add it at that time. Since then, 4 new functions were added, factorial() stopped supporting floats, gcd() was extended. Now, implementation of these 6 functions takes 1/3 of the math module volume.

Several functions that I planned to add to the new module have not yet been added to math, partially because it is difficult to support them intermixed with floating point functions in one file:

  • ilog() – determines the number of digits of a number in a given number system.
  • as_integer_ratio() – represents a number as an integer ratio.
  • Integer division with rounding up and to the nearest integer. Useful for datetime and fractions.
  • isprime() predicate and primes() generator.
  • Variants of isqrt() and ilog() with rounding up.

The more we delay adding a new module, the higher the price.

16 Likes

That’s an odd meaning for “batteries included” :wink:. The integer-domain functions find increasing use in a world where crypto- and AI-related technologies attract ever-increasing billions of dollars worth of attention.

CPython versions will never be speed-competitive with the fastest out there, but I’m very sympathetic too to the “merely curious” who want to putz around with their own code experiments. Some non-trivial level of “batteries included” is a large part of what made Python fun to use. Not just the language, but the broad coverage of the out-of-the-box modules. CPython’s developers put a lot of care into designing & building those too, and the quality shows.

Python is also “a brand” now too, and inclusion in the core distribution is an assurance of quality few 3rd-party packages can hope to offer.

Need Stirling numbers of the second kind? SciPy offers it, but that’s such a large system last time I needed them I wrote one myself.

Not saying Python needs stirling2(n, k), but am saying I don’t reject the idea without a hearing. If a case can be made for it, it certainly doesn’t belong in math, though :wink:.

15 Likes

At the very least, pull the int functions out of mathmodule.c and into their own file. It can simply be #included.

Since very few people work on this code, they can’t be expected to grasp the pragmatics. In part, in some respects it takes a very different mindset to work on exact integer functions than approximate float ones, and it’s an unhelpful distraction for that reason alone to have such very differerent kinds of code in the same file.

5 Likes

No. It’s only minor point, the last one in the Motivation section. You could say it’s something implementation-dependent: i.e. Java’s BigInteger class has sqrt() and gcd() for free, GraalPy probably will just call that stuff.

Yes! The main motivation is that these functions looks very different wrt the rest of the module. Other return values, other convention for processing arguments.

Last but not least — a different application domain. I rarely need libm’s-borrowed functions when I import number-theoretic stuff from the math module and vice versa. (So, doing from math import * just pollute my namespace with unwanted stuff either way. Same for math.<tab> in IDE/CLI. Though, this seems as issues mostly while interactive work.)

Thanks, I’ll do. In the end, this will look as a different option for naming. And that remove possible conflicts with external projects.

4 Likes

Obviously, any function, that satisfy two constraints (accept integers, compute results exactly) — is a possible candidate.

I would expect a different border line: if we need really advanced algorithms to do things with reasonable time/space constraints — that’s rather belongs to some specialized package on the PyPI.

As a small addition to Tim’s comment, I could say that functions in the proposed module will work also for integer-like objects (those, that have the __index__ special method).

3 Likes

Concrete suggestions are welcome.

Which mathematical functions? The cmath or statistics modules have them a lot!

As a bit of off-topic, did you consider to have some global flag(s) to control this? E.g. gmpy2 or mpmath have a context notion. In gmpy2, where the CPython’s counterpart have ValueError for domain error — you can get either nan/inf (per C standard), or an exception, or a complex result:

>>> import gmpy2
>>> gmpy2.sqrt(-1)
mpfr('nan')
>>> gmpy2.get_context().trap_invalid=True
>>> gmpy2.sqrt(-1)
Traceback (most recent call last):
  File "<python-input-4>", line 1, in <module>
    gmpy2.sqrt(-1)
    ~~~~~~~~~~^^^^
gmpy2.InvalidOperationError: invalid operation
>>> gmpy2.get_context().allow_complex = True
>>> gmpy2.sqrt(-1)
mpc('0.0+1.0j')

I can’t add much more to Tim’s and Serhyi’s comments. My 2c:

IIUIC, the “soft deprecation” doesn’t mean we will have to keep them forever. Eventually, we can deprecate these aliases. So, I doubt that backward compatibility or maintenance burden to keep aliases are good arguments against.

How about suggested by Steve submodule, say math.ntheory? The math module holds “catch-all” role for math, but integer-specific functions will have own namespace?

3 Likes