Subject: snmpconf PM: Integer NaN?

From: Pablo Halpern <[email-masked]>
Date: Tue, 13 Mar 2001 12:20:41 -0500

One thing that troubles me greatly about the current draft of the Policy 
MIB is that the policy language introduces the notion of an integer NaN 
(not-a-number). In no other language have I seen NaN represent an integer 
value. Every place I have seen it NaN has been a floating-point value. The 
purpose for introducing NaN is very dubious to me and seems have come about 
through an uncritical cut-and-past of ECMAScript Number semantics. 
(ECMAScript Numbers are floats.)  In the future, it seems certain that 
floating point numbers will be added to the set of SNMP data types. At that 
time, the Policy language will probably be extended to including floating 
point types as well. Introducing NaN for integers now will confuse matters 
when true floating-point NaN is needed later.  Let me, then break the issue 
into two parts:

1. Should we have NaN for integers?
2. If so, should it be called "NaN" or something else?

My opinion is that we should *not* have NaN for integers. One of our goals 
was to stick to existing language concepts was much as possible. The policy 
language is mostly C++ with a bit of ECMAScript (JavaScript) thrown in to 
make it better for scripting.  An integer NaN is not part of either C++ or 
ECMAScript.

In the draft as it currently is written, NaN is the result of one of the 
following:

1. Divide by zero
2. Converting a string to an Integer when that string does not contain a 
syntactically valid integer.
3. Using the result of either of the above in a computation.

All three of these constitute error conditions and should simply result in 
a run-time exception. Unlike floating point numbers, zero's don't happen 
"accidentally" in integer arithmetic as a result of lost precision. Thus, 
you should *never* have a divide-by-zero situation. Similarly, you should 
*never* attempt to convert a string to an integer unless you are sure it 
contains a syntactically valid integer. In both cases, the script writer is 
making an assumption about the inputs to an operation. If those assumptions 
(that the divisor is non-zero or that the string represents an integer) 
turn out to be wrong, the script should abort.

Scripting languages, unlike application languages should abort on a 
hair-trigger. It can be very damaging for a script to continue running 
after something has gone wrong.  It could result in shutting down a bunch 
of interfaces or changing some other configuration parameter. The current 
definition uses integer NaN values to make scripts continue running after 
an error. I don't think that's a good idea.

One might argue that a poorly-formed string might come from MIB objects and 
that it is too harsh to terminate the script in those situations. I don't 
agree. If you use a string in an integer context, it is because you are 
certain that the string will always represent an integer. If you're 
mistaken, an exception is the most humane way of alerting you. Propagating 
an NaN through calculations just puts the system into a strange state and 
does not help get the script's job done. At best, the script will silently 
do nothing. At worse, it could damage your configuration. Besides, how 
often will you retrieve an integer from a string-valued MIB object?

My proposal:

- Eliminate NaN from the current PM.
- Divide-by-zero causes a run-time exception and terminates the script.
- If ToString fails, it causes a run-time exception and terminates the script.
- Add an IsInteger() function that returns 1 if its argument is an integer 
or can be converted to an integer else returns 0.

P.S. If we decide to keep NaN, then let's at least rename it to "Error" or 
"IError" or something like that.  That way, it won't be confused with 
floating-point NaN.

- Pablo
---------------------------------------------------------------------
Pablo Halpern                                    [email-masked]
http://www.halpernwightsoftware.com

---

From: Steve Waldbusser <[email-masked]>
Date: Wed, 14 Mar 2001 12:40:09 -0800

I think the following is the real issue:

    Is it better for the script to silently terminate if ToInteger()
    fails or should the script be given an error indication and
    allowed to continue? The only reason the NaN error indication
    exists is because the following code must not continue to run
    without some error indication:

      var x, y = "foo";

      x = 5 * y; // equivalent to x = 5 * ToInteger("foo")

    clearly there is no defined integer value appropriate for x
    because ToInteger("foo") is undefined.

    The only 2 choices are to terminate the script or to flag x and
    any offspring of x with an error indication. Both do the job,
    while the latter provides some flexibility in error
    recovery. Since data that comes from the network can have
    unexpected values, this flexibility may be valuable. For example,
    if we retrieve objects that are expected to contain
    decimal-encoded strings yet it unexpectantly contains a
    non-decimal value, we may want to do some error-recovery.

    On the other hand, the var data type would be simpler without the
    NaN error indication. If it isn't going to be valuable, we
    should remove it.

    BTW, for what it's worth, Perl silently converts non-decimal
    strings to zero, raising no error indication. We are trying to
    avoid this behavior by choosing one of these two solutions.

    If we decide we like the flexibility offered by continuing while
    raising a flag, the next question is should we call the extra
    value "NaN" or give it some other name? (e.g., "Error").

> turn out to be wrong, the script should abort.

I think you have it wrong about why divide-by-zero terminates a C
program. The int data type contains no value that represents
infinity/NaN. Therefore, short of setting errno, the only option is to
abort the program. I don't think the analysis of "accidental" zero's
had any bearing on C's decision. Similarly, I don't think there is
any such rule that one should "*never* attempt to convert a string to
an integer ...". sscanf() certainly doesn't enforce such a rule.

> hair-trigger.

I'm not ready yet to accept this "hair-trigger doctrine".

> It can be very damaging for a script to continue running
> after something has gone wrong...

This would be true in Perl where you silently convert to zero. But
with an error indication, you can continue running with no correctness
issues. And if there were resources to clean up or state to restore,
it could be very damaging to *not* continue running. Of course, we
could use an IsValidInteger() function to bracket arithmetic code and
to call cleanup code. The question is "Which is best for the
programmer?" I don't yet know the right answer but it isn't as cut and
dried as you make it out to be.

Finally, the original message makes some claims that are false. After
considerable thought, I can assert the following
facts:

  Fact: This is the most straightforward translation of the ECMAScript
  var data type and its type conversion rules and there is no
  relevant inconsistency with C.
  Fact: This error value for integers won't interfere with future
  floating point whether we abstractly name it "NaN" or "Error" or
  "Foobar".

If we decide we want to keep using NaN, I'll go through the effort
to show why they are false (I'm deferring it now because it's tedious
and potentially avoidable if we decide that script termination is
better). These issues should not be considerations in deciding whether
termination or error indication is better for the programmer.

To sum up, I have no vested interest (or strong opinion) in the
outcome of the termination vs. error indication decision except to
make sure we analyze it thoroughly (I haven't yet) and give it a fair
hearing. Whatever conclusion we come to I want to come to it for the
right reason.

Steve

---

From: Steve Moulton <[email-masked]>
Date: Wed, 14 Mar 2001 19:41:01 -0500

A long time ago, I was a developer on a general statistical 
analysis system.  One of the concepts designed
in (and hardly original to the system in particular) was a 
"unknown value".  In doing statistical analyses, there are occasionally
holes in the data, and in some cases it is vital that any operation
where an unknown value was an operand produce an unknown value. 
Thus the effects of missing values propagate throughout the
calculation, and you _know_ if your results are unreliable, and
what part of your results are tainted.  

The idea of a NaN in the policy mib language is similar, and 
more like an "unknown value" than like a floating point NaN.

There are two upsides I can think of offhand:

.  You know what you don't know.  If something has failed, you
   don't inadvertently take its value to be zero, and have that
   error propagated throughout your whole calculation.

.  If you are willing to put in the additional care to address
   situations where this can occur, it can conveniently prevent
   programs from halting during critical calculations/configuration
   operations/whatever.

The downsides:

.  The script writer has to be very aware of the behaviors associated
   with a NaN/unknown value, and code accordingly.  Many programmers
   with a non-numerical background find the whole concept to be foreign
   and make false assumptions about the origin and behavior
   of NaNs in calculations (please don't read this as addressed
   to anyone involved in this issue).

.  Requires additional language design, implementation time, and
   coding complexity.

Steve has nicely and, and with only one minor omission that I can
see, covered the design issues.

The question that bothers me most is "under what circumstances can
a NaN-valued var enter a calculation".  I can only see the following:

.   divide by zero

.   toInteger(a-string-that-does-not-represent-an-integer)

As Jeff Case mentioned in an earlier message, the addition of
a isInteger(string) function will handle the second case.
Careful coding will in general handle the first.  Given 
an isInteger() accessor function to check the string before
converting it, and its proper use, any other situations giving
rise to these conditions should be regarded as an exception, I think.
For this limited number of cases, proper script coding would
probably be less onerous than the additional consideration required
for addressing NaNs.

While I will happily argue the utility of the NaN concept, 
unless there are more cases where NaNs can enter the system,
I don't think the additional utility will not make up for the cost.
Should more cases be found, I can easily be convinced otherwise.

Steve, thanks for giving the whole issue a patient hearing.

        - Steve
---
Steve Moulton        SNMP Research, Inc            voice: +1 865 573 1434
Sr Software Engineer 3001 Kimberlin Heights Rd.    fax: +1 865 573 9197
[email-masked]     Knoxville, TN 37920-9716 USA  http://www.snmp.com

---

From: Pablo Halpern <[email-masked]>
Date: Thu, 15 Mar 2001 11:24:13 -0500

>     without some error indication:

Who said anything about silent termination? A run-time exception (which is 
what I advocated) leaves an error trail, no?

>     non-decimal value, we may want to do some error-recovery.

My point was that a) this should be rare, since most integers are encoded 
as integers, not strings and b) Tainting values doesn't help unless you 
check for tainted values (presumably by comparing to "NaN"). If you have 
reason to expect bad values, then test for them in advance and take 
"recovery" action as needed. If not, then your code will cause bad things 
to happen on bad values unless we terminate the program for you.

The IsInteger() function I propose would allow you to do error recovery (or 
prevention) without exposing a quick-and-dirty script to the possibility of 
spinning its wheels and destroying your configuration in the process. My 
expectation is that a lot of these scripts will be in the quick-and-dirty 
category. Personally, I'd rather not check for errors unless I believe they 
are likely. I'd rather the script abort by itself and tell me that I goofed.

>     NaN error indication. If it isn't going to be valuable, we

I think we should remove it.  By the way, I don't think an error indication 
of this sort is necessarily a bad idea for all languages. I just think it 
doesn't belong in our small Policy scripting language.

>     avoid this behavior by choosing one of these two solutions.

I agree. Both solutions are better than silently converting to zero.

>     value "NaN" or give it some other name? (e.g., "Error").

I've already stated that I believe NaN is a bad name, even if we keep the 
general concept.

>an integer ...". sscanf() certainly doesn't enforce such a rule.

I said nothing about enforcement. The sscanf() function is an explicit 
conversion function. This is much less dangerous than the implicit 
conversions that occur in the Policy language. I thought about a way of 
modifying the Integer() function such that it could optionally return an 
error status rather than abort on error, but decided that an 
IsValidInteger() function would be simpler and less confusing.

>it could be very damaging to *not* continue running. Of course, we

Perl is far from a prefect scripting language. Its rules for boolean 
expressions are also badly flawed and can cause scripts to misbehave. 
(Unfortunately, it would be hard to fix the boolean rules in Perl because 
of its typelessness.)

>dried as you make it out to be.

We need to make the decision for the most common cases. Any run-time 
exception has the same potential flaw.  Do we want to add an 
exception-catching mechanism?

>   floating point whether we abstractly name it "NaN" or "Error" or

Fact: The above are opinions. I disagree with both (especially the latter).

>termination or error indication is better for the programmer.

Agreed.

>hearing. Whatever conclusion we come to I want to come to it for the

Agreed. You made some valid points, but my analysis still firmly points 
away from NaN.  I await your analysis and the analysis of others on this list.

- Pablo

---

From: Pablo Halpern <[email-masked]>
Date: Thu, 15 Mar 2001 11:41:51 -0500

>what part of your results are tainted.

Yes. I think the concept of an "unknown" and/or "error" value is very 
useful in certain environments. I am not against the concept in general. 
I'm just not convinced that it is the best thing for the Policy language.

>    error propagated throughout your whole calculation.

The only disagreement I have with this is use of the term "calculation."  A 
statistical package performs a series of calculations in which an unknown 
value may enter and where it is very useful to propagate that value to an 
eventual result with value "unknown."  In a scripting language, however, 
the value of an expression will often be used to control an if-statement or 
while-loop. The actions inside the if-statement will often be much more 
than just more calculation. Even if they are just calculations, the error 
value could easily be lost, leading to incorrect code:

           var y;
         var x = some-expression-returning-NaN;
         if (x > 5)
           y = 1;
         else
           y = 0;
         setvar("ifStatus", y, INTEGER);

In the above code, the if conditional will always evaluate to NaN which 
converts to FALSE. The setvar() at the end does not see the NaN 
value.  Scripts are not just calculations.  This is fixable (by having 
special rules for conditional expressions), but is it worth the added 
language complexity? What is the chance that we'll miss some other 
important situation that I haven't thought up yet?

>Steve, thanks for giving the whole issue a patient hearing.

Thank you, Steve, for your thoughtful response.

- Pablo

---

From: Steve Waldbusser <[email-masked]>
Date: Thu, 15 Mar 2001 16:24:56 -0800

Steve

> Given an isInteger() accessor function to check the string before

The thing I'm interested in learning is which is better for the
programmer, bracketing with isInteger() or checking the expression
value for NaN. The most interesting difference between them is that
isInteger is checked before the conversion and NaN is checked after
the conversion. For example:

With isInteger():
  // Retrieve 1 digit decimal integer at 7th byte of octet string object
  str = getVar("xxx");
  i = substr(str, 7, 1);
  if (isInteger(i)){
      value = i * 1000;
      doSomeThingWith(value);
  } else {
     // do some cleanup (perhaps of scratchpad contents)
     // or maybe call signalException()
  }

With NaN:
  // Retrieve 1 digit decimal integer at 7th byte of octet string object
  str = getVar("xxx");
  i = substr(str, 7, 1);
  value = i * 1000;
  if (String(value) == "NaN"){
     // do some cleanup (perhaps of scratchpad contents)
     // or maybe call signalException()
     return;
  }
  doSomeThingWith(value);

Based on this example, I don't see much difference between having
to check before or being able to check after (i.e. no benefit to 
NaN). One scenario where it would be simpler with NaN is where many 
operands are rolled up into a value - it would be simpler to check
the value instead of each operand. I don't think this happens very
often though.

Another angle on this decision is that by gaining the flexibility
to detect errors after the fact and correct them, you lose the
flexibility to *skip* error detection (unless you're willing
to throw correctness out the window).

> I don't think the additional utility will not make up for the cost.

No, there are no other cases.

Personally I'm leaning towards no NaN as well. Does anyone see cases
where the taint value would simplify scripts?

Steve

---

From: Pablo Halpern <[email-masked]>
Date: Thu, 15 Mar 2001 21:35:37 -0500

>to throw correctness out the window).

If I understand you correctly, this is the nub of my objection. I suspect 
that *most* scripts can handle a run-time exception as a default error 
handling. If the programmer wants to take error handling into his/her own 
hands, IsValidInteger() provides the flexibility to do so.  NaN, however 
*requires* that the programmer do the error checking, or else errors will 
never be detected.

>where the taint value would simplify scripts?

I'm sure we could come up with some, but I think there are more cases where 
the errors-cause-abort model would simplify scripts.

- Pablo

---

From: Juergen Schoenwaelder <[email-masked]>
Date: Fri, 16 Mar 2001 09:57:46 +0100

>>>>> Steve Waldbusser writes:

Steve> The thing I'm interested in learning is which is better for the
Steve> programmer, bracketing with isInteger() or checking the
Steve> expression value for NaN. The most interesting difference
Steve> between them is that isInteger is checked before the conversion
Steve> and NaN is checked after the conversion. For example:

[...]

Modern programming languages have a concept of exceptions which you
can catch and handle:

    try {
       // Retrieve 1 digit decimal integer at 7th byte of octet string object
       str = getVar("xxx");
       i = substr(str, 7, 1);
       value = i * 1000;
    }
    catch (conversion-error) {
       ...
    }

The general idea here is to stop execution as soon as you have an
exception (which is IMHO a good thing) and to pass the error up the
call stack for someone to catch and handle it. Additional constructs
such as "finally" make it easier to have cleanup code.

[I am sending this just to make it clear that this thread is about
 general exception handling and that there are more general ways to
 address this than inventing specific test functions or special values
 for certain data types.]

/js

-- 
Juergen Schoenwaelder      Technical University Braunschweig
<[email-masked]>  Dept. Operating Systems & Computer Networks
Phone: +49 531 391 3289    Bueltenweg 74/75, 38106 Braunschweig, Germany
Fax:   +49 531 391 5936    <URL:http://www.ibr.cs.tu-bs.de/~schoenw/>