Integer infinity

Consider a function that takes number of retries as a parameter. Number of retries should type check as int. But maybe you want to allow an infinite number of retries. You could accomplish this by annotating retries: int | float and passing retries=float("inf") or retries=math.inf, but it would be nice if you could just pass int("inf") and not need to change the type annotation. This is punctuated by the fact that int | float is actually too broad, you don’t want to accept 3.5 (though you don’t want to accept -3 either which is indeed an int…). You really want something like int | Literal[float("inf")] or Literal[math.inf], but these are not actually valid Literal constructions. I’m not sure if there is a literal inf in the language?

I wonder if this is a bad idea because infinities are not supported by the underlying integer data structure, but I’m not sure, and I figured this would be quick to litigate either way and I’m curious to see what people think. Maybe this could somehow be handled with existing typing features or a new typing feature.

Perhaps I’m missing a better way to type annotate a parameter that accepts positive integer or positive infinite numbers. One alternative would be to introduce an infinite sentinel so that you have retries = int | InfSentinel or retries = int | type(InfSentinel) depending on how the sentinel is implemented. Or you could use None as the sentinel with the major downside that readers would interpret retries=None to be the same as retries=0.

1 Like

I think integer infinity has been suggested a few times for various situations (IIRC also for min/max) but never made it into the language, because it’s just too esoteric, and there’s no good way to represent it except as a magical singleton that is treated special by both int and float objects.

Pragmatically, for your example, a max retries of 999_999_999 (or 1_000_000_000 if you’re into base-one :slight_smile: probably works just fine. :slight_smile:

2 Likes

sys.maxsize also often works well as a pragmatic “really big int”.

1 Like

There’s been float inf and -inf for a long while, and their semantics are as good as it gets.

The OP is asking for unlimited bounds for cases like min() and max(), which probably involves only comparisons.

The esoteric parts of floating point, that involve INF, -INF, NAN, 0, and -0, should be easier to be skipped for int.

Constants like MAXINT were present in Pascal and other languages. Python improved it all with integers not constrained by a number of bytes. But MAXINT is still missing.

Using 999_999_999 is not good enough for current computing power and dataset sizes (999_999_999 is less than 8_100_000_000, the current world polulation).

EDIT:

Another problem with 999_999_999 (or any larger int) is that it takes a considerable number of bytes for a comparison that would be simple if there was a MAXINT.

1 Like

This is actually directly discussed in PEP 586: PEP 586 – Literal Types | peps.python.org

The following are provisionally disallowed for simplicity. We can consider allowing them in future extensions of this PEP.

Floats: e.g. Literal[3.14]. Representing Literals of infinity or NaN in a clean way is tricky; real-world APIs are unlikely to vary their behavior based on a float parameter.

1 Like

MAXINT is equal to the highest possible integer. It is still a finite number. In Python, there is no such maximum (although as an implementation detail, the GMP library is limited to something of the order of 2**2**37 - but that number would require a stupid amount of RAM to represent), so it wouldn’t make sense to have a MAXINT.

A value of “infinity” (which, side note, doesn’t actually make sense mathematically; infinity is not a value, it’s a concept, and generally only shows up in places like limits) is one that compares greater than ANY finite integer or float. I’ve often used float(“inf”) for this purpose, though, and it’s usually not a problem to have that in a context where you otherwise are working with integers. In my opinion, this is really a typing question (“how can I annotate that this is either an integer or float(‘inf’)”) rather than a need for an actual integer that compares greater than every other integer.

3 Likes

I personally always used None for this purpose, i.e. retry_limit: int | None. This does ofcourse require extra code to handle, for example internally replacing None with float("inf"), but IMO it does read quite nicely.

1 Like

You may be interested in my implementation of “extended integers”. If you were to use this, you would annotate your parameters ExtendedIntegral and then you can:

  • simply compare elements to int_inf and -int_inf or do ordinary comparisons (counter < value), and
  • pass integers as before and have them support instance checks.

In my opinion, your sentinel idea is roughly the same, but has slightly poorer separation of concerns.

My personal feeling is that using a sentinel is better for readers than using a really large magic number.

I’m not sure this would be a great addition to Python since it’s a fairly rare problem. Nevertheless, if it were added to Python, I think it would have to be a new class that sits between Real and Integral. I don’t think it’s fair to expect every function that accepts an int to have to support a new “infinity” object.

3 Likes

Thanks for the context. Yeah, I don’t claim this isn’t esoteric. The 999_999_999 strategy would be a little annoying because it forces the caller to think: “I want this to retry so many times that it will never matter for my caller, what is the biggest number I can pick here?” when the intent (and code) is really: “I want to bypass the logic for breaking out of the retry loop”.

Yes, I think this is the right take. I don’t necessarily need integers to be extended in such a way that they can handle comparisons with infinity (though that would be nice), I’m still happy to special case if the caller passes in retries=int_inf within the function. I mostly just want a way to annotate like you said.

Renaming the parameter retry_limit instead of retries makes the None semantics better for this.

Nice. I think this does accomplish everything I’m looking for.

This seems reasonable to me. I suspect extending the regular integers to include infinity would possibly break existing code that doesn’t currently worry what would become a new type of edge case.

2 Likes

If you want a clean API, just use None and write the two extra lines of code in your implementation. That’s what everything taking a float timeout uses — not math.inf.

2 Likes

It’s fair. retries=None meaning infinite retries is confusing, you would interpret it as retries=0 so that approach didn’t immediately sound good. But renaming the parameter to max_retries=None is better because it’s kind of like saying “there is no limit to the number of retries”.

I figured the vetting on this would be quick. I think the linked extended integers pypi package does precisely what is being request, so if I or anyone really wants something with the semantics of finite integers + an infinite integer then they can use that.

Like a lot of the ideas here, I think the existence of the extended integers package shows that it is a useful feature that people would use, it is only a question of if the usefulness warrants inclusion, and the associated costs of that inclusion, in the standard library.

MAXINT would have to be a “magic value”. That’s the reason it is opposed as strongly.

Not sure what you mean. Is 2**N-1 a magic number? Or do you mean there’s some magic number that behaves like an infinity, which is completely different from MAXINT in concept? Or that there’s a magic number that, in the API in question, indicates “no limit”?

It makes as much sense as -1 or 0 do.

It is not a value of int, but it is a value of float.

And so is everything else that you implicitly could be calling a value.

Well, in floating point arithmetic they appear as results of cases of nearly all arithmetic operations.

It makes very good sense as a comparison sentinel though, since it compares greater than any finite value.

Thank you for quote-mining me. That is NOT WHAT I SAID. I said:

You split off the “mathematically”, and that completely changed the meaning of the quote. In mathematics, “infinity” most certainly is NOT a value, it is a concept, one which shows up in limits.

But other numbers really ARE values. Do you see the problem when you try to take my words out of context?

Is it that hard to read one entire sentence and respond to it? By breaking it into four pieces like this, you managed to completely miss everything I was saying.

At least you had the decency to do it all in one post, so people can see exactly what happened, But I still very much do not appreciate this.

1 Like

While this is not the case in the integers, there definitely are number systems where infinity is a number like all the others. But they are kinda esoteric and pythons integers do not model them.

If an “integer infinity” value was added it would just be a singleton like any other, so you might as well use None IMO.

2 Likes

Both the concept of value as member of a type, or the common-language notion (not a mathematical concept) of value as output of a function, or particularization of a quantified variable, etc, infinity can be both.

Either you have a special notion of “value”, or what you are saying is devoid of meaning. What do you call “value”? Note that I don’t think you have a special notion of value, only that I think you haven’t thought carefully whether it applies and are repeating mechanically an idea that have heard others say, but that is actually incorrectly expressed.

1 Like

You shouldn’t get irritated. You are really saying nonsense. As a mathematician, my intention is to educate about why that often repeated phrase “infinity is not a number” (or in this case “value”) is meaningless. So, it doesn’t bring any weight into a reasoning.

Having an two integer singleton values:

  • one greater than any other integer
  • one smaller than any other integer

would be very useful exactly from typing perspective. cast(int, a_float_inf_value) is the only workaround I know.

The use-cases I encountered where this would be very useful are loops with current min and current max values.

I’ve seen sys.maxsize being used, and it’s ugly and semantically incorrect.

As a curiosity, None in Python 2 used to be treated as “less than everything but itself”:

Summary
Python 2.7.18 (default, Oct 31 2023, 16:46:08)

In [1]: def compare_with_none(item):
   ...:     return item < None, item == None, item > None
   ...:

In [2]: compare_with_none(-1)
Out[2]: (False, False, True)

In [3]: compare_with_none(0)  # the same with `False`, naturally
Out[3]: (False, False, True)

In [4]: compare_with_none(1)  # the same with `True`, naturally
Out[4]: (False, False, True)

In [5]: compare_with_none("")
Out[5]: (False, False, True)

In [6]: compare_with_none("parrot")
Out[6]: (False, False, True)

In [7]: compare_with_none(None)
Out[7]: (False, True, False)

In [8]: compare_with_none(float("inf"))
Out[8]: (False, False, True)

In [9]: compare_with_none(float("-inf"))
Out[9]: (False, False, True)

(etc.)

In Python 3 all above < and > comparisons raise TypeError.

Acceptable alternatives have been pointed out. This has devolved into personal arguments.