Rank: Advanced Member
Groups: Registered, Registered Users, Subscribers, Unverified Users Joined: 10/28/2004(UTC) Posts: 3,111 Location: Perth, Western Australia
Was thanked: 16 time(s) in 16 post(s)
|
It seems there are a bunch of people who have forgotten (how) to handle DBZ (divide by zero) errors; hence the bump of this thread, with a few more examples. 1. When do we need to check for DBZ?
Some coders will try to figure out when the denominator in a division operation may be zero and only then try to trap the error, but good coding practices mandate that EVERY TIME we perform a division operation, we MUST check for a zero condition. It isn't hard and it uses very little computing power; the time saved writing a simple check will save hours of frustration at erroneous results later. 2. How to check for DBZ in MSFL?
The MS formula language is a fully evaluated scripting language (there are lots of discussions on the Forum about this, but if you don't understand right now, press on reading anyway and it might become more apparent). MS also some issues when it comes to how it performs math operations on very large and very small numbers, so we need to be very careful how we get MS to perform what should be a simple check. Let's start with trying to gain an understanding of what doesn't work. An example of what DOESN'T work: Code:{Example 1}
{Bad example of DBZ check}
If( H-L=0, 0, CLOSE/(H-L) );
By reading the code, many would expect that when (H-L)=0 the function will return the TRUE value of the If() expression i.e. zero, and, only in the event that (H-L)<>0 will the division operation be evaluated in the FALSE value of the If() expression; but, MS is a fully evaluated language, so MS looks at BOTH values in the If() expression, so will return an error if either part contains an error. Another example of what DOESN'T work: Code:{Example 2}
{Bad example of DBZ check}
numerator:={some expression};
denominator:={some expression};
{plot}
numerator / (denominator + 0.00001);
An often touted solution to resolving DBZ is to simply add a very small value to the denominator on every computation. This is bad for two reasons: first, it introduces an error to the expected value of the division operation, but secondly and more importantly, what if the desired denominator is -0.00001? We'd be creating a DBZ when one shouldn't exist! 3. So, what does work?
Henry's method as show in the previous post works to trap the DBZ, but we can extend its functionality to avoid messing with the scaling when the denominator is very small; we 'eliminate' the result, or in the example below, we set the output to zero if the desired denominator is zero (we have to check this twice): Code:{Example 3}
{Better method to capture DBZ}
numerator:=CLOSE;
desiredDenominator:=HIGH - LOW;
verySmallNumber:=0.00001;
denominator:=If( desiredDenominator=0, verySmallNumber, desiredDenominator );
divisionOperation:=numerator / denominator;
{plot}
If( desiredDenominator=0, 0, divisionOperation );
Of course, you could return a different value than zero. 4. A practical example
On another forum, a member asked for assistance to resolve errors with this code: Code:{Sourav's Normalized Indicator}
Ind:=Cum(((Pwr((C-L),2)-Pwr((H-C),2))/(H-L))*V);
Npds:=Input("periods to normalize", 1,500,48);
Norm:=(Ind-LLV(Ind,Npds))/(HHV(Ind,Npds)-LLV(Ind,Npds)+.0000001)*100;
Norm;
Q. Can you spot where the errors will be generated? A. Of course you can! The DBZ error is being caused in the Ind line where a division operation is being carried out without a DBZ check. We can see the author is trapping the DBZ
error in the Norm line (and introducing accuracy errors!) Possible solution: Code:{Sourav's Normalized Indicator}
Npds:=Input("periods to normalize", 1,500,48);
verySmallNumber:=0.00001;
{first part of the operation}
numerator:=V*(Pwr(C-L,2)-Pwr(H-C,2));
desiredDenominator:=H-L;
denominator:=If( desiredDenominator=0, verySmallNumber, desiredDenominator);
Ind:= Cum(numerator / denominator);
{second part of the operation}
numerator:=100*(Ind-LLV(Ind,Npds));
desiredDenominator:=HHV(Ind,Npds)-LLV(Ind,Npds);
denominator:=If( desiredDenominator=0, verySmallNumber, desiredDenominator);
Norm:=numerator / denominator;
{plot}
Norm;
Note: I have written this in a very verbose form to emphasise the lessons; of course, this can be easily abbreviated. 5. More advanced methodsIf we KNOW the sign of the denominator i.e. positive or negative, then we can use some slightly more technical tricks, but you have to very sure! Code:{Example 4}
{We know the sign of the denominator will ALWAYS be positive}
numerator:=CLOSE;
denominator:=HIGH - LOW; {will always be greater than or equal to zero}
verySmallPosNumber:=0.00001;
{plot}
numerator / Max(denominator, verySmallPosNumber);
Code:{Example 5} {We know the sign of the denominator will ALWAYS be negative}
indy:=WillR(10);
numerator:=indy; denominator:=Ref(indy,-1); {will ALWAYS be less than or equal to zero}
verySmallNegNumber:=-0.00001;
return:=numerator / Min(verySmallNegNumber, denominator);
{plot} If( denominator=0, 0, return ); {see Example 3}
And of course, the more advanced coders will now be writing their own codes to test whether the denominator is positive or negative, and using a verySmallPosNumber or verySmallNegNumber as required. Armed with this knowledge, we can revisit Sourav's indicator: Code:{Sourav's Normalized Indicator}
Npds:=Input("periods to normalize", 1,500,48);
verySmallNumber:=0.00001;
{first part of the operation}
numerator:=V*(Pwr(C-L,2)-Pwr(H-C,2));
denominator:=H-L; {always >=0}
Ind:= Cum( numerator / Max(denominator, verySmallNumber) );
{second part of the operation}
numerator:=100*(Ind-LLV(Ind,Npds));
denominator:=HHV(Ind,Npds)-LLV(Ind,Npds); {always >=0}
Norm:=numerator / Max(denominator, verySmallNumber);
{plot}
Norm;
Anyway, I hope this clears up the DBZ issues. wabbit [:D]
|