Lately I have been playing a bit with FP in C# (esp with the new clr float
widenning).

I have put some thoughts for changes to the C# compiler here
http://geekswithblogs.net/gyoung/archive/2006/06/05/80747.aspx and would
like to hear other's thought either for or against those possible changes.

I've replied on your blog, but I think this is important enough to
dicuss in public as well.  I'm strongly against behavior-changing
command-line compile switches, and always have been.  They aren't
clearly visible when reading the code, so you don't know about the
magic happening (which is why the CLR changing rules bit you in the
first place... floating point implementation specific behavior
changed).

The root of this specific problem is breaking the commandments of
floating-point:

A) Thou shalt not compare floats of any size for EXACT equality.
B) Thou shalt use the Double.Epsilon or Float.Epsilon when appropriate
(to tell the absolute difference between two values of the same scale
very near zero)
C) Thou shalt use a "digits of significance" check when values may be
of wildly different scale [see code below]
D) If thy will is for consistent handling in a specific resolution,
thou shalt cast to Single or Double at every point thou wilst such
consistency
E) Thou shalt not complain when D doesn't cure your problems if thou
also violated  A through C.

Code to check floats/doubles for "significant differences" (since
Epsilon is way to tiny when comparing things that are not very need
zero).

       /// <summary>
       /// Allows reinterpretation conversion doubles to/from longs
AND floats to/from ints to allow direct
       /// manipulation of the bit patterns.  Similar to
BitConverter.DoubleToInt64 and
       /// BitConverter.Int64ToDouble, but also works for
floats/ints. Uses a clever trick of explicit
       /// struct layout to insure that the same memory is used for
all values suggested by Jeroen Frijters
       /// see http://groups.yahoo.com/group/win_tech_off_topic/message/31670
       /// </summary>
       
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]
       private struct BitReinterpreter
       {
           public static int Convert(float f)
           {
               BitReinterpreter br = new BitReinterpreter(f);
               return br.i;
           }

           public static float Convert(int i)
           {
               BitReinterpreter br = new BitReinterpreter(i);
               return br.f;
           }

           public static long Convert(double d)
           {
               BitReinterpreter br = new BitReinterpreter(d);
               return br.l;
           }

           public static double Convert(long l)
           {
               BitReinterpreter br = new BitReinterpreter(l);
               return br.d;
           }

           [System.Runtime.InteropServices.FieldOffset(0)]
           float f;
           [System.Runtime.InteropServices.FieldOffset(0)]
           int i;
           [System.Runtime.InteropServices.FieldOffset(0)]
           double d;
           [System.Runtime.InteropServices.FieldOffset(0)]
           long l;

           private BitReinterpreter(float f)
           {
               this.l = 0;
               this.d = 0;
               this.i = 0;
               this.f = f;
           }

           private BitReinterpreter(int i)
           {
               this.d = 0;
               this.l = 0;
               this.f = 0;
               this.i = i;
           }

           private BitReinterpreter(double d)
           {
               this.i = 0;
               this.f = 0;
               this.l = 0;
               this.d = d;
           }

           private BitReinterpreter(long l)
           {
               this.f = 0;
               this.i = 0;
               this.d = 0;
               this.l = l;
           }
       }

       /// <summary>
       /// Compared two single precision floating-point values to see
if they are equal within a
       /// designated precision specified as the number of digits in
the last significant place
       /// </summary>
       /// <param name="left">The first value</param>
       /// <param name="right">The second value</param>
       /// <param name="maxUnitsInLastPlace">The maximum error in
terms of Units in the Last Place.
       /// This specifies how big an error we are willing to accept
in terms of the value of the
       /// least significant digit of the floating point number's
representation.
       /// It can also be interpreted in terms of how many
representable doubles we are willing
       /// to accept between left and right. This function will allow
maxUnitsInLastPlace - 1 floats
       /// between left and right.</param>
       /// <returns>True if left and right are equal or nearly equal.
False if not nearly equal OR if
       /// either value is a NaN.</returns>
       public static bool AlmostEqual(float left, float right, int
maxUnitsInLastPlace)
       {
           // If left or right are infinity (positive or negative) then
           // only return true if they are exactly equal to each other -
           // that is, if they are both infinities of the same sign.
           if (float.IsInfinity(left) || float.IsInfinity(right))
               return left == right;

           // If left or right are a NAN, return false. NANs are
equal to nothing,
           // not even themselves.
           if (float.IsNaN(left) || float.IsNaN(right))
               return false;

           // The check for left == right is because zero and
negative zero have different
           // signs but are equal to each other.
           if (Math.Sign(left) != Math.Sign(right))
               return left == right;

           int aInt = BitReinterpreter.Convert(left);

           // Make aInt lexicographically ordered as a twos-complement int
           if (aInt < 0)
               aInt = (int)(0x80000000 - (uint)aInt);

           int bInt = BitReinterpreter.Convert(right);

           // Make bInt lexicographically ordered as a twos-complement int
           if (bInt < 0)
               bInt = (int)(0x80000000 - (uint)bInt);

           // Now we can compare aInt and bInt to find out how far
apart left and right
           // are.
           int intDiff = Math.Abs(aInt - bInt);

           if (intDiff <= maxUnitsInLastPlace)
               return true;

           return false;
       }

       /// <summary>
       /// Compared two double precision floating-point values to see
if they are equal within a
       /// designated precision specified as the number of digits in
the last significant place
       /// </summary>
       /// <param name="left">The first value</param>
       /// <param name="right">The second value</param>
       /// <param name="maxUnitsInLastPlace">The maximum error in
terms of Units in the Last Place.
       /// This specifies how big an error we are willing to accept
in terms of the value of the
       /// least significant digit of the floating point number's
representation.
       /// It can also be interpreted in terms of how many
representable doubles we are willing
       /// to accept between left and right. This function will allow
maxUnitsInLastPlace - 1 floats
       /// between left and right.</param>
       /// <returns>True if left and right are equal or nearly equal.
False if not nearly equal OR if
       /// either value is a NaN.</returns>
       public static bool AlmostEqual(double left, double right, long
maxUnitsInLastPlace)
       {
           // If left or right are infinity (positive or negative) then
           // only return true if they are exactly equal to each other -
           // that is, if they are both infinities of the same sign.
           if (double.IsInfinity(left) || double.IsInfinity(right))
               return left == right;

           // If left or right are a NAN, return false. NANs are
equal to nothing,
           // not even themselves.
           if (double.IsNaN(left) || double.IsNaN(right))
               return false;

           // The check for left == right is because zero and
negative zero have different
           // signs but are equal to each other.
           if (Math.Sign(left) != Math.Sign(right))
               return left == right;

           long aInt = BitReinterpreter.Convert(left);

           // Make aInt lexicographically ordered as a twos-complement int
           if (aInt < 0)
               aInt = (long)(0x8000000000000000 - (ulong)aInt);

           long bInt = BitReinterpreter.Convert(right);

           // Make bInt lexicographically ordered as a twos-complement int
           if (bInt < 0)
               bInt = (long)(0x8000000000000000 - (ulong)bInt);

           // Now we can compare aInt and bInt to find out how far
apart A and B
           // are.
           long intDiff = Math.Abs(aInt - bInt);

           if (intDiff <= maxUnitsInLastPlace)
               return true;

           return false;
       }

--
"One of the main causes of the fall of the Roman Empire was that,
lacking zero, they had no way to indicate successful termination of
their C programs." --Robert Firth

Marc C. Brooks
http://musingmarc.blogspot.com

===================================
This list is hosted by DevelopMentorĀ®  http://www.develop.com

View archives and manage your subscription(s) at http://discuss.develop.com

Reply via email to