Click here to Skip to main content
Click here to Skip to main content

Tagged as

Go to top

Go to Basic/ .NET Floating Numbers

, 10 Mar 2014
Rate this:
Please Sign up or sign in to vote.
Go to Basic/ .NET Floating Numbers

Introduction

This tip reveals number problems that exist in .NET. The first part describes common limitations of floating numbers. In the second part specific to .NET number problems are shown.

This will be very helpful for junior and mid level developers, though still in my career I have been meeting senior developers that are not aware of such basic things.

Part 1 - Floating Numbers

There are 3 types of floating numbers in .NET:

float or System.Single    // 32 bit floating number with base 2
double  or System.Double  // 64 bit floating number with base 2
decimal or System.Decimal // 128 bit floating number with base 10 

(float, double and decimal are the keywords available in C# and not in VB.)

Decimal floating number is mostly convenient for people.

decimal m = 102.45m 
Dim m As Decimal = 102.45 

In the computer, it will be represented approximately as we expected:

10^2   10^1    10^0 . 10^-1   10^-2
100    10      1    . 1/10     1/100
1      0       2    . 4        5  

The same strategy is used in Single and Double (base 2 floating numbers)

double d =  12.75 
Dim d as Double = 12.75 
2^3  2^2   2^1   2^0 . 2^-1  2^-2 
8    4     2     1   . 1/2   1/4  
---------------------------------------- 
1    1     0     0   . 1     1          // bit representation 
----------------------------------------
8  + 4  +  0  +  0   . 1/2 + 1/4 = 12.75    

The first restriction is that sum of 2^(-N) cannot map all fractional numbers with limitation in bits. For example, decimal 0.2 is represented in endless double number.

Using DoubleConverter created by Jon Skeet, we can look at the representation of the number 0.2 in double format:

0.200000000000000011102230246251565404236316680908203125 

So do not be surprised in equality checking for base-2 floating numbers (it is always approximation if exact conversion is not possible).

[TestMethod]
public void DoubleNumberEqualityCheck()
{
    double d1 = 0.6 - 0.2;
    double d2 = 0.4;
    Assert.AreNotEqual(d1, d2); // d1 != d2
}     
<TestMethod()> Public Sub DoubleNumberEqualityCheck()
     Dim d1 As Double = 0.6 - 0.2
     Dim d2 As Double = 0.4
     Assert.AreNotEqual(d1, d2)   ' d1 <> d2
End Sub  

Use number type that works for the task.
Decimal type fits well for money. Double and Single are good for scientific calculations, because processor works faster with them (than with Decimal) and approximation can be smartly taken.

Because of its nature, this limitation exists for any program language (Java, .NET, SQL).

Part 2 Floating Numbers in .NET

Conversion Problem

The next conversion problem is less known. Casting and converting in C# have distinct implementation.

[TestMethod]
public void ConvertAndCastFromDecimalToInt32ShouldBeTheSame()
{
     decimal m1 = 2.500001m;
     int i1 = (int)m1; // calls Decimal.ToInt32(value)
     int i2 = Convert.ToInt32(m1); // has own algorithm
     Assert.AreEqual(i1, i2); // i1 = 2; i2 = 3 
}   

In C# integer i1 = 2 after casting from decimal and conversion for the same decimal produces i2 = 3.

It is interesting that the same code in VB.NET with CType casting does not produce this error.

    <TestMethod()> Public Sub ConvertAndCastFromDecimalToInt32ShouldBeTheSame()
        Dim m1 As Decimal = 2.500001D
        Dim i1 As Integer = CType(m1, Integer)
        Dim i2 As Integer = Convert.ToInt32(m1)
        Assert.AreEqual(i1, i2)  '  i1 = 3; i2 = 3
    End Sub  

Only in C#, the same problem exists for double and int, float and int (Int32/Int64).

Not a Number

For Double and Single type, there are several special values reserved in .NET (and not for Decimal).

Double.NaN;
Double.NegativeInfinity;
Double.PositiveInfinity;

Single.NaN;
Single.NegativeInfinity;
Single.PositiveInfinity;  

These numbers can appear in special operations:

  • square root from negative number. e.g. Math.Sqrt(-2)
  • 0 / 0
  • 0 * 8
  • 8 / 8

Idea behind NaN numbers is that it should not be equal to another NaN number. Otherwise, how we should compare Math.Sqrt(-2) and Math.Sqrt(-100)?
So, Double.Nan != Double.Nan, however if we compare on equality with Equals() method, it will produce very messy result for NaN and Infinity.

Why Double.NaN != Double.NaN, but Double.NaN.Equals(Double.NaN) and Double.PositiveInfinity == Double.PositiveInfinity?

[TestMethod]
public void CheckNaNForInequality() // Test will pass without errors
{
        double d = Double.NaN;
        Assert.IsFalse(d == Double.NaN);
        Assert.IsTrue(d.Equals(Double.NaN));
        double d2 = Double.PositiveInfinity;
        Assert.IsTrue(d2 == Double.PositiveInfinity);
        Assert.IsTrue(d2.Equals(Double.PositiveInfinity));
        double d3 = Double.NegativeInfinity;
        Assert.IsTrue(d3 == Double.NegativeInfinity);
        Assert.IsTrue(d3.Equals(Double.NegativeInfinity));
}
<TestMethod()> Public Sub CheckNaNForInequality() // Test will pass without errors
        Dim d As Double = Double.NaN
        Assert.IsFalse(d = Double.NaN)
        Assert.IsTrue(d.Equals(Double.NaN))
        Dim d2 As Double = Double.PositiveInfinity
        Assert.IsTrue(d2 = Double.PositiveInfinity)
        Assert.IsTrue(d2.Equals(Double.PositiveInfinity))
        Dim d3 As Double = Double.NegativeInfinity
        Assert.IsTrue(d3 = Double.NegativeInfinity)
        Assert.IsTrue(d3.Equals(Double.NegativeInfinity))
End Sub   

To avoid errors, it is better to use next methods for special number checking:

Double.IsNaN(value)
Double.IsInfinity(value)

Double.IsNegativeInfinity(value)
Double.IsPositiveInfinity(value) 

(similar for Single)

There is a difference in representation between C# and VB in debug value inspection:

  • NaN is presented as NaN in C# and -1.#IND in VB (IND for indeterminate)
  • PositiveInfinity is presented as +Infinity in C# and 1.#INF in VB
  • NegativeInfinity is presented as -Infinity in C# and -1.#INF in VB

Related links

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

mighty-whity
Software Developer (Senior)
Russian Federation Russian Federation
No Biography provided

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 10 Mar 2014
Article Copyright 2014 by mighty-whity
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid