I searched usages of is_integer() on GitHub and have found that it is used *only* in silly code like (x/5).is_integer(), (x**0.5).is_integer() (or even (x**(1/3)).is_integer()) and in loops like: i = 0 while i < 20: if i.is_integer(): print(i) i += 0.1 (x/5).is_integer() is an awful way of determining the divisibility by 5. It returns wrong result for large integers and some floats. (x % 5 == 0) is a more clear and reliable way (or PEP 8 compliant (not x % 5)). Does anybody know examples of the correct use of float.is_integer() in real programs? For now it looks just like a bug magnet. I suggest to deprecate it in 3.7 or 3.8 and remove in 3.9 or 3.10. If you even need to test if a float is an exact integer, you could use (not x % 1.0). It is even faster than x.is_integer().
Does anybody know examples of the correct use of float.is_integer() in real programs? For now it looks just like a bug magnet. I suggest to deprecate it in 3.7 or 3.8 and remove in 3.9 or 3.10.
+1 It really doesn’t appear to be the right solution for any problem. -CHB -- Christopher Barker, Ph.D. Oceanographer Emergency Response Division NOAA/NOS/OR&R (206) 526-6959 voice 7600 Sand Point Way NE (206) 526-6329 fax Seattle, WA 98115 (206) 526-6317 main reception Chris.Barker@noaa.gov
I searched 6M LoC of Python code at Dropbox and found only three uses. They seem legit. Two are about formatting a number that's given as a float, deciding whether to print a float as 42 or 3.14. The third is attempting a conversion from float to integer where a non-integer must raise a specific exception (the same function also supports a string as long as it can be parsed as an int). I don't doubt we would get by if is_integer() was deprecated. On Wed, Mar 21, 2018 at 3:31 AM, Chris Barker <chris.barker@noaa.gov> wrote:
Does anybody know examples of the correct use of float.is_integer() in real programs? For now it looks just like a bug magnet. I suggest to deprecate it in 3.7 or 3.8 and remove in 3.9 or 3.10.
+1
It really doesn’t appear to be the right solution for any problem.
-CHB --
Christopher Barker, Ph.D. Oceanographer
Emergency Response Division NOAA/NOS/OR&R (206) 526-6959 voice 7600 Sand Point Way NE (206) 526-6329 fax Seattle, WA 98115 (206) 526-6317 main reception
Chris.Barker@noaa.gov
_______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/ guido%40python.org
-- --Guido van Rossum (python.org/~guido)
On Wed, Mar 21, 2018 at 3:08 PM, Guido van Rossum <guido@python.org> wrote:
I searched 6M LoC of Python code at Dropbox and found only three uses. They seem legit. Two are about formatting a number that's given as a float, deciding whether to print a float as 42 or 3.14. The third is attempting a conversion from float to integer where a non-integer must raise a specific exception (the same function also supports a string as long as it can be parsed as an int).
I don't doubt we would get by if is_integer() was deprecated.
Since code that's been deleted can't have bugs, +1.
Here's an excerpted (and slightly simplified for consumption here) usage of float.is_integer() from the top of a function which does some convolution/filtering in a geophysics application. I've mostly seen it used in guard clauses in this way to reject either illegal numeric arguments directly, or particular combinations of arguments as in this case: def filter_convolve(x, y, xf, yf, stride=1, padding=1): x_out = (x - xf + 2*padding) / stride + 1 y_out = (y - yf + 2*padding) / stride + 1 if not (x_out.is_integer() and y_out.is_integer()): raise ValueError("Invalid convolution filter_convolve({x}, {y}, {xf}, {yf}, {stride}, {padding})" .format(x=x, y=y, xf=xf, yf=yf, stride=stride, padding=padding)) x_out = int(x_out) y_out = int(y_out) # ... Of course, there are other ways to do this check, but the approach here is obvious and easy to comprehend.
I'd prefer to see `float.is_integer` stay. There _are_ occasions when one wants to check that a floating-point number is integral, and on those occasions, using `x.is_integer()` is the one obvious way to do it. I don't think the fact that it can be misused should be grounds for deprecation. As far as real uses: I didn't find uses of `is_integer` in our code base here at Enthought, but I did find plenty of places where it _could_ reasonably have been used, and where something less readable like `x % 1 == 0` was being used instead. For evidence that it's generally useful: it's already been noted that the decimal module uses it internally. The mpmath package defines its own "isint" function and uses it in several places: see https://github.com/fredrik-johansson/mpmath/blob/2858b1000ffdd8596defb50381d.... MPFR also has an mpfr_integer_p predicate: http://www.mpfr.org/mpfr-current/mpfr.html#index-mpfr_005finteger_005fp. A concrete use-case: suppose you wanted to implement the beta function ( https://en.wikipedia.org/wiki/Beta_function) for real arguments in Python. You'll likely need special handling for the poles, which occur only for some negative integer arguments, so you'll need an is_integer test for those. For small positive integer arguments, you may well want the accuracy advantage that arises from computing the beta function in terms of factorials (giving a correctly-rounded result) instead of via the log of the gamma function. So again, you'll want an is_integer test to identify those cases. (Oddly enough, I found myself looking at this recently as a result of the thread about quartile definitions: there are links between the beta function, the beta distribution, and order statistics, and the (k-1/3)/(n+1/3) expression used in the recommended quartile definition comes from an approximation to the median of a beta distribution with integral parameters.) Or, you could look at the SciPy implementation of the beta function, which does indeed do the C equivalent of is_integer in many places: https://github.com/scipy/scipy/blob/11509c4a98edded6c59423ac44ca1b7f28fba1fd... In sum: it's an occasionally useful operation; there's no other obvious, readable spelling of the operation that does the right thing in all cases, and it's _already_ in Python! In general, I'd think that deprecation of an existing construct should not be done lightly, and should only be done when there's an obvious and significant benefit to that deprecation. I don't see that benefit here. -- Mark
I've been using and teaching python for close to 20 years and I never noticed that x.is_integer() exists until this thread. I would say the "one obvious way" is less than obvious. On the other hand, `x == int(x)` is genuinely obvious... and it immediately suggests the probably better `math.isclose(x, int(x))` that is what you usually mean. On Wed, Mar 21, 2018, 2:08 PM Mark Dickinson <dickinsm@gmail.com> wrote:
I'd prefer to see `float.is_integer` stay. There _are_ occasions when one wants to check that a floating-point number is integral, and on those occasions, using `x.is_integer()` is the one obvious way to do it. I don't think the fact that it can be misused should be grounds for deprecation.
As far as real uses: I didn't find uses of `is_integer` in our code base here at Enthought, but I did find plenty of places where it _could_ reasonably have been used, and where something less readable like `x % 1 == 0` was being used instead. For evidence that it's generally useful: it's already been noted that the decimal module uses it internally. The mpmath package defines its own "isint" function and uses it in several places: see https://github.com/fredrik-johansson/mpmath/blob/2858b1000ffdd8596defb50381d.... MPFR also has an mpfr_integer_p predicate: http://www.mpfr.org/mpfr-current/mpfr.html#index-mpfr_005finteger_005fp.
A concrete use-case: suppose you wanted to implement the beta function ( https://en.wikipedia.org/wiki/Beta_function) for real arguments in Python. You'll likely need special handling for the poles, which occur only for some negative integer arguments, so you'll need an is_integer test for those. For small positive integer arguments, you may well want the accuracy advantage that arises from computing the beta function in terms of factorials (giving a correctly-rounded result) instead of via the log of the gamma function. So again, you'll want an is_integer test to identify those cases. (Oddly enough, I found myself looking at this recently as a result of the thread about quartile definitions: there are links between the beta function, the beta distribution, and order statistics, and the (k-1/3)/(n+1/3) expression used in the recommended quartile definition comes from an approximation to the median of a beta distribution with integral parameters.)
Or, you could look at the SciPy implementation of the beta function, which does indeed do the C equivalent of is_integer in many places: https://github.com/scipy/scipy/blob/11509c4a98edded6c59423ac44ca1b7f28fba1fd...
In sum: it's an occasionally useful operation; there's no other obvious, readable spelling of the operation that does the right thing in all cases, and it's _already_ in Python! In general, I'd think that deprecation of an existing construct should not be done lightly, and should only be done when there's an obvious and significant benefit to that deprecation. I don't see that benefit here.
-- Mark
_______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/mertz%40gnosis.cx
On Wed, Mar 21, 2018 at 11:14 AM, David Mertz <mertz@gnosis.cx> wrote:
I've been using and teaching python for close to 20 years and I never noticed that x.is_integer() exists until this thread. I would say the "one obvious way" is less than obvious.
On the other hand, `x == int(x)` is genuinely obvious... and it immediately suggests the probably better `math.isclose(x, int(x))` that is what you usually mean.
We can argue about this forever, but I don't think I would have come up with that either when asked "how to test a float for being a whole number". I would probably have tried "x%1 == 0" which is terrible. I like to have an API that doesn't have the pitfalls of any of the "obvious" solutions that numerically naive people would come up with, and x.is_integer() is it. Let's keep it. -- --Guido van Rossum (python.org/~guido)
[David Mertz]
I've been using and teaching python for close to 20 years and I never noticed that x.is_integer() exists until this thread.
Except it was impossible to notice across most of those years, because it didn't exist across most of those years ;-)
I would say the "one obvious way" is less than obvious.
When it was introduced, it _became_ the one obvious way.
On the other hand, `x == int(x)` is genuinely obvious..
But a bad approach: it can raise OverflowError (for infinite x); it can raise ValueError (for x a NaN); and can waste relative mountains of time creating huge integers, e.g.,
int(1e306) 1000000000000000017216064596736454828831087825013238982328892017892380671244575047987920451875459594568606138861698291060311049225532948520696938805711440650122628514669428460356992624968028329550689224175284346730060716088829214255439694630119794546505512415617982143262670862918816362862119154749127262208
In Python 2, x == math.floor(x) was much better on the latter count, but not in Python 3 (math.floor used to return a float, but returns an int now). As to Serhiy's `not x % 1.0`, after 5 minutes I gave up trying to prove it's always correct. Besides infinities and NaNs, there's also that Python's float mod can be surprising:
(-1e-20) % 1.0 1.0
There isn't a "clean" mathematical definition of what Python's float % does, which is why proof is strained. In general, the "natural" result is patched when and if needed to maintain that x == y*(x//y) + x%y is approximately true. The odd % result above is a consequence of that, and that (-1e-20) // 1.0 is inarguably -1.0.
and it immediately suggests the probably better `math.isclose(x, int(x))` that is what you usually mean.
Even in some of the poor cases Serhiy found, that wouldn't be a lick better. For example, math.isclose(x/5, int(x/5)) is still a plain wrong way to check whether x is divisible by 5.
x = 1e306 math.isclose(x/5, int(x/5)) True x/5 == int(x/5) True int(x) % 5 3
The problem there isn't how "is it an integer?" is spelled, it's that _any_ way of spelling "is it an integer?" doesn't answer the question they're trying to answer. They're just plain confused about how floating point works. The use of `.is_integer()` (however spelled!) isn't the cause of that, it's a symptom.
On Wed, Mar 21, 2018 at 3:02 PM, Tim Peters <tim.peters@gmail.com> wrote:
[David Mertz]
I've been using and teaching python for close to 20 years and I never noticed that x.is_integer() exists until this thread.
Except it was impossible to notice across most of those years, because it didn't exist across most of those years ;-)
That's probably some of the reason. I wasn't sure if someone used the time machine to stick it back into Python 1.4.
On the other hand, `x == int(x)` is genuinely obvious..
But a bad approach: it can raise OverflowError (for infinite x); it can raise ValueError (for x a NaN);
These are the CORRECT answers! Infinity neither is nor is not an integer. Returning a boolean as an answer is bad behavior; I might argue about *which* exception is best, but False is not a good answer to `float('inf').is_integer()`. Infinity is neither in the Reals nor in the Integers, but it's just as much the limit of either. Likewise Not-a-Number isn't any less an integer than it is a real number (approximated by a floating point number). It's NOT a number, which is just as much not an integer.
and can waste relative mountains of time creating huge integers, e.g.,
True enough. But it's hard to see where that should matter. No floating point number on the order of 1e306 is sufficiently precise as to be an integer in any meaningful sense. If you are doing number theory with integers of that size (or larger is perfectly fine too) the actual test is `isinstance(x, int)`. Using a float is just simply wrong for the task to start with, whether or not those bits happen to represent something Integral... the only case where you should see this is "measuring/estimating something VERY big, very approximately." For example, this can be true (even without reaching inf):
x.is_integer() True (math.sqrt(x**2)).is_integer() False
The problem there isn't how "is it an integer?" is spelled, it's that
_any_ way of spelling "is it an integer?" doesn't answer the question they're trying to answer. They're just plain confused about how floating point works. The use of `.is_integer()` (however spelled!) isn't the cause of that, it's a symptom.
Agreed! -- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.
On Wed, Mar 21, 2018 at 8:49 PM, David Mertz <mertz@gnosis.cx> wrote:
For example, this can be true (even without reaching inf):
x.is_integer() True (math.sqrt(x**2)).is_integer() False
If you have a moment to share it, I'd be interested to know what value of `x` you used to achieve this, and what system you were on. This can't happen under IEEE 754 arithmetic. -- Mark
[David Mertz <mertz@gnosis.cx>]
For example, this can be true (even without reaching inf):
x.is_integer() True (math.sqrt(x**2)).is_integer() False
[Mark Dickinson <dickinsm@gmail.com> ]
If you have a moment to share it, I'd be interested to know what value of `x` you used to achieve this, and what system you were on. This can't happen under IEEE 754 arithmetic.
I expect it might happen under one of the directed rounding modes (like "to +infinity"). But under 754 binary round-nearest/even arithmetic, it's been formally proved that sqrt(x*x) == x exactly for all non-negative finite x such that x*x neither overflows nor underflows (and .as_integer() has nothing to do with that very strong result): https://hal.inria.fr/hal-01148409/document OTOH, the paper notes that it's not necessarily true for IEEE decimal arithmetic; e.g.,
import decimal decimal.getcontext().prec = 4 (decimal.Decimal("31.66") ** 2).sqrt() # result is 1 ulp smaller Decimal('31.65')
decimal.getcontext().prec = 5 (decimal.Decimal("31.660") ** 2).sqrt() # result is 1 ulp larger Decimal('31.661')
Ok. I'm wrong on that example. On Wed, Mar 21, 2018, 9:11 PM Tim Peters <tim.peters@gmail.com> wrote:
[David Mertz <mertz@gnosis.cx>]
For example, this can be true (even without reaching inf):
x.is_integer() True (math.sqrt(x**2)).is_integer() False
[Mark Dickinson <dickinsm@gmail.com> ]
If you have a moment to share it, I'd be interested to know what value of `x` you used to achieve this, and what system you were on. This can't happen under IEEE 754 arithmetic.
I expect it might happen under one of the directed rounding modes (like "to +infinity").
But under 754 binary round-nearest/even arithmetic, it's been formally proved that sqrt(x*x) == x exactly for all non-negative finite x such that x*x neither overflows nor underflows (and .as_integer() has nothing to do with that very strong result):
https://hal.inria.fr/hal-01148409/document
OTOH, the paper notes that it's not necessarily true for IEEE decimal arithmetic; e.g.,
import decimal decimal.getcontext().prec = 4 (decimal.Decimal("31.66") ** 2).sqrt() # result is 1 ulp smaller Decimal('31.65')
decimal.getcontext().prec = 5 (decimal.Decimal("31.660") ** 2).sqrt() # result is 1 ulp larger Decimal('31.661')
[Mark Dickinson <dickinsm@gmail.com> ]
If you have a moment to share it, I'd be interested to know what value of `x` you used to achieve this, and what system you were on. This can't happen under IEEE 754 arithmetic.
I expect it might happen under one of the directed rounding modes (like "to +infinity").
PyPy (5.8):
x = 1e300 x.is_integer() True math.sqrt(x**2).is_integer() False x**2 inf
(It gives an OverflowError on my CPython installs.) I believe this is allowed, and Python is not required to raise OverflowError here: https://docs.python.org/3.6/library/exceptions.html#OverflowError says:
for historical reasons, OverflowError is sometimes raised for integers that are outside a required range. Because of the lack of standardization of floating point exception handling in C, most floating point operations are not checked
-- Devin
[Devin Jeanpierre <jeanpierreda@gmail.com>]
PyPy (5.8):
x = 1e300 x.is_integer() True math.sqrt(x**2).is_integer() False x**2 inf
I think you missed that David said "even without reaching inf" (you did reach inf), and that I said "such that x*x neither overflows nor underflows". Those are technical words related to IEEE-754: your x*x sets the IEEE overflow flag, although CPython may or may not raise the Python OverflowError exception.
(It gives an OverflowError on my CPython installs.)
I believe this is allowed, and Python is not required to raise OverflowError here: https://docs.python.org/3.6/library/exceptions.html#OverflowError says:
for historical reasons, OverflowError is sometimes raised for integers that are outside a required range. Because of the lack of standardization of floating point exception handling in C, most floating point operations are not checked
You can avoid the OverflowError (but not the IEEE overflow condition!) under CPython by multiplying instead:
x = 1e300 x*x inf
From that fundamental "take floats exactly at face value" view, what .is_integer() should do for floats is utterly obvious: there is no
Note: this is a top-posted essay much more about floating-point philosophy than about details. Details follow from the philosophy, and if philosophies don't match the desired details will never match either. Understanding floating point requires accepting that they're a funky subset of rational numbers, augmented with some oddballs (NaNs, "infinities", minus zero). At best the reals are a vague inspiration, and floats have their own terminology serving their actual nature. Thinking about reals instead is often unhelpful. For example, it's bog standard terminology to call all IEEE-754 values that aren't infinities or NaNs "finite". Which, by no coincidence, is how Python's math.isfinite() discriminates. Within the finites - which are all rational numbers - the distinction between integers and non-integers is obvious, but only after you're aware of it and give it some thought. Which most people aren't and don't - but that's no reason to prevent the rest of us from getting work done ;-) This isn't anything new in Python - it's as old as floating-point. For example, look up C's ancient "modf" function (which breaks a float/double into its "integer" and "fractional" parts, and treats all finite floats of sufficiently large magnitude as having fractional parts of 0.0 - because they in are fact exact integers). The idea that floats are "just approximations - so all kinds of slop is acceptable and all kinds of fear inescapable" went out of style when IEEE-754 was introduced. That standard codified an alternative view: that functions on floats should behave as if their inputs were _exactly_ correct, and - given that - produce the closest representable value to the infinitely precise result. That proved to be extremely valuable in practice, allowing the development of shorter, faster, more robust, and more accurate numerical algorithms. The trend ever since has been to do more & more along those lines, from trig functions doing argument reduction as if pi were represented with infinite precision, to adding single-rounding dot product primitives (all again acting as if all the inputs were exactly correct). Since that approach has been highly productive in real life, it's the one I favor. Arguments like "no floating point number on the order of 1e306 is sufficiently precise as to be an integer in any meaningful sense" don't even start to get off the ground in that approach. Maybe in 1970 ;-) You can have no actual idea of whether 1e306 is exactly right or off by a factor of a million just from staring at it, and real progress has been made by assuming all values are exactly what they appear to be, then acting accordingly. If you want to model that some values are uncertain, that's fine, but then you need something like interval arithmetic instead. possible argument about whether a given IEEE-754 float is or is not an integer, provided you're thinking about IEEE-754 floats (and not, e.g., about mathematical reals), and making even a tiny attempt to honor the spirit of the IEEE-754 standard. Whether that's _useful_ to you depends on the application you're writing at the time. The advantage of the philosophy is that it often gives clear guidance about what implementations "should do" regardless, and following that guidance has repeatedly proved to be a boon to those writing numerical methods. And, yes, also a pain in the ass ;-) --- nothing new below --- On Wed, Mar 21, 2018 at 3:49 PM, David Mertz <mertz@gnosis.cx> wrote:
On Wed, Mar 21, 2018 at 3:02 PM, Tim Peters <tim.peters@gmail.com> wrote:
[David Mertz]
I've been using and teaching python for close to 20 years and I never noticed that x.is_integer() exists until this thread.
Except it was impossible to notice across most of those years, because it didn't exist across most of those years ;-)
That's probably some of the reason. I wasn't sure if someone used the time machine to stick it back into Python 1.4.
On the other hand, `x == int(x)` is genuinely obvious..
But a bad approach: it can raise OverflowError (for infinite x); it can raise ValueError (for x a NaN);
These are the CORRECT answers! Infinity neither is nor is not an integer. Returning a boolean as an answer is bad behavior; I might argue about *which* exception is best, but False is not a good answer to `float('inf').is_integer()`. Infinity is neither in the Reals nor in the Integers, but it's just as much the limit of either.
Likewise Not-a-Number isn't any less an integer than it is a real number (approximated by a floating point number). It's NOT a number, which is just as much not an integer.
and can waste relative mountains of time creating huge integers, e.g.,
True enough. But it's hard to see where that should matter. No floating point number on the order of 1e306 is sufficiently precise as to be an integer in any meaningful sense. If you are doing number theory with integers of that size (or larger is perfectly fine too) the actual test is `isinstance(x, int)`. Using a float is just simply wrong for the task to start with, whether or not those bits happen to represent something Integral... the only case where you should see this is "measuring/estimating something VERY big, very approximately."
For example, this can be true (even without reaching inf):
x.is_integer() True (math.sqrt(x**2)).is_integer() False
The problem there isn't how "is it an integer?" is spelled, it's that _any_ way of spelling "is it an integer?" doesn't answer the question they're trying to answer. They're just plain confused about how floating point works. The use of `.is_integer()` (however spelled!) isn't the cause of that, it's a symptom.
Agreed!
-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.
On Wed, Mar 21, 2018 at 11:43 PM, Tim Peters <tim.peters@gmail.com> wrote:
Note: this is a top-posted essay much more about floating-point philosophy than about details. Details follow from the philosophy, and if philosophies don't match the desired details will never match either.
but of course :-)
From that fundamental "take floats exactly at face value" view, what .is_integer() should do for floats is utterly obvious:
sure -- but I don't think anyone is arguing that -- the question is whether the function should exist -- and that means not "how should it work?" or "is it clearly and appropriately defined?" but rather, "is it the "right" thing to do in most cases, when deployed by folks that haven't thought deeply about floating point.
Whether that's _useful_ to you depends on the application you're
writing at the time.
exactly. I think pretty much all the real world code that's been shown here for using .is_integer() is really about type errors (issues). The function at hand really wants integer inputs -- but wants to allow the user to be sloppy and provide a float type that happens to be an int. Given Python's duck-typing nature, maybe that's a good thing? I know I really discourage dynamic type checking.... Also, every example has been for small-ish integers -- exponents, factorials, etc -- not order 1e300 -- or inf or NaN, etc. Finally, the use-cases where the value that happens-to-be-an-int is computed via floating point -- .is_integer() is probably the wrong check -- you probably want isclose(). The other use-cases: and floor() and ceil() and round() all produce actual integers -- so no need for that anymore. All this points to: we don't need .is_integer All the being said -- the standard for depreciation is much higher bar than not-adding-it-in-the-first-place. -CHB -- Christopher Barker, Ph.D. Oceanographer Emergency Response Division NOAA/NOS/OR&R (206) 526-6959 voice 7600 Sand Point Way NE (206) 526-6329 fax Seattle, WA 98115 (206) 526-6317 main reception Chris.Barker@noaa.gov
[Chris Barker <chris.barker@noaa.gov>]
... ... "is it the "right" thing to do in most cases, when deployed by folks that haven't thought deeply about floating point.
Gimme a break ;-) Even people who _believe_ they've thought about floating point still litter the bug tracker with
.1 + .2 0.30000000000000004
"bug reports". .is_integer() is easy to explain compared to that - and you have to go out of your way to use it.
... I think pretty much all the real world code that's been shown here for using .is_integer() is really about type errors (issues). The function at hand really wants integer inputs -- but wants to allow the user to be sloppy and provide a float type that happens to be an int. Given Python's duck-typing nature, maybe that's a good thing? I know I really discourage dynamic type checking....
So you identified a use case. One you don't approve of (nor do I), but not strongly enough to demand they suffer instead ;-)
Also, every example has been for small-ish integers -- exponents, factorials, etc -- not order 1e300 -- or inf or NaN, etc.
Finally, the use-cases where the value that happens-to-be-an-int is computed via floating point -- .is_integer() is probably the wrong check -- you probably want isclose().
Everyone who has implemented a production math library can recall cases where the functionality was needed. Here, that includes at least Stefan Krah and me. You could also follow the link from Mark Dickinson to SciPy's implementation of the beta function. In every case I've needed the functionality, isclose() would have been utterly useless. Behold:
(-1.0) ** 3.0 -1.0 (-1.0) ** 3.000000000001 # different result _type_ (-1-3.142007854859299e-12j) math.isclose(3.0, 3.000000000001) True
And another showing that the same functionality is needed regardless of how large the power:
(-1.0) ** 1e300 # an even integer power 1.0
When implementing an externally defined standard, when it says "and if such-and-such is an integer ...", it _means_ exactly an integer, not "or a few ULP away from an integer". IEEE pow()-like functions bristle with special cases for integers.
(-math.inf) ** 3.1 inf (-math.inf) ** 3.0 # note: this one has a negative result (odd integer power) -inf (-math.inf) ** 2.9 inf
... All this points to: we don't need .is_integer
I'll grant that you don't think you need it. So don't use it ;-)
All the being said -- the standard for depreciation is much higher bar than not-adding-it-in-the-first-place.
I would not have added it as a method to begin with - but I agree with Guido that it doesn't reach the bar for deprecation. The only examples of "bad" uses we saw were from people still so naive about floating-point behavior that they'll easily fall into other ways to get it wrong. What we haven't seen: a single person here saying "you know, I think _I'd_ be seduced into misusing it!". It's not _inherently_ confusing at all.
OK, we'll keep float.is_integer(), and that's a pronouncement, so that we can hopefully end this thread soon. It should also be added to int. After all that's what started this thread, with the observation that mypy and PEP 484 consider an int valid whenever a float is expected. Since PEP 484 and mypy frown upon the numeric tower I don't care too much about whether it's added it there (or to Decimal) but given that we're keeping float.is_integer() and adding int.is_integer(), I don't see what's gained by not adding it to the numeric tower and Decimal either. Numpy can do what it likes, it doesn't play by the same rules as the stdlib anyway. On Wed, Mar 21, 2018 at 7:29 PM, Tim Peters <tim.peters@gmail.com> wrote:
[Chris Barker <chris.barker@noaa.gov>]
... ... "is it the "right" thing to do in most cases, when deployed by folks that haven't thought deeply about floating point.
Gimme a break ;-) Even people who _believe_ they've thought about floating point still litter the bug tracker with
.1 + .2 0.30000000000000004
"bug reports". .is_integer() is easy to explain compared to that - and you have to go out of your way to use it.
... I think pretty much all the real world code that's been shown here for using .is_integer() is really about type errors (issues). The function at hand really wants integer inputs -- but wants to allow the user to be sloppy and provide a float type that happens to be an int. Given Python's duck-typing nature, maybe that's a good thing? I know I really discourage dynamic type checking....
So you identified a use case. One you don't approve of (nor do I), but not strongly enough to demand they suffer instead ;-)
Also, every example has been for small-ish integers -- exponents, factorials, etc -- not order 1e300 -- or inf or NaN, etc.
Finally, the use-cases where the value that happens-to-be-an-int is computed via floating point -- .is_integer() is probably the wrong check -- you probably want isclose().
Everyone who has implemented a production math library can recall cases where the functionality was needed. Here, that includes at least Stefan Krah and me. You could also follow the link from Mark Dickinson to SciPy's implementation of the beta function.
In every case I've needed the functionality, isclose() would have been utterly useless. Behold:
(-1.0) ** 3.0 -1.0 (-1.0) ** 3.000000000001 # different result _type_ (-1-3.142007854859299e-12j) math.isclose(3.0, 3.000000000001) True
And another showing that the same functionality is needed regardless of how large the power:
(-1.0) ** 1e300 # an even integer power 1.0
When implementing an externally defined standard, when it says "and if such-and-such is an integer ...", it _means_ exactly an integer, not "or a few ULP away from an integer". IEEE pow()-like functions bristle with special cases for integers.
(-math.inf) ** 3.1 inf (-math.inf) ** 3.0 # note: this one has a negative result (odd integer power) -inf (-math.inf) ** 2.9 inf
... All this points to: we don't need .is_integer
I'll grant that you don't think you need it. So don't use it ;-)
All the being said -- the standard for depreciation is much higher bar than not-adding-it-in-the-first-place.
I would not have added it as a method to begin with - but I agree with Guido that it doesn't reach the bar for deprecation. The only examples of "bad" uses we saw were from people still so naive about floating-point behavior that they'll easily fall into other ways to get it wrong. What we haven't seen: a single person here saying "you know, I think _I'd_ be seduced into misusing it!". It's not _inherently_ confusing at all. _______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/ guido%40python.org
-- --Guido van Rossum (python.org/~guido)
[Tim]
from trig functions doing argument reduction as if pi were represented with infinite precision,
[Greg Ewing <greg.ewing@canterbury.ac.nz>]
That sounds like an interesting trick! Can you provide pointers to any literature describing how it's done?
Not doubting it's possible, just curious.
As I recall, when it was first done a "lazy" routine produced as many bits of pi as a given argument required, doing gonzo arbitrary precision arithmetic. Later, computer-aided analysis based on continued fraction expansions identified the worst possible case across all IEEE doubles (& singles). For example, it's possible in reasonable time to find the IEEE double that comes closest to being an exact integer multiple of pi/4 (or whatever other range you want to reduce to). Then it's only necessary to precompute pi to as many bits as needed to handle the worst case. In practice, falling back to that is necessary only for "large" arguments, and the usual double-precision numeric tricks suffice for smaller arguments. Search the web for "trig argument reduction" for whatever the state of the art may be today ;-) For actual code, FDLIBM does "as if infinite precision" trig argument reduction, using a precomputed number of pi bits sufficient to handle the worst possible IEEE double case, and is available for free from NETLIB: http://www.netlib.org/fdlibm/ The code is likely to be baffling, though, as there's scant explanation. Reading a paper or two first would be a huge help.
I apologize that I get into the discussion. Obviously in some situations it will be useful to check that a floating-point number is integral, but from the examples given it is clear that they are very rare. Why the variant with the inclusion of this functionality into the math module was not considered at all. If the answer is - consistency upon the numeric tower - will it go for complex type and what will it mean (there can be two point of views)? Is this functionality so often used and practical to be a method of float, int, ..., and not just to be an auxiliary function? p.s.: The same thoughts about `as_integer_ratio` discussion. With kind regards, -gdg
In the PR which implements is_integer() for int, the numeric tower, and Decimal I elected not to implement it for Complex or complex. This was principally because complex instances, even if they have an integral real value, are not convertible to int and it seems reasonable to me that any number for which is_integer() returns True should be convertible to int successfully, and without loss of information.
int(complex(2, 0))
Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can't convert complex to int There could be an argument that a putative complex.is_integral() should therefore return False, but I expect that would get even less support than the other suggestions in these threads. *Robert Smallshire | *Managing Director *Sixty North* | Applications | Consulting | Training rob@sixty-north.com | T +47 63 01 04 44 | M +47 924 30 350 http://sixty-north.com On 22 March 2018 at 10:51, Kirill Balunov <kirillbalunov@gmail.com> wrote:
I apologize that I get into the discussion. Obviously in some situations it will be useful to check that a floating-point number is integral, but from the examples given it is clear that they are very rare. Why the variant with the inclusion of this functionality into the math module was not considered at all. If the answer is - consistency upon the numeric tower - will it go for complex type and what will it mean (there can be two point of views)?
Is this functionality so often used and practical to be a method of float, int, ..., and not just to be an auxiliary function?
p.s.: The same thoughts about `as_integer_ratio` discussion.
With kind regards, -gdg
_______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/ rob%40sixty-north.com
[Kirill Balunov <kirillbalunov@gmail.com>]
I apologize that I get into the discussion. Obviously in some situations it will be useful to check that a floating-point number is integral, but from the examples given it is clear that they are very rare. Why the variant with the inclusion of this functionality into the math module was not considered at all.
Nobody here really discussed the history, and I don't know. The questions here have been about what to do given that `is_integer` and `as_integer_ratio` are _already_ advertised (public) methods on some numeric types.
If the answer is - consistency upon the numeric tower - will it go for complex type and what will it mean (there can be two point of views)?
I haven't seen anyone suggest either method be added to Complex. There are lots of methods that don't show up in the tower before hitting Real. For example, given that Complex doesn't support __float__, it would be bizarre if it _did_ support as_integer_ratio.
Is this functionality so often used and practical to be a method of float, int, ..., and not just to be an auxiliary function?
p.s.: The same thoughts about `as_integer_ratio` discussion.
I would have added them as functions in the `math` module instead. perhaps supported by dunder methods (__as_integer_ratio__, __is_integer__). But that's not what happened, and whether or not they have double underscores on each end doesn't really make all that much difference except to dedicated pedants ;-)
2018-03-22 19:47 GMT+03:00 Tim Peters <tim.peters@gmail.com>:
Is this functionality so often used and practical to be a method of float, int, ..., and not just to be an auxiliary function?
p.s.: The same thoughts about `as_integer_ratio` discussion.
I would have added them as functions in the `math` module instead. perhaps supported by dunder methods (__as_integer_ratio__, __is_integer__). But that's not what happened, and whether or not they have double underscores on each end doesn't really make all that much difference except to dedicated pedants ;-)
Yes, this was my point. In spite of the fact that the pronouncement has already been made, there may still be an opportunity to influence this decision. I do not think that this is only a matter of choice, how this functionality will be accessed through a method or function, in fact these highly specialized methods heavily pollute the API and open the door for persistent questions. Given the frequency and activity of using this `.is_integer` method the deprecation of this method is unlikely to greatly affect someone. (for `as_integer_ratio` I think the bar is higher). Summarizing this thread it seems to me that with deprecation of `is_integer` method and with addition of `is_integer` function in math module will make everyone happy: PROS: 1. Those who do not like this method, and do not want to see it as a redundant part of `int`, ... will be happy 2. Those who need it will have this functionality through math module 3. Compatible packages do not have to quack louder 4. Cleaner API (no need to add this through numeric tower) 5. Make everyone happy and stop this thread :) CONS: 1. Backward incompatible change I do not want to restart this topic, but I think that there is an opportunity for improvement that can be missed. With kind regards, -gdg
[Kirill Balunov <kirillbalunov@gmail.com>]
... .... In spite of the fact that the pronouncement has already been made, there may still be an opportunity to influence this decision.
That's not really how this works. Guido has been doing this for decades, and when he Pronounces he's done with it :-)
I do not think that this is only a matter of choice, how this functionality will be accessed through a method or function, in fact these highly specialized methods heavily pollute the API
"Heavily"? Seems oversold.
and open the door for persistent questions.
That's a door that can never be closed, no matter what.
Given the frequency and activity of using this `.is_integer` method the deprecation of this method is unlikely to greatly affect someone. (for `as_integer_ratio` I think the bar is higher). Summarizing this thread it seems to me that with deprecation of `is_integer` method and with addition of `is_integer` function in math module will make everyone happy:
Not at all, but that's already been explained. Deprecation is _serous_ business: it's not only the presumably relative handful of direct users who are directly annoyed, but any number of worldwide web pages, blogs, books, papers, slides, handouts, message boards ... that so much as mentioned the now-deprecated feature. The language implementation is the tiniest part of what's affected, yet is the _only_ part we (Python developers) can repair. Deprecation really requires that something is a security hole that can't be repaired, impossible to make work as intended, approximately senseless, or is superseded by a new way to accomplish a thing that's near-universally agreed to be vastly superior. Maybe others? Regardless, they're all "really big deals". The "harm" done by keeping these methods seems approximately insignificant. Serhiy certainly found examples where uses made no good sense, but that's _common_ among floating-point features. For example, here's a near-useless implementation of Newton's method for computing square roots: def mysqrt(x): guess = x / 2.0 while guess ** 2 != x: guess = (guess + x / guess) / 2.0 return guess And here I'll use it:
mysqrt(25.0) 5.0 mysqrt(25.2) 5.019960159204453
Works great! Ship it :-)
mysqrt(25.1)
Oops. It just sits there, consuming cycles. That's because there is no IEEE double x such that x*x == 25.1. While that's not at all obvious, it's true. Some people really have argued to deprecate (in)equality testing of floats because of "things like that", but that's fundamentally nuts. We may as well remove floats entirely then. In short, that an fp feature can be misused, and _is_ misused, is no argument for deprecating it. If it can _only_ be misused, that's different, but that doesn't apply to is_integer. That someone - or even almost everyone - is merely annoyed by seeing an API they have no personal use for doesn't get close to "really big deal". The time to stop it was before it was added.
PROS: ... 5. Make everyone happy and stop this thread :)
This thread ended before you replied to it - I'm just a ghost haunting its graveyard to keep you from feeling ignored -)
participants (11)
-
Chris Barker -
David Mertz -
Devin Jeanpierre -
Greg Ewing -
Guido van Rossum -
Kirill Balunov -
Mark Dickinson -
Robert Smallshire -
Serhiy Storchaka -
Steve Holden -
Tim Peters