27

In Python 3, I am checking whether a given value is triangular, that is, it can be represented as n * (n + 1) / 2 for some positive integer n.

Can I just write:

import math

def is_triangular1(x):
    num = (1 / 2) * (math.sqrt(8 * x + 1) - 1)
    return int(num) == num

Or do I need to do check within a tolerance instead?

epsilon = 0.000000000001
def is_triangular2(x):
    num = (1 / 2) * (math.sqrt(8 * x + 1) - 1)
    return abs(int(num) - num) < epsilon

I checked that both of the functions return same results for x up to 1,000,000. But I am not sure if generally speaking int(x) == x will always correctly determine whether a number is integer, because of the cases when for example 5 is represented as 4.99999999999997 etc.

As far as I know, the second way is the correct one if I do it in C, but I am not sure about Python 3.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
Sunny88
  • 2,860
  • 3
  • 24
  • 25

12 Answers12

42

There is is_integer function in python float type:

>>> float(1.0).is_integer()
True
>>> float(1.001).is_integer()
False
>>> 
ndpu
  • 22,225
  • 6
  • 54
  • 69
11

You'll want to do the latter. In Programming in Python 3 the following example is given as the most accurate way to compare

def equal_float(a, b):
    #return abs(a - b) <= sys.float_info.epsilon
    return abs(a - b) <= chosen_value #see edit below for more info

Also, since epsilon is the "smallest difference the machine can distinguish between two floating-point numbers", you'll want to use <= in your function.

Edit: After reading the comments below I have looked back at the book and it specifically says "Here is a simple function for comparing floats for equality to the limit of the machines accuracy". I believe this was just an example for comparing floats to extreme precision but the fact that error is introduced with many float calculations this should rarely if ever be used. I characterized it as the "most accurate" way to compare in my answer, which in some sense is true, but rarely what is intended when comparing floats or integers to floats. Choosing a value (ex: 0.00000000001) based on the "problem domain" of the function instead of using sys.float_info.epsilon is the correct approach.

Thanks to S.Lott and Sven Marnach for their corrections, and I apologize if I led anyone down the wrong path.

Dan Roberts
  • 4,664
  • 3
  • 34
  • 43
  • 1
    Don't work for the question: `5 - 4.999999999999997 <= sys.float_info.epsilon` is False (at least on my python3.2 @64-bit) – JBernardo Jun 02 '11 at 00:03
  • i think that was just an example by the question asker? Not an actual observed float representation of 5. – mindthief Jun 02 '11 at 00:09
  • 4.999999999999997 is not a real example, I just wrote arbitrary number of 9s there. Perhaps it can never happen in real life. – Sunny88 Jun 02 '11 at 00:10
  • 6
    Half right. Don't use `sys.float_info.epsilon`. Use a value that's specific to the problem domain. – S.Lott Jun 02 '11 at 00:51
  • 2
    @S-Lott Why not sys.float_info.epsilon ? – Sunny88 Jun 02 '11 at 00:58
  • 1
    @Sunny88: Do the algebra. It's possible that your float value's representation error is larger that `sys.float_info.epsilon`. `epsilon = 0.000000000001` is more likely to be correct, since it depends on something in your problem domain, not a fairly arbitrary property of the floating-point representation. – S.Lott Jun 02 '11 at 02:21
  • @Sunny88: Any single floating point operation should have an error of at most sys.float_info.epsilon, but if you keep doing floating point operations you can accumulate error. – Daniel Stutzbach Jun 03 '11 at 14:35
  • This is only accurate if a - b < 1 –  Jun 04 '11 at 04:16
  • Please explain. If it were > 1 then it would accurately return False. – Dan Roberts Jun 04 '11 at 11:59
  • @Dan: The relative error of most floating point operations is at most `sys.float_info.epsilon`. The above code checks for the absolute error, which is plain wrong. As an example, compare `1011` and `1011.0 / 7.0 * 7.0` using your `equal_float()` function. – Sven Marnach Jun 04 '11 at 13:09
  • @Sven: Thanks for the example values. I generally understood the criticisms above but it is good to have an example. I was actually curious though about Pyson's comment which did not make sense to me. I mean if a - b > 1 then you would never want to return True right? Or, is it possible for error to build up to such an extent that the function should take into account some percent error? – Dan Roberts Jun 04 '11 at 13:24
  • ...some percent error instead of a particular number? It certainly sounds like having any specific value could lead to a problem so any generic function could not be used in all systems/cases. This has been very helpful information for me in understanding how to compare. In looking back at the book it says "Here is a simple function for comparing floats for equality to the limit of the machines accuracy". I characterized it as the "most accurate" way to compare in my answer which may be true but it is obviously not the most practical. – Dan Roberts Jun 04 '11 at 13:35
  • @Dan: I don't understand Pyson's remark. But even if `abs(a - b) > 1` we might want to consider the numbers equal if they are big. To reiterate: FP numbers only have a specified *relative* error, while `abs(a - b) > 1` again is a statement about the *absolute* error. As an example, `a = 1e16` and `b = 1e16 + 2.0` should be considered equal up to FP accuracy. There is actually no represantable double precision number between those two numbers. – Sven Marnach Jun 04 '11 at 13:43
  • @Dan: Your book is wrong. The above function definition should never be used. – Sven Marnach Jun 04 '11 at 13:48
  • 1
    @Dan: You edit fixes the issue only partially. You need to check **relative** errors against some epsilon, and this epsilon is not necessarily `sys.float_info.epsilon`. Maybe you want to read the [standard reference on this topic](http://download.oracle.com/docs/cd/E19957-01/806-3568/ncg_goldberg.html). – Sven Marnach Jun 04 '11 at 14:05
  • You are right. I do need to learn more about floats so thank you for the link. Is there an answer already posted that you recommend, or can you post an answer and then I'll remove my own? Is the primary change needed to my answer that the comparison should be done by comparing percent difference to a calculated percent error? Something like percent_error = percent_err(epsilon, func(epsilon)) ...and then in compare function... percent_diff(val1, val2) <= percent_diff? – Dan Roberts Jun 04 '11 at 15:01
  • Just use NumPy oh my god... avoid all of this mess – NoName Jan 13 '20 at 16:30
  • @NoName I am not surprised there is something for float comparison in numpy but it is just not a library I am familiar with. You should add an answer with some examples so others will see it. – Dan Roberts Jan 15 '20 at 14:53
11

Both your implementations have problems. It actually can happen that you end up with something like 4.999999999999997, so using int() is not an option.

I'd go for a completely different approach: First assume that your number is triangular, and compute what n would be in that case. In that first step, you can round generously, since it's only necessary to get the result right if the number actually is triangular. Next, compute n * (n + 1) / 2 for this n, and compare the result to x. Now, you are comparing two integers, so there are no inaccuracies left.

The computation of n can be simplified by expanding

(1/2) * (math.sqrt(8*x+1)-1) = math.sqrt(2 * x + 0.25) - 0.5

and utilizing that

round(y - 0.5) = int(y)

for positive y.

def is_triangular(x):
    n = int(math.sqrt(2 * x))
    return x == n * (n + 1) / 2
Ankur Agarwal
  • 23,692
  • 41
  • 137
  • 208
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • round(y - 0.5) == int(y) does not seem to be always correct: In Python 3, round(1-0.5)==0, but int(1)==1 – Sunny88 Jun 02 '11 at 00:54
  • @Sunny: Interesting -- the behaviour of `round()` changed in Python 3.x. But since we only want to round numbers that are pretty close to integers, we don't have to worry about the case that the fractional part is exactly `0.5` here. As I said before, we can be pretty tolerant with rounding since we only need to get it right if the number is actually triangular. That's why it's also ok to omit the `0.25`. – Sven Marnach Jun 02 '11 at 01:35
  • You can just list all triangular numbers `((x*x+x)/2 for x in count())` and test that the function gives True for these and False for everything else ... it does, for all numbers Python can turn into ints. – Jochen Ritzel Jun 02 '11 at 01:52
5

Python does have a Decimal class (in the decimal module), which you could use to avoid the imprecision of floats.

Paul D. Waite
  • 96,640
  • 56
  • 199
  • 270
4

floats can exactly represent all integers in their range - floating-point equality is only tricky if you care about the bit after the point. So, as long as all of the calculations in your formula return whole numbers for the cases you're interested in, int(num) == num is perfectly safe.

So, we need to prove that for any triangular number, every piece of maths you do can be done with integer arithmetic (and anything coming out as a non-integer must imply that x is not triangular):

To start with, we can assume that x must be an integer - this is required in the definition of 'triangular number'.

This being the case, 8*x + 1 will also be an integer, since the integers are closed under + and * .

math.sqrt() returns float; but if x is triangular, then the square root will be a whole number - ie, again exactly represented.

So, for all x that should return true in your functions, int(num) == num will be true, and so your istriangular1 will always work. The only sticking point, as mentioned in the comments to the question, is that Python 2 by default does integer division in the same way as C - int/int => int, truncating if the result can't be represented exactly as an int. So, 1/2 == 0. This is fixed in Python 3, or by having the line

from __future__ import division

near the top of your code.

lvc
  • 34,233
  • 10
  • 73
  • 98
2

I think the module decimal is what you need

eyquem
  • 26,771
  • 7
  • 38
  • 46
  • Care to explain how this module will aid the OP (or any of the Google audience) or at least post a link? – BlackVegetable Mar 11 '13 at 21:36
  • @BlackVegetable In the absolute, you're not wrong. But relatively to the ocean of posts in which mine was going to be lost, I probably didn't consider worth while to write a detailed answer that would have re-explain what is already explained in the documenbtation. Sometimes, just showing the direction is usefull. – eyquem Mar 11 '13 at 21:59
  • Fair enough, your answer just seemed to be taking a different direction than the others. I was wishing I could click on the word *decimal* and get a link to that module. (It might even earn you an up-vote!) – BlackVegetable Mar 11 '13 at 22:23
  • I have a kind of principle: I bet on the intelligence of others. That day, I was probably tired and hadn't energy to explain that ``decimal`` manages problems of precision, and I thought that everybody was enough trained in Python to know where to find the documentation on this module, either by clicking on **Help** button in IDLE, either on python.org site (moreover everybody has his/her preference) - Concerning upvote, oh I have some other answers having no points in several threads, and I still sleep well. – eyquem Mar 11 '13 at 22:53
1

Consider using NumPy, they take care of everything under the hood.

import numpy as np

result_bool = np.isclose(float1, float2)

NoName
  • 9,824
  • 5
  • 32
  • 52
1

You can round your number to e.g. 14 decimal places or less:

 >>> round(4.999999999999997, 14)
 5.0

PS: double precision is about 15 decimal places

JBernardo
  • 32,262
  • 10
  • 90
  • 115
1

It is hard to argue with standards.

In C99 and POSIX, the standard for rounding a float to an int is defined by nearbyint() The important concept is the direction of rounding and the locale specific rounding convention.

Assuming the convention is common rounding, this is the same as the C99 convention in Python:

#!/usr/bin/python

import math

infinity = math.ldexp(1.0, 1023) * 2

def nearbyint(x): 
   """returns the nearest int as the C99 standard would"""

   # handle NaN
   if x!=x:
       return x      

   if x >= infinity:
       return infinity

   if x <= -infinity:
       return -infinity

   if x==0.0:
       return x

   return math.floor(x + 0.5)

If you want more control over rounding, consider using the Decimal module and choose the rounding convention you wish to employ. You may want to use Banker's Rounding for example.

Once you have decided on the convention, round to an int and compare to the other int.

dawg
  • 98,345
  • 23
  • 131
  • 206
1

Python has unlimited integer precision, but only 53 bits of float precision. When you square a number, you double the number of bits it requires. This means that the ULP of the original number is (approximately) twice the ULP of the square root.

You start running into issues with numbers around 50 bits or so, because the difference between the fractional representation of an irrational root and the nearest integer can be smaller than the ULP. Even in this case, checking if you are within tolerance will do more harm than good (by increasing the number of false positives).

For example:

>>> x = (1 << 26) - 1
>>> (math.sqrt(x**2)).is_integer()
True
>>> (math.sqrt(x**2 + 1)).is_integer()
False
>>> (math.sqrt(x**2 - 1)).is_integer()
False

>>> y = (1 << 27) - 1
>>> (math.sqrt(y**2)).is_integer()
True
>>> (math.sqrt(y**2 + 1)).is_integer()
True
>>> (math.sqrt(y**2 - 1)).is_integer()
True
>>> (math.sqrt(y**2 + 2)).is_integer()
False
>>> (math.sqrt(y**2 - 2)).is_integer()
True
>>> (math.sqrt(y**2 - 3)).is_integer()
False

You can therefore rework the formulation of your problem slightly. If an integer x is a triangular number, there exists an integer n such that x = n * (n + 1) // 2. The resulting quadratic is n**2 + n - 2 * x = 0. All you need to know is if the discriminant 1 + 8 * x is a perfect square. You can compute the integer square root of an integer using math.isqrt starting with python 3.8. Prior to that, you could use one of the algorithms from Wikipedia, implemented on SO here.

You can therefore stay entirely in python's infinite-precision integer domain with the following one-liner:

def is_triangular(x):
    return math.isqrt(k := 8 * x + 1)**2 == k

Now you can do something like this:

>>> x = 58686775177009424410876674976531835606028390913650409380075
>>> math.isqrt(k := 8 * x + 1)**2 == k
True
>>> math.isqrt(k := 8 * (x + 1) + 1)**2 == k
False

>>> math.sqrt(k := 8 * x + 1)**2 == k
False

The first result is correct: x in this example is a triangular number computed with n = 342598234604352345342958762349.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
0

Python still uses the same floating point representation and operations C does, so the second one is the correct way.

Aurojit Panda
  • 909
  • 6
  • 11
0

Under the hood, Python's float type is a C double.

The most robust way would be to get the nearest integer to num, then test if that integers satisfies the property you're after:

import math
def is_triangular1(x):
    num = (1/2) * (math.sqrt(8*x+1)-1 )
    inum = int(round(num))
    return inum*(inum+1) == 2*x  # This line uses only integer arithmetic
Daniel Stutzbach
  • 74,198
  • 17
  • 88
  • 77