Ben Blank
Ben Blank

Reputation: 56572

Which is more accurate, x**.5 or math.sqrt(x)?

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, the two methods produced different results for almost exactly 0.1% of the samples, with the size of the error increasing (slowly) for larger numbers.

So the question is, which method is more accurate?

Upvotes: 23

Views: 8499

Answers (9)

Emil H
Emil H

Reputation: 40240

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.

Upvotes: 11

Mark Ransom
Mark Ransom

Reputation: 308206

I performed the same test and got the same results, 10103 differences out of 10000000. This was using Python 2.7 in Windows.

The difference is one of rounding. I believe when the two results differ, it is only one ULP which is the smallest possible difference for a float. The true answer lies between the two, but a float doesn't have the ability to represent it exactly and it must be rounded.

As noted in another answer, the decimal module can be used to get better accuracy than a float. I utilized this to get a better idea of the true error, and in all cases the sqrt was closer than the **0.5. Although not by much!

>>> s1 = sqrt(8885558)
>>> s2 = 8885558**0.5
>>> s3 = decimal.Decimal(8885558).sqrt()
>>> s1, s2, s3
(2980.865310610327, 2980.8653106103266, Decimal('2980.865310610326787899635293'))
>>> s3 - decimal.Decimal(s1)
Decimal('-2.268290468226740188598632812E-13')
>>> s3 - decimal.Decimal(s2)
Decimal('2.2791830406379010009765625E-13')

Upvotes: 0

Unknown
Unknown

Reputation: 46781

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.

Upvotes: 25

Brian
Brian

Reputation: 25834

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 Which is faster in Python: x**.5 or math.sqrt(x)? .

Upvotes: 4

Alexandru
Alexandru

Reputation: 25810

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

Upvotes: 0

tzot
tzot

Reputation: 95961

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.

Upvotes: 3

jfs
jfs

Reputation: 414325

Use decimal to find more precise square roots:

>>> import decimal
>>> decimal.getcontext().prec = 60
>>> decimal.Decimal(8885558).sqrt()
Decimal("2980.86531061032678789963529280900544861029083861907705317042")

Upvotes: 7

Jason Baker
Jason Baker

Reputation: 198607

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. :-)

Upvotes: 3

user26294
user26294

Reputation: 5652

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

Upvotes: 1

Related Questions