I Was Just Dipping in a Toe and Got Bit by Python

I have been learning Python by dribs and drabs since my days with the Health & Life Sciences group. I started out using the default installation on the Linux systems on which I had access. This was usually Python 2.6 (Py2). Now, with the Intel Distribution for Python coming out soon (just went to beta release a few days before I wrote this), I decided to make the plunge and step up to Python 3 (Py3).

One of the codes I was playing with involved finding the area of a triangle. If you remember any trigonometry (or have just done a quick web search), you recall that the area of a triangle is half the product of the base length times the height of the triangle (from the base). The specific details of the problem I was addressing required use of isosceles triangles, so I chose the base of the isosceles triangle to be the base of the area computation, rather than one of the legs of the isosceles. In that way, I could form a right triangle with one side being half the base length and the hypotenuse as the leg of the isosceles triangle. Further, since all side lengths had to be integral, I decided to compute the height of the overall triangle by use of the Pythagorean Theorem. Thus, I wrote the following function to test the relationship of the three sides of a potential right triangle:

>>> def pythagorean_triple(c, a, b):
...         return (c*c == (a*a + b*b))

Because I would have two sides of a right triangle I wanted to compute the third side with a variation of the above formula. I take the square of the hypotenuse, subtract the square of the other given side (half the base of the isosceles triangle), and then compute the square root of that difference. In fact, I want to take the truncated integral version of that square root. This last bit is due to the fact that I’m trying to see if the area of the isosceles triangle is integral. If the third side is any fractional value, I know that I don’t have a Pythagorean Triple and, therefore, the isosceles triangle doesn’t have an integral area. (In the general case this isn’t necessarily true since the multiplication of the integral base length could give an integral value. However, due to the special relationship of the isosceles side lengths that I haven’t gone into, I knew that this would never happen.)

>>> e = 126500416/2
>>> e
63250208
>>> d = int(math.sqrt(126500417*126500417 - e*e))
>>> d
109552575
>>> pythagorean_triple(126500417, e, d)
True

This is exactly what I expected. When I ran the same thing in Py3, I got this:

>>> e = 126500416/2
>>> e
63250208.0
>>> d = int(math.sqrt(126500417*126500417 - e*e))
>>> d
109552575
>>> pythagorean_triple(126500417, e, d)
False

I was running the above interactively, so before I got to the test for Pythagorean Triple, I was puzzled with the output of the value from ‘e’ [line 3]. I just shrugged my shoulders since I knew I was dividing an even value (126500416) by 2. While the printed result was an obvious floating point value, the actual value was arithmetically equivalent to the integral value. My jaw dropped when I saw the final line of output that was contrary to the Py2 output and contrary to what I knew about the triangle I was describing.

My first thought was that I might have discovered some weird corner case in the Python interpreter or runtime. I immediately realized that it was more likely something that I’d done wrong. Nothing wrong with the code between the two runs. (I’d actually cut-and-pasted from one Python session to the other.) I next started asking around about this specific case to folks that were much more knowledgeable in Python programming than I am.

The answer came back quickly. The division operator in Py3 has changed from the definition used in Py2. Specifically, the single slash operator will compute the floating value regardless of the operands, i.e., “true division”. In Py2, if the operands were both integral the quotient would be the integral portion of the result (floor or truncated division). To get a quotient that was a floating point value required that one of the operands be a float. (This tracks with how at least one other programming language I am fluent in and had obviously colored my expectations about arithmetic operations in other languages.)

Using the double slash operator in Py3 will give me the integral quotient that I’m expecting, and, since I’m interested in integer slide lengths, it is what I should be using anyway. All of this should have been obvious to me when the output value of ‘e’ was a float. This episode does remind me to try to know all about the tools I’m using before I get bit by some unexpected behavior from an application. I likely do not need to delve into all the dusty corner cases that could potentially arise, but something as obvious as this change between Python versions (and that has been well-documented online) should not have caught me unaware. I should read a little further in my Python 3 text before I go off halfcocked again.

For more complete information about compiler optimizations, see our Optimization Notice.