views:

742

answers:

9

I recently discovered that x**.5 and math.sqrt(x) do not always produce the same result in Python:

Python 2.6.1 (r261:67517, Dec 4 2008, 16:51:00) [MSC v.1500 32 bit (Intel)]
on win32
>>> 8885558**.5 - math.sqrt(8885558)
-4.5474735088646412e-13

Checking all integers below 10**7 produced an error rate of almost exactly 0.1%, with the size of the error increasing (slowly) for larger numbers.

So the question is, which method is more accurate?

Edit: For the record, this isn't current causing me issues, nor do I know of a case where it might. But it's a slow Friday afternoon, so I ask out of curiosity and in the name of best practices. :-)

+8  A: 

Both the pow function and the math.sqrt() function can calculate results that are more accurate than what the default float type can store. I think the errors you're seeing is a result of the limitations of floating point math rather than inaccuracies of the functions. Also, since when is a difference of ~10^(-13) a problem when taking the square root of a 7 digit number? Even the most accurate physics calculations seldom requires that many significant digits...

Another reason to use math.sqrt() is that it's easier to read and understand, which generally is a good reason to do things a certain way.

Emil H
The answer to this question would seem to contradict your comment about speed: http://stackoverflow.com/questions/327002/python-which-is-faster-x-5-or-math-sqrtx
Ben Blank
A friend of mine actually benchmarked this earlier today, and concluded that math.sqrt() was faster. This was in the context of a project euler calculation. I trusted his word without checking his method, so that assertion might be incorrect. Still, it seems wildly unlikely that math.sqrt() would be slower. If that was the case, why didn't they simply implement it in turns of pow()?
Emil H
"in terms of pow()"... I'm a bit tired. :)
Emil H
My understanding is that the `**` operator is actually implemented via math.pow, but that it's faster __in Python__ because it doesn't require a symbol lookup.
Ben Blank
Sounds like an probable explanation, so you might be right. I think my friend (who isn't very experienced) benchmarked the pow() function rather than the operator. I'll remove my statement about the speed.
Emil H
I'll admit that I don't have a case on-hand where that level of accuracy is significant — I'm thinking more in terms of best practices. My main concern, actually, is Project Euler and the increasingly-large numbers which it's throwing at me. ;-)
Ben Blank
That's a great reason. PE is totally addictive. :D Take a look at decimal: http://docs.python.org/library/decimal.html It provides all the accuracy you'll ever need.
Emil H
I'll have to do that — I've put off checking out decimal for too long, anyway.
Ben Blank
A: 

If you've already tested all the integers below 10**7, why don't you tell us?

sth
All I have are two sets of (disagreeing) answers. I don't know how to tell which one's correct. :-)
Ben Blank
Multiply the result by itself and see which is closer to the original number?
Ken
@Ken - it doesn't necessarily work that way. Floating point numbers can be tricky.
Jason Baker
Amen to that! @Jason
SplittingField
I know "floating point numbers can be tricky", but I'm not sure what specific problems you'd have here (other than corner cases like NaNs). That's how square root is defined, after all!
Ken
+3  A: 

Any time you are given a choice between two functions which are built into a language, the more specific function will almost always be equal to or better than the generic one (since if it was worse, the coders would've just implemented it in terms of the generic function). Sqrt is more specific than generic exponentiation so you can expect it's a better choice. And it is, at least in terms of speed. In terms of accuracy, you aren't dealing with enough precision in your numbers to be able to tell.

Note: To clarify, sqrt is faster in Python 3.0. It's slower in older versions of Python. See J.F. Sebastians measurements at http://stackoverflow.com/questions/327002/python-which-is-faster-x-5-or-math-sqrtx .

Brian
Can you clarify that last sentence for me?
Ben Blank
Your point about speed is incorrect. **0.5 is actually faster than sqrt.
Unknown
Not in Python 3.0.
Brian
+1  A: 

I don't get the same behavior. Perhaps the error is platform specific? On amd64 I get this:

Python 2.5.2 (r252:60911, Mar 10 2008, 15:14:55) 
[GCC 3.3.5 (propolice)] on openbsd4
Type "help", "copyright", "credits" or "license" for more information.
>>> import math
>>> math.sqrt(8885558) - (8885558**.5)
0.0
>>> (8885558**.5) - math.sqrt(8885558)
0.0
I was afraid that might be the case — I read somewhere that math.sqrt is passed directly to the C implementation, but I can't find the reference right now.
Ben Blank
I can confirm the result.
David Zaslavsky
Ben: I can't find it documented, but it's true for Python 2.5 at least: http://tinyurl.com/omvudo
Ken
+1  A: 

This has to be some kind of platform-specific thing because I get different results:

Python 2.5.1 (r251:54863, Jan 13 2009, 10:26:13) 
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
>>> 8885558**.5 - math.sqrt(8885558)
0.0

What version of python are you using and what OS?

My guess is that it has something to do with promotion and casting. In other words, since you're doing 8885558**.5, 8885558 has to be promoted to a float. All this is handled differently depending on the operating system, processor, and version of Python. Welcome to the wonderful world of floating point arithmetic. :-)

Jason Baker
"Python 2.6.1 (r261:67517, Dec 4 2008, 16:51:00) [MSC v.1500 32 bit (Intel)] on win32"
Ben Blank
Interesting. Perhaps it's a 32 bit vs 64 bit issue of some kind? Me and the other person who got different results are both using a 64-bit OS. Or it could be a BSD thing. I'm using Mac OS X.
Jason Baker
I get 0.0 on 32-bit Linux as well (Python 2.5.2).
David Zaslavsky
+16  A: 

Neither one is more accurate, they both diverge from the actual answer in equal parts:

>>> (8885558**0.5)**2
8885557.9999999981
>>> sqrt(8885558)**2
8885558.0000000019

>>> 2**1023.99999999999
1.7976931348498497e+308

>>> (sqrt(2**1023.99999999999))**2
1.7976931348498495e+308
>>> ((2**1023.99999999999)**0.5)**2
1.7976931348498499e+308

>>> ((2**1023.99999999999)**0.5)**2 - 2**1023.99999999999
1.9958403095347198e+292
>>> (sqrt(2**1023.99999999999))**2 - 2**1023.99999999999
-1.9958403095347198e+292

http://mail.python.org/pipermail/python-list/2003-November/238546.html

The math module wraps the platform C library math functions of the same names; math.pow() is most useful if you need (or just want) high compatibility with C extensions calling C's pow().

__builtin__.pow() is the implementation of Python's infix ** operator, and deals with complex numbers, unbounded integer powers, and modular exponentiation too (the C pow() doesn't handle any of those).

** is more complete. math.sqrt is probably just the C implementation of sqrt which is probably related to pow.

Unknown
+1 for link to authoritative source :)
Van Gale
`sqrt` is one of the basic operations defined by IEEE 754. It should be implemented directly, not as a call to `pow`, and as a basic operation it should have correct rounding (which usual implementations of `pow` do not have, not by a long shot).
Pascal Cuoq
+4  A: 

Use decimal to find more precise square roots:

>>> import decimal
>>> decimal.getcontext().prec = 60
>>> decimal.Decimal(8885558).sqrt()
Decimal("2980.86531061032678789963529280900544861029083861907705317042")
J.F. Sebastian
+3  A: 

I got the same issue with you on Win XP Python 2.5.1, while I don't on 32-bit Gentoo Python 2.5.4. It's a matter of C library implementation.

Now, on Win, math.sqrt(8885558)**2 gives 8885558.0000000019, while (8885558**.5)**2 gives 8885557.9999999981, which seem to amount to the same epsilon.

I say that one cannot really say which one is the "better" option.

ΤΖΩΤΖΙΟΥ
+1  A: 

In theory math.sqrt should have a higher precision then math.pow. See Newton's method to compute square roots [0]. However the limitation in the number of decimal digits of the python float (or the C double) will probably mask the difference.

[0] http://en.wikipedia.org/wiki/Integer_square_root

Alexandru