Let me quote the wonderful book Numerical Recipes in C++ (but also applicable)
We assume that you know enough never to evaluate a polynomial this way:
p=c[0]+c[1]*x+c[2]*x*x+c[3]*x*x*x+c[4]*x*x*x*x;
or (even worse!),
p=c[0]+c[1]*x+c[2]*pow(x,2.0)+c[3]*pow(x,3.0)+c[4]*pow(x,4.0);
Come the (computer) revolution, all persons found guilty of such criminal behavior will be summarily executed, and their programs won’t be!
(You can find the page in your edition in the analytical index, under the entry "puns, particullary bad". I love this book.)
There are two reasons not to do that: accuracy and performance. The correct way to evaluate the polynomial is like this:
-t * (0.319381530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + 1.330274429 * t))))
And you can, of course, split at your convenience, as the newlines inside parenthesis are ignored. Remember the PEP: " The preferred place to break around a binary operator is after the operator, not before it."
-t * (0.319381530 + t * (-0.356563782 +
t * (1.781477937 + t * (-1.821255978 + 1.330274429 * t))))
Another alternative is to save the coefficients in a list:
coeff = [0, 0.319381530, -0.356563782, 1.781477937, -1.821255978, 1.330274429]
poly = coeff[-1]
for i in xrange(len(coeff) - 2, -1, -1):
poly *= x
poly += coeff[i]
I am doing operations in place to avoid allocating and deallocating memory, but this is only relevant if x
is a numpy array. If you are evaluating on a single number, you can just use instead the nicer expression:
poly = poly * x + coeff[i]
But I would stick with the first one because it is more general.
Of course, the result has to be multiplied by the prefactor:
return 1 - 0.3989423*math.exp(-z*z/2) * poly
Or, if you want to do it in place:
z2 = z * z # Be careful not to modify your input!
z2 *= 0.5 # Multiplication is faster than division.
np.exp(z2, out=z2)
probd = z2 * poly
probd *= -0.3989423
probd += 1
return probd
Bonus track:
If you are applying this function to large arrays (more than a thousand numbers), you may benefit from using the first technique in numexpr:
expr += '1 - 0.3989423* exp(-z * z / 2) * '
expr += '(-t * (0.319381530 + t * (-0.356563782 + t * '
expr += '(1.781477937 + t * (-1.821255978 + 1.330274429 * t)))))'
ne.evaluate(expr)
This will compile the expression for you and transparently use as many cores as you have.
typedef
for your mathematical constants? It would make the code a hell of a lot more readable, imo. Something liketypdef 0.319381530 NORMALITY
. But you know, pick better names and stuff. For Python, you could just have all your constants at the top of the method (for readability), something likeNORMALITY = 0.319381530
. That's my preferred method of organizing mathematical functions anyway. – Chris Cirefice yesterdaytypedef
in Python, which is why I suggested just defining a "constant" likeNORMALITY = 0.319381530
. Personally, I don't like the list approach as suggested in @Davidmh's answer:coeff = [1, 0.319381530, -0.356563782, 1.781477937, -1.821255978, 1.330274429]
If there are a lot of constants, usingcoeff[0]
,coeff[1]
etc. starts to become unreadable in a long function, especially functions that use the constants multiple times. Again, just my opinion. – Chris Cirefice yesterday