By Michael R.
While working on a .NET 2.0 web application, the team encountered a bug of a particularly maddening sort (often described as a Heisenbug). The issue can be reproduced reliably, but only when the source is compiled and run in release mode. With debug-mode compilation, or stepping through a release-mode compile in Visual Studio, the code performs as expected.
Essentially, when the release-mode code executes outside of Visual Studio, requesting the index of a known item in a list returns -1, indicating no matching object was found in the list. IndexOf uses equality between the argument and each object in the list to determine if item is in the list. In this instance, the object is there, but the test for equality failed.
The details of using the debugger and the “Immediate window” to identify the culprit are fodder for future discussion. The bug itself, which is recreated in the console application below, shows why it failed, along with a demonstration of the fix. Some readers may notice that several details of implementing the IComparable interface are missing, which were omitted for brevity.
using System;
namespace FloatIssue
{
class Program
{
static void Main(string[] args)
{
MyFloat mf0 = new MyFloat(0f);
Console.WriteLine(“MyFloat(0f).Equals(self) : {0}”, mf0.Equals(mf0));
MyFloat mf1 = new MyFloat(0.01f);
Console.WriteLine(“MyFloat(0.01f).Equals(self) : {0}”, mf1.Equals(mf1));
Console.WriteLine(“MyFloat(0f).EqualsFix(self) : {0}”, mf0.EqualsFix(mf0));
Console.WriteLine(“MyFloat(0.01f).EqualsFix(self): {0}”, mf1.EqualsFix(mf1));
}
}
class MyFloat
{
private readonly float _DisplayValue;
public MyFloat(float pDisplayValue)
{
_DisplayValue = pDisplayValue;
}
public float Value
{
get
{
return _DisplayValue / 100;
}
}
public float DisplayValue
{
get
{
return _DisplayValue;
}
}
public bool Equals(MyFloat pFloatBug)
{
return (Value == pFloatBug.Value);
}
public bool EqualsFix(MyFloat pFloatFix)
{
return (DisplayValue == pFloatFix.DisplayValue);
}
}
}
The value object class is a composition of several other value object classes, so its equality test depends on the equality tests for its members. One member, which implements a percentage-weighted value, is represented by the MyFloat class. The parameters of the constructor include a display value. The display value is retrieved using the DisplayValue property, and the derived Value is calculated and returned by the getter each time the property is accessed. The Equals method tests equality of the (derived) Value properties.
In debug mode, the output of the application is:
MyFloat(0f).Equals(self) : True
MyFloat(0.01f).Equals(self): True
MyFloat(0f).EqualsFix(self) : True
MyFloat(0.01f).EqualsFix(self): True
However, in release mode, executed from the command line, the output is:
MyFloat(0f).Equals(self) : True
MyFloat(0.01f).Equals(self) : False
MyFloat(0f).EqualsFix(self) : True
MyFloat(0.01f).EqualsFix(self): True
A small change to the Equals method (shown as EqualsFix) to compare the readonly DisplayValue attribute rather than the derived-on-demand Value property causes the class to behave as expected in both modes.
—Michael R.
Is that a IEEE 754 failure in C#?
http://en.wikipedia.org/wiki/IEEE_754
Floating-point math = ghost in the machine.
I found this interesting.
http://www.math.utah.edu/~beebe/software/ieee/
@Bill:
We suspect it is. We are, as they say, running more tests (as well as adding similar QA checks for other systems). :-)
Thanks for the University of Utah link. It looks like there are some conversions to Java. Could a transliteration to C# be far behind?