Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I'm in the middle of developing a new programming language to solve some business requirements, and this language is targeted at novice users. So there is no support for exception handling in the language, and I wouldn't expect them to use it even if I added it.

I've reached the point where I have to implement the divide operator, and I'm wondering how to best handle a divide by zero error?

I seem to have only three possible ways to handle this case.

  1. Ignore the error and produce 0 as the result. Logging a warning if possible.
  2. Add NaN as a possible value for numbers, but that raises questions about how to handle NaN values in other areas of the language.
  3. Terminate the execution of the program and report to the user a severe error occurred.

Option #1 seems the only reasonable solution. Option #3 is not practical as this language will be used to run logic as a nightly cron.

What are my alternatives to handling a divide by zero error, and what are the risks with going with option #1.

share|improve this question
8  
if you did add exception support and the user didn't catch it then you'd have option #3 –  ratchet freak Aug 18 '13 at 11:54
41  
I'm curious, what kind of stupid requirement would require you to create a whole new programming language? In my experience, every language ever created sucks (in design or in execution, often in both) and it took unreasonably much effort to even get that much. There are a few exceptions to the first, but not to the second, and as they're easily <0.01% of the cases, they're probably measurement errors ;-) –  delnan Aug 18 '13 at 12:04
11  
@delnan most new languages are created to allow business rules to be separated from how they are implemented. The user does not need to know how reject "Foo" was implemented, but simply that it rejects a document if it contains the keyword Foo. I try to make the language as easy to read using terms the user is familiar with. Giving a user their own programming language empowers them to add business rules without depending upon technical staff. –  Mathew Foscarini Aug 18 '13 at 12:15
6  
While not of the greatest concern if this is used in a learning environment exclusively, I could see some REALLY hard to detect bugs occurring with getting a 0 when I really shouldn't in scenario #1. I would expect something like INTMAX or INTMIN to be less anonymous if I was seeking out a bug. –  Rig Aug 18 '13 at 14:02
8  
@Mathew Foscarini. Never, ever, ignore the error and silently return 0. When doing a division, 0 may be a perfectly legal value (for some reason, there is such a thing in Power Basic, and it's really a pain). If you divie floating point numbers, Nan or Inf would be nice (have a look at IEEE 754 to understand why). If you divide integers, you may stop the program, divide by 0 should never be allowed (well, unless you want to implement a true exception system). –  Jean-Claude Arbaut Aug 19 '13 at 6:07
show 23 more comments

10 Answers

up vote 60 down vote accepted

I would strongly advise against #1, because just ignoring errors is a dangerous anti-pattern. It can lead to hard to analyze bugs. Setting the result of a division by zero to 0 makes no sense whatsoever, and continuing program execution with a nonsensical value is going to cause trouble. Especially when the program is running unattended. When the program interpreter notices that there is an error in the program (and a division-by-zero is almost always a design error), aborting it and keeping everything as-is is usually preferred over filling your database with garbage.

Also, you will unlikely be successful with thoroughly following this pattern through. Sooner or later you will run into error situations which just can't be ignored (like running out of memory or a stack overflow) and you will have to implement a way to terminate the program anyway.

Option #2 (using NaN) would be a bit of work, but not as much as you might think. How to handle NaN in different calculations is well-documented in the IEEE 754 standard, so you can likely just do what the language your interpreter is written in does.

By the way: Creating a programming language usable by non-programmers is something we try to do since 1964 (Dartmouth BASIC). So far, we were unsuccessful. But good luck anyway.

share|improve this answer
5  
+1 thanks. You convinced me to throw an error, and now that I read your answer I don't understand why I was hesitating. PHP has been a bad influence on me. –  Mathew Foscarini Aug 18 '13 at 14:17
11  
Yes, it has. When I read your question, I immediately thought that it was a very PHP-esque thing to produce incorrect output and keep trucking along in the face of errors. There are good reasons why PHP is the exception in doing this. –  Joel Aug 18 '13 at 16:14
2  
+1 for the BASIC comment. I don't advise using NaN in a beginner's language, but in general, great answer. –  Ross Patterson Aug 19 '13 at 11:15
3  
@Joel If he'd lived long enough, Dijkstra would probably have said "The use of [PHP] cripples the mind; its teaching should, therefore, be regarded as a criminal offense." –  Ross Patterson Aug 19 '13 at 11:16
5  
@Ross. "arrogance in computer science is measured in nano-Dijkstras" -- Alan Kay –  Jean-Claude Arbaut Aug 19 '13 at 12:44
show 2 more comments

1 - Ignore the error and produce 0 as the result. Logging a warning if possible.

That's not a good idea. At all. People will start depending on it and should you ever fix it, you will break a lot of code.

2 - Add NaN as a possible value for numbers, but that raises questions about how to handle NaN values in other areas of the language.

You should handle NaN the way runtimes of other languages do it: Any further calculation also yields NaN and every comparison (even NaN == NaN) yields false.

I think this is acceptable, but not necessarily new comer friendly.

3 - Terminate the execution of the program and report to the user a severe error occurred.

This is the best solution I think. With that information in hand users should be able to handle 0. You should provide a testing environment, especially if it's intended to run once a night.

There's also a fourth option. Make division a ternary operation. Any of these two will work:

  • div(numerator, denumerator, alternative_result)
  • div(numerator, denumerator, alternative_denumerator)
share|improve this answer
    
But if you make NaN == NaN be false, then you will have to add a isNaN() function so that users are able to detect NaNs. –  AJMansfield Aug 18 '13 at 20:54
2  
@AJMansfield: Either that, or people implement it themselves: isNan(x) => x != x. Still, when you have NaN coming up in your programming code, you should not start adding isNaN checks, but rather track down the cause and make the necessary checks there. Therefore it is important for NaN to propagate fully. –  back2dos Aug 18 '13 at 21:31
4  
NaNs are majorly counter-intuitive. In a beginner's language, they're dead on arrival. –  Ross Patterson Aug 19 '13 at 11:17
    
@RossPatterson But a beginner can easily say 1/0 -- you have to do something with it. There is no possibly useful result other than Inf or NaN -- something that will propagate the error further into the program. Otherwise the only solution is to stop with an error at this point. –  Mark Hurd Aug 21 '13 at 1:23
add comment

Terminate the running application with extreme prejudice. (While providing adequate debug information)

Then educate your users to identify and handle conditions where the divisor might be zero (user entered values, etc.)

share|improve this answer
add comment

Other answers have already considered the relative merits of your ideas. I propose another one: use basic flow analysis to determine whether a variable can be zero. Then you can simply disallow division by variables that are potentially zero.

x = ...
y = ...

if y ≠ 0:
  return x / y    // In this block, y is known to be nonzero.
else:
  return x / y    // This, however, is a compile-time error.

Alternatively, have an intelligent assert function that establishes invariants:

x = ...
require x ≠ 0, "Unexpected zero in calculation"
// For the remainder of this scope, x is known to be nonzero.

This is as good as throwing a runtime error—you skirt undefined operations entirely—but has the advantage that the code path need not even be hit for the potential failure to be exposed. It can be done much like ordinary typechecking, by evaluating all branches of a program with nested typing environments for tracking and verifying invariants:

x = ...           // env1 = { x :: int }
y = ...           // env2 = env1 + { y :: int }
if y ≠ 0:         // env3 = env2 + { y ≠ 0 }
  return x / y    // (/) :: (int, int ≠ 0) → int
else:             // env4 = env2 + { y = 0 }
  ...
...               // env5 = env2

Furthermore, it extends naturally to range and null checking, if your language has such features.

share|improve this answer
add comment

In Haskell (and similar in Scala), instead of throwing exceptions (or returning null references) the wrapper types Maybe and Either can be used. With Maybe the user has a chance to test if the value he got is "empty", or he might provide a default value when "unwrapping". Either is similar, but can be used returns an object (e.g. an error string) describing the problem if there is one.

share|improve this answer
    
True, but note that Haskell doesn't use this for division by zero. Instead, every Haskell type implicitly has "bottom" as a possible value. This isn't like null pointers in the sense that it's the "value" of an expression that fails to terminate. You can't test for nontermination as a value, of course, but in operational semantics the cases that fail to terminate are part of the meaning of an expression. In Haskell, that "bottom" value also handles additional error-case results such as the error "some message" function being evaluated. –  Steve314 Nov 15 '13 at 16:46
    
Personally, if the effect of aborting the whole program is considered valid, I don't know why pure code can't have the effect of throwing an exception, but that's just me - Haskell doesn't allow pure expressions to throw exceptions. –  Steve314 Nov 15 '13 at 16:49
add comment

Number 1 (insert undebuggable zero) is always bad. The choice between #2 (propagate NaN) and #3 (kill the process) depends on context and ideally should be a global setting, as it is in Numpy.

If you're doing one big, integrated calculation, propagating NaN is a bad idea because it will eventually spread and infect your whole calculation--- when you look at the results in the morning and see that they're all NaN, you'd have to throw out the results and start again anyway. It would have been better if the program terminated, you got a call in the middle of the night and fixed it--- in terms of the number of wasted hours, at least.

If you're doing many little, mostly-independent calculations (like map-reduce or embarrassingly parallel calculations), and you can tolerate some percentage of them being unusable due to NaNs, the that's probably the best option. Terminating the program and not doing the 99% that would be good and useful on account of the 1% that are malformed and divide by zero might be a mistake.

Another option, related to NaNs: the same IEEE floating-point specification defines Inf and -Inf, and these are propagated differently than NaN. For instance, I'm pretty sure that Inf > any number and -Inf < any number, which would be what you wanted if your division by zero happened because the zero was just supposed to be a small number. If your inputs are rounded and suffer from measument error (like physical measurements taken by hand), the difference of two large quantities can result in zero. Without the division-by-zero, you would have gotten some large number, and maybe you don't care how large it is. In that case, In and -Inf are perrfectly valid results.

It can be formally correct, too--- just say you're working in the extended reals.

share|improve this answer
    
But we can't tell whether the denominator was intended to be positive or negative, so division might yield +inf when -inf was desired, or visa versa. –  Daniel Nov 15 '13 at 17:31
    
True, your measurement error is too small to distinguish between +inf and -inf. This most closely resembles the Riemann sphere, in which the whole complex plane is mapped to a ball with exactly one infinite point (the point diametrically opposite to the origin). Very large positive numbers, very large negative numbers, and even very large imaginary and complex numbers are all close to that one infinite point. With a little measurement error, you can't distinguish them. –  Jim Pivarski Nov 15 '13 at 21:21
    
If you're working in that sort of system, you'd have to identify +inf and -inf as being equivalent, just as you have to identify +0 and -0 as being equivalent, even though they have different binary representations. –  Jim Pivarski Nov 15 '13 at 21:22
add comment

3. Terminate the execution of the program and report to the user a severe error occurred.

[This option] is not practical…

Of course it is practical: It is the programmers' responsibility to write a program that actually makes sense. Dividing by 0 does not make any sense. Therefore, if the programmer is performing a division, it is also his/her responsibility to verify beforehand that the divisor is not equal to 0. If the programmer fails to perform that validation check, then s/he should realize that mistake as soon as possible, and denormalized (NaN) or incorrect (0) computation results simply won't help in that respect.

Option 3 happens to be the one I would have recommended to you, btw, for being the most straightforward, honest, and mathematically correct one.

share|improve this answer
add comment

It seems like a bad idea to me to run important tasks (ie; "nightly cron") in an environment where errors are ignored. It's a terrible idea to make this a feature. This rules out options 1 and 2.

Option 3 is the only acceptable solution. Exceptions don't have to be part of the language, but they are part of reality. Your termination message ought to be as specific and informative as possible about the error.

share|improve this answer
add comment

Disallow it in the language. That is to say, disallow dividing by a number until it's provably not zero, usually by testing it first. Ie.

int div = random(0,100);
int b = 10000 / div; // Error E0000: div might be zero
share|improve this answer
    
To do this you need a new numeric type, a natural number, as opposed to an integer. That could be...difficult...to deal with. –  Servy Nov 15 '13 at 18:24
    
@Servy: No, you wouldn't. Why would you? You do need logic in the compiler to determine possible values, but you want that anyway (for optimizing reasons). –  MSalters Nov 15 '13 at 18:27
    
If you don't have a different type, one for zero and one for non-zero values, then you wouldn't be able to solve the problem in the general case. You'd either have false positives, and force the user to check against zero way more often than they actually should, or you'll create situations where they can still divide by zero. –  Servy Nov 15 '13 at 18:29
    
@Servy: You're mistaken: a compiler can track that state without needing such a type, and for instance GCC already does so. E.g. the C type int allows zero values, but GCC can still determine where in the code specific ints can't be zero. –  MSalters Nov 15 '13 at 18:32
1  
But only in certain cases; it can't do so, with 100% accuracy, in all cases. You'll either have false positives or false negatives. This is provably true. For example, I could create a snippet of code that may or may not even complete. If the compiler can't even know if it finished, how could it know if the resulting int is non-zero? It can catch simple obvious cases, but not all cases. –  Servy Nov 15 '13 at 18:35
add comment

Ohh, great idea to rule out 3. I'd go with option 1, but not only with division by zero! You could also, if the user tries to open a file that isn't there, automatically redirect to /dev/null, or, (even more fun!) to /dev/random.

</sarcasm>

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.