About

I am a software developer in Seattle, building a new AI software company.

Ads

April 2009

Sun Mon Tue Wed Thu Fri Sat
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    

Categories

Ads


« Math Quiz II | Main | Pointers in VB »

January 10, 2005

Numbers in .NET

Working in Excel, I encountered all sorts of issues with floating point numbers. Excel had to deal with NaNs (of which there are many), +/- infinity, negative zeros (1/NegativeInfinity), and denormalized numbers (numbers smaller than 1/double.MaxValue but larger than zero). In addition, there were also a number of rounding issues, with which we solved with Visual Rounding (converting a number to its visual representation in base ten) and chopping the result of a difference to zero if the result is 16 decimal places (precision) away from the operands.

It's funny how a lot of familiar assumptions break down because using numbers in computers. .NET introduces a number of new issues with numeric types.

  1. +/- Infinity and NaNs are more likely to occur in .NET than in the past. Double & Single do not trigger Division By Zero exceptions as they do in other languages such as C++ (and probably even C++/CLI).
  2. Int32 (int) can safely be converted to higher order types (Decimal, Double, and Int64) except Single as it can be represented completely and accurately in any one of those representations.
  3. Conversions to Double/Single never cause exceptions, because of the ability to represent the full range of numbers, possibly by losing precision or by collapsing a large value to infinity or a small value to zero. Notably, a double to single conversion always succeeds.
  4. Both Int64 and Decimal have higher precision, 64 and 96 bits, respectively, than the floating-point cousins. Thus, neither can be accurately represented by a Double or Single. So, for example, (Int64) (Double) Int64.MaxValue < Int64.MaxValue. Despite the higher precision, both types have lower range and can produce overflow exceptions when converting from a floating-point value.
  5. Decimal values have the fewest surprises, since it uses a base 10 scale and has almost twice the precision of a double. However, one quirk is that the same value can have two different string representations: For example, 1.00m and 1m print out differently, yet are equals for all purposes.
Type Bytes Precision Range
Single 4 23+1 bits (7 digits) 10 ^ 38
Double 8 52+1 bits (15 digits) 10 ^ 308
Int32 4 32 bits (9 digits) 10 ^ 9
Int64 8 64 bits (19 digits) 10 ^ 19
Decimal 16 96 bits (28 digits) 10 ^ 28

Floating-point numbers (Single & Double) are as fast as integral types (Int32 & Int64) and even slightly faster in certain operations such as multiplication and division, so speed is never really a factor in the decision to use one over the other except when using a floating-number requires it to be cast into an integral type first. However, the Decimal type, which is not supported in hardware, is almost 3 orders of magnitude slower than the other types.

There's no single universal numeric type; you have to trade off range and performance against precision and accuracy. I never really understood why the Decimal type couldn't have allowed a scale that could encompass the full range of Double values.

---------------

Anyway, in my quiz, I came up with various assumptions we have that are ordinarily true about numbers in the real world, but prove to be false within the realm of computers. Unfortunately, two of my questions are ambiguous and another one is incorrectly written. It wasn't suppose to be a hard test. In practice, these assumptions are almost always valid, because the edge cases in which they fail are rare--basically because the magnitudes in edge case are high, approximating infinity.

If x and y are ints, ....

  1. If x < 0, then -x > 0. False, because the negation of int.MinValue is itself. This is due to the assymetry of having more negative values than positive values. Note that such an action would throw an overflow exception if checked arithmetic is enabled.
  2. If x = -x, then x - 0. False, for the same reasons as above.
  3. If x - y > 0, then x > y. False, because of overflow such as in the case int.MinValue-int.MaxValue = 1. This can only fail if any of operands are greater than or equal to 2^30 in magnitude.
  4. If x and y are positive, then x + y > x. False, because of overflow as in the case of int.MaxValue + int.MaxValue == -2.
  5. If x and y are positive, then (double)x * (double)y = (long)x * (long)y. False. The question is ambiguous because of casting issues. The product of two positive ints fits into a long but not necessarily in a double due to loss of precision.
  6. If x-y>0 and y-z>0, then x-z>0. False, because of overflow such as with x=int.MaxValue-1, y=-1, and z=int.MinValue. I was testing out the Law of Transitivity for inequality and this was the best violation I could come up with.

Most problems with ints can be checked by turning on overflow checking. This is why I leave checked mode turned on in debug mode.

If x and y are doubles, ...

  1. x = x. False, NaNs behave like database nulls, returning false for all comparison operators except inequality.
  2. If x>y is false, then x<=y is true. False, due to NaNs.
  3. If x > 0, then x - x = 0. False, because difference of two Infinite value of the same sign is a NaN. The ratio of two Infinite values is also NaN.
  4. If x and y are positive integers, then x + y > x.  False, due to precision loss. If x/y is greater than 1o^16, than y essentially becomes zero and x + y = x.
  5. If x and y are positive integers, then the statement "x + y = x for all y" is false for all x. True. If x were PositiveInfinity, the conclusion would be false, but the premise precludes that possibility. If x were MaxValue, the statement "x + y = x" is true for all 0< y < approx. 10e+292, which is all the numbers that people use.
  6. If x <= 0 is false, then x > 0.  False, due to NaNs. I was initially thinking about negative zeros here, but negative zeros are not distinguishable from zeros except in the case of division by zero.
  7. If x and y are longs, then (double)(x + y) = (long)(x + y). False. This is a somewhat ambiguous question, but I was trying to emphasize potential loss of precision when converting a long to a double. For example, (long) (double) long.MaxValue doesn't equal long.MaxValue.
  8. if x.Equals(y), then x = y. False, NaNs will Equals themselves, which is necessary for collections operations to work.
  9. If x.Equals(-x), then x = 0.  False, the Negation of a NaN is itself.
  10. if x.ComparesTo(y) < 0, then x < y. False, NaNs evaluate to -1 against all other value.

In my second quiz,

  1. If x and y are double and x = y,  then 1/x = 1/y. If x is zero and y is negative zero, the 1/x is positive infinity and 1/y is negative infinity. Negative zero is obtained by operation which leads to negative underflow such as 1e-200/1e200 or 1/NegativeInfinity and is normally indistinguable from positive zero.
  2. If x is an int, then x >> 1  = x / 2. False, this statement fails for negative odd numbers. The arithmetic shift right operator performs a floor division by 2, where as the division operator truncates toward zero.
  3. If x and y are decimal and x = y,  then x.ToString() == y.ToString(). False. 1.00m and 1m are equal in value but have different internal and string representations.
  4. 1 / ( 1/ x ) = x. False. The reciprocal of double.Epsilon and other denormalized numbers is Infinity.
  5. If x is a long, (long) (double) x = x. False, If x is long.MaxValue, conversion to a double loses precision.
  6. If x is a double and an integer and y = x, then x++ causes x > y. False, adding one to 10^17 or greater doesn't change the value.

The following mathematical laws may not work with doubles due to rounding errors.

  • Associative Law: a + (b + c) = (a + b) + c.
    Fails when int.MaxValue + (int.MaxValue - int.MaxValue).
  • Commutative Law: a + b = b + a.
  • Distributive Law:  a( b + c ) = ab + bc.
    Fails when a is +-Infinity and b and c have opposite signs.

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/services/trackback/6a00d8345242f069e200d83422a8ec53ef

Listed below are links to weblogs that reference Numbers in .NET:

» A must read about numbers in .NET from Insert Zippy Title Here
[Read More]

» RE: Numbers in .NET from IImplement
[Read More]

» Dealing With Numbers from Stuart Radcliffe
When there is a possibility of an overflow condition many of the normal math rules break down. [Read More]

Comments

Here are two more:

If x is an int:

1. -x = x * -1
2. -x = x / -1

Does anybody have an ideea how to demonstrate (in Excel or .NET) that

(a+b) + c != a + (b+c)

?

Thank you

Excel is a little harder, because of Visual Rounding after calculating the result but perform placing the value in a cell. In effect, Excel makes double values behaves as if they were written in base ten.

Great little quizzes. Regarding the 'new' issues in .NET:

1. This isn't exactly true. The default behavior (according to the IEEE-754 standard) should be that floating-point exceptions are signaled (i.e. a flag is raised in the FPU status register), but execution continues with a default value. For operations like 0/0 and Inf/Inf, this default value is NaN. For underflow it's 0, and for overflow it's +/-Inf.

In C++ and many other languages, you can detect FP exceptions by inspecting the status register, and - if you choose to - trap them as they occur.

The CLI specification states that the default behavior is what we get. Nothing more.

2. This isn't new to .NET.

3. "a double to single conversion always succeeds" is true, but if the value is outside the range of single, then either 0 or Infinity is returned.

Question 8 of the Double quiz is interesting. A NaN value has a biased exponent of 2047 (11 ones) and a non-zero mantissa. NaN's were originally intended to carry more information (a 'payload') in the mantissa bits. The CLI doesn't make this distinction. It treats NaN's as one value regardless of the payload. So even if x and y are NaN's with different payloads, x.Equals(y) will still be true.

In other words, if x.Equals(y) then BitConverter.DoubleToInt64Bits(x) == BitConverter.DoubleToInt64Bits(y) is false.

And finally: the statement "Operations on Double values are always carried out in at least double precision" is also false. See the 'How Do I...' section on http://msdn.microsoft.com/library/en-us/directx9_m/directx/ref/ns/microsoft.directx.direct3d/c/device/m/ctor3.asp

Verify your Comment

Previewing your Comment

This is only a preview. Your comment has not yet been posted.

Working...
Your comment could not be posted. Error type:
Your comment has been posted. Post another comment

The letters and numbers you entered did not match the image. Please try again.

As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.

Having trouble reading this image? View an alternate.

Working...

Post a comment