3

The following test will fail in C#

Assert.AreEqual<double>(10.0d, 16.1d - 6.1d);

The problem appears to be a floating point error.

16.1d - 6.1d == 10.000000000000002

This is causing me headaches in writing unit tests for code that uses double. Is there a way to fix this?

Reactgular
  • 52,335
  • 19
  • 158
  • 208
  • 1
    Yes, not using `double` if you need precision. Use `decimal`. And don't divide per 3, 7, 11, 13, 17, 19, 23, ... or they multiples :-) – xanatos Aug 18 '13 at 16:02

4 Answers4

5

There is no exact conversion between the decimal system and the binary representation of a double (see excellent comment by @PatriciaShanahan below on why).

In this case the .1 part of the numbers is the problem, it cannot be finitely represented in a double (like 1/3 can't be finitely represented exactly as a decimal number).

A code snippet to explain what happends:

double larger = 16.1d; //Assign closest double representation of 16.1.
double smaller = 6.1; //Assign closest double representation of 6.1.
double diff = larger - smaller; //Assign closest diff between larger and  
                                //smaller, but since a smaller value has a  
                                //larger precision the result will have better  
                                //precision than larger but worse than smaller. 
                                //The difference shows up as the ...000002.

Always use the Assert.Equal overload which takes a delta parameter when comparing doubles.

Alternatively if you really need exact decimal conversion, use the decimal data type, that has another binary representation and would return exactly 10 in your example.

Anders Abel
  • 67,989
  • 17
  • 150
  • 217
  • 2
    One slight clarification: Every value that can be expressed as a binary fraction has an exactly equal decimal fraction. That is because two is a factor of ten. The problem is specific to conversion from decimal to binary. – Patricia Shanahan Aug 18 '13 at 16:11
  • 1
    The number `16.1` lies in the mathematical interval `[16, 32)`, while the number `6.1` is in `[4, 8)`. The significance of this is that `16.1` uses two more bits than `6.1` for its integer part. Therefore `16.1` has two bits les for the fractional part, decimal `.1`, again compared to `6.1`. The infinite binary expansion of one tenth is therefore rounded at different "positions" in the two cases. When doing subtraction `16.1 - 6.1` which ends in `[8, 16)`, the difference in fractional part becomes important. – Jeppe Stig Nielsen Aug 19 '13 at 17:49
2

Floatingoint numbers are an estimate of the actual value based on an exponent so the test fails correctly. If you require exact equivalence in two decimal numbers you may need to check out the decimal data type.

1

If you are using NUnit please use the Within option. Here can you find additional information: http://www.nunit.org/index.php?p=equalConstraint&r=2.6.2.

meilke
  • 3,280
  • 1
  • 15
  • 31
  • But Within makes it much less readable: *Assert.That(order.Products.Skip(1).First()._FinalDiscountedPrice, Is.EqualTo(12.7161).Within(0.001).Percent);* – Vitalii Vasylenko May 10 '21 at 21:50
0

I agree with anders abel. There won't be a way to do this using a float number representation.
In direct result of IEE 1985-754 only the numbers that can be represented by
Powers of 2 in the sum
can be stored and calculated with precisly (as long as the chosen bit number allows this).

For Example :
1024 * 1.75 * 183.375 / 1040.0675 <-- will be stored precisly
10 / 1.1 <-- wont be stored precisly

If you are hardly interested in exact representation of rational numbers you could write your own number-implementation using fractions.
This could be done by saving numerator, denominator and sign. Then operations like multiply, subtract, etc. need to be implemented (very hard to ensure good performance). A toString()-method could look like this (I assume cachedRepresentation, cachedDotIndex and cachedNumerator to be member-variables)

 public String getString(int digits) {
            if(this.cachedRepresentation == ""){
                this.cachedRepresentation += this.positiveSign ? "" : "-";  
                this.cachedRepresentation += this.numerator/this.denominator; 
                this.cachedNumerator = 10 * (this.numerator % this.denominator);
                this.cachedDotIndex = this.cachedRepresentation.Length;
                this.cachedRepresentation += ".";
            }

            if ((this.cachedDotIndex + digits) < this.cachedRepresentation.Length)
                return this.cachedRepresentation.Substring(0, this.cachedDotIndex + digits + 1);

            while((this.cachedDotIndex + digits) >= this.cachedRepresentation.Length){
                this.cachedRepresentation += this.cachedNumerator / this.denominator;
                this.cachedNumerator = 10 * (this.cachedNumerator % denominator);
            }
            return cachedRepresentation;
        }


This worked for me. At the operations itself with long numbers I got some problems with too small datatypes (usually I don't use c#). I think for an experienced c#-developer it should be no problem to implement this without problems of to small datatypes.

If you want to implement this you should do minifications of the fraction at initializing and before operations using euclids greatest-common-divider.

Non rational numbers can (in every case I know) be specified by a algorithm that comes as close to the exact representation as you want (and computer allows).

L. Monty
  • 872
  • 9
  • 17