To those of you who have not read my previous post, Dividing by zero with SAS, it's not too late to go back and make it up. You missed a lot of fun, deep thought and opportunity to solve an unusual SAS coding challenge.
For those who have already read it, let’s get serious for a second.
When it comes to division by zero in a SAS DATA step, I have found a lot of misconceptions and misunderstandings percolated among SAS online communities and discussion groups. The goal of this post is to dispel all those fallacies and delusions, also known in civilized societies as myths.
When SAS encounters division by zero during program execution it generates an ERROR message in the SAS log and stops. (A more histrionic version of this myth is, “Your computer disconnects from the Internet and burns down.”)
SAS does not generate an ERROR message in the SAS log and does not stop executing the current step and subsequent steps in the event of division by zero.
When SAS encounters division by zero during program execution it generates a WARNING message in the SAS log and continues execution.
Consider yourself officially warned that SAS does not generate even a WARNING message in the SAS log in the event of division by zero.
When SAS encounters division by zero during SAS data step execution it does the following:
1. For each occurrence, it generates a NOTE in the SAS log:
NOTE: Division by zero detected at line XXX column XX.
2. For each occurrence, it sets the result of division by zero to a missing value and dumps the Program Data Vector (PDV) including data step variables and automatic variables (_N_ and _ERROR_) to the SAS log, e.g.
a=0 b=0 c=. _ERROR_=1 _N_=1
3. Finally, it generates a summary NOTE to the SAS log, e.g.
NOTE: Mathematical operations could not be performed at the following places. The results of the operations have been set to missing values.
Each place is given by: (Number of times) at (Line):(Column).
3 at 244:8
You can run the following SAS code snippet to confirm reality over myth for yourself:
data a; input n d; datalines; 2 0 -2 0 0 0 ; data b; set a; c = n/d; run;
SAS programmers don’t care what messages SAS generates in the SAS log about division by zero and just ignore them.
Good programming practices and good SAS programmers do care about the possibility of dividing by zero and try to eliminate those messages by taking control over the situation. For example, instead of blindly dividing by a variable that can potentially have zero values, you can write the following “clean” code:
if d ne 0 then r = n/d; else r = .;
In this case the division by zero event will never happen during program execution, and you will not receive any nastygrams in the SAS log, but still the result will be the same – a missing value.
To avoid those “Division by zero detected” NOTEs, one can use ifn() function as in the following example:
r = ifn(d=0, ., n/d);
The assumption is that SAS will first evaluate the first argument (logical expression d=0); if it is true, then return the second argument (missing value); if false, then evaluate and return the third argument (n/d).
Despite the ifn() function being one of my favorites, the reality is that it works somewhat differently than the above assumption – it evaluates all its arguments before deciding which argument to return. If d does in fact equal 0, evaluating the third argument, n/d, will trigger an attempt to divide by 0, resulting in the “Division by zero detected” NOTE and the PDV dump in the SAS log; that disqualifies this function from being a graceful handler of division by zero events.
SAS does not have an effective solution for graceful handling of division by zero events; therefore, SAS programmers are compelled to write additional special programming logic.
SAS doesn’t just have an effective solution to gracefully handle division by zero events. It has a perfect solution! That perfect solution is even conveniently named the divide() function.
The divide(n,d) function has two arguments, each is either a numeric constant, variable, or expression. It divides the first argument by the second argument and returns a non-missing quotient in cases when none of the arguments is missing and the second argument is not zero. Otherwise, it returns missing value. No ugly “Division by zero detected” NOTES in the SAS log, just what we want.
So, instead of using:
r = n/d;
you would just use
r = divide(n,d);
Moreover, it gives you extended functionality by providing additional information about its argument’s composition. In the case of a zero divisor, it returns three different types of missing values. If the dividend (the first argument) is positive, it returns a special missing value of .I (I for infinity); if the dividend is negative, it returns special missing value of .M (M for minus infinity); if dividend is zero, it returns . as an ordinary missing value.
The icing on the cake
SAS’ missing() function equates all those ordinary and special missing values:
missing(.) = missing(.I) = missing(.M) = 1.
If for some reason you don’t like having different missing values X in your results after using the X=divide(n,d) function, you can easily recode them all to the generic missing value:
if missing(X) then X=.;
How do you prefer handling division by zero? Please share with us.