Though you programmers all should have read What Every Computer Scientist Should Know About Floating-Point Arithmetic.
But I know you don’t, so below is a small difference on which floating point comparisons fail when using float (f, Single), double (d) and decimal (m, money) values in C#:
0.1 | fd_cast_none | fd_cast_up | fm_cast_none | dm_cast_none |
0.3 | fd_cast_none | fd_cast_up | fm_cast_none | dm_cast_none |
0.7 | fd_cast_none | fd_cast_up | fm_cast_none | dm_cast_none |
0.5 | fm_cast_none | dm_cast_none | ||
0.9 | fd_cast_none | fd_cast_up | fm_cast_none | dm_cast_none |
The can help you decide what kind of floating point type you want to use, for instance to answer c# – When should I use double instead of decimal? – Stack Overflow.
I specifically choose the values 0.1, 0.3, 0.5, 0.7 and 0.9 to stress the difference between binary and decimal representations. Apart from the decimal type, you cannot store these decimal values in a binary representation. You can see the decimal representation for a double using the DoubleConverter class (thanks Jon Skeet!)
If you are have a problem that isn’t suite for floating point, then don’t use it. Use rational types, IntX, BigInteger or Complex from the System.Numerics namespace, or arbitraty precision floating point numbers.
The failing methods are part of a bigger DecimalDoubleSingleTestProject, for which you will find the source at BeSharp.net repository (you can browse the sources, and access it through SVN and TFS).
That project contains more checks (see the table at the end which includes 0.100000000001 and 0.1222222222222222222221 based on the accuracy you can get with float/double/decimal) and the failing/succeeding methods are the same.
This is what the failing fd_cast_none
, fd_cast_up
, fm_cast_none
and dm_cast_none
methods do:
//... [TestClass] public class UnitTestBase { protected float f { get; private set; } protected double d { get; private set; } protected decimal m { get; private set; } //... public void TestMethod_fd_cast_none() { Assert.AreEqual(f, d); } //... public void TestMethod_fd_cast_up() { Assert.AreEqual((double)f, d); } //... public void TestMethod_fm_cast_none() { Assert.AreEqual(f, m); } //... public void TestMethod_dm_cast_none() { Assert.AreEqual(d, m); } }
The methods seem to succeed, but that is just the deceiving part: when you carefully select the values you compare, all checks will eventually fail.
The table at the end shows some more literals that fail other tests. It is caused by all these types have different storage formats.
<h3>Conclusion</h3>
When comparing floating point literals, make sure they are of the same type, and select the type according to what precision or representation features you need.
Note this gets even worse when you start calculating with floating points. You will almost always loose accuracy, watch rounding errors and you cannot even do direct AreEqual comparisons any more. Read the articles by Eric Lippert tagged floating+point+arithmetic – Fabulous Adventures In Coding.
–jeroen
Full table:
0.1 | fd_cast_none | fd_cast_up | fm_cast_none | dm_cast_none | |||
0.3 | fd_cast_none | fd_cast_up | fm_cast_none | dm_cast_none | |||
0.5 | fm_cast_none | dm_cast_none | |||||
0.7 | fd_cast_none | fd_cast_up | fm_cast_none | dm_cast_none | |||
0.9 | fd_cast_none | fd_cast_up | fm_cast_none | dm_cast_none | |||
0.100000000001 | fd_cast_none | fd_cast_up | fm_cast_none | fm_cast_up | dm_cast_none | ||
0.300000000003 | fd_cast_none | fd_cast_up | fm_cast_none | fm_cast_up | dm_cast_none | ||
0.500000000005 | fd_cast_none | fd_cast_up | fm_cast_none | fm_cast_up | dm_cast_none | ||
0.700000000007 | fd_cast_none | fd_cast_up | fm_cast_none | fm_cast_up | dm_cast_none | ||
0.900000000009 | fd_cast_none | fd_cast_up | fm_cast_none | fm_cast_up | dm_cast_none | ||
0.1222222222222222222221 | fd_cast_none | fd_cast_up | fm_cast_none | fm_cast_up | dm_cast_none | dm_cast_up | |
0.3222222222222222222223 | fd_cast_none | fd_cast_up | fm_cast_none | fm_cast_up | dm_cast_none | dm_cast_up | |
0.5222222222222222222225 | fd_cast_none | fd_cast_up | fm_cast_none | fm_cast_up | dm_cast_none | dm_cast_up | |
0.7222222222222222222227 | fd_cast_none | fd_cast_up | fm_cast_none | fm_cast_up | dm_cast_none | dm_cast_down | dm_cast_up |
0.9222222222222222222229 | fd_cast_none | fd_cast_up | fm_cast_none | fm_cast_up | dm_cast_none | dm_cast_up | |
144444444444444444444.1 | fd_cast_none | fd_cast_up | fm_cast_none | fm_cast_up | dm_cast_none | dm_cast_down | dm_cast_up |
344444444444444444444.3 | fd_cast_none | fd_cast_up | fm_cast_none | fm_cast_up | dm_cast_none | dm_cast_up | |
544444444444444444444.5 | fd_cast_none | fd_cast_up | fm_cast_none | fm_cast_up | dm_cast_none | dm_cast_up | |
744444444444444444444.7 | fd_cast_none | fd_cast_up | fm_cast_none | fm_cast_up | dm_cast_none | dm_cast_up | |
944444444444444444444.9 | fd_cast_none | fd_cast_up | fm_cast_none | fm_cast_up | dm_cast_none | dm_cast_up |