using System; using System.Collections.Generic; using System.Runtime.InteropServices; namespace Nuclex.Support { /// Helper routines for working with floating point numbers /// /// The floating point comparison code is based on this excellent article: /// http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm /// public static class FloatHelper { #region struct FloatIntUnion /// Union of a floating point variable and an integer [StructLayout(LayoutKind.Explicit)] private struct FloatIntUnion { /// The union's value as a floating point variable [FieldOffset(0)] public float Float; /// The union's value as an integer [FieldOffset(0)] public int Int; /// The union's value as an unsigned integer [FieldOffset(0)] public uint UInt; } #endregion // struct FloatIntUnion #region struct DoubleLongUnion /// Union of a double precision floating point variable and a long [StructLayout(LayoutKind.Explicit)] private struct DoubleLongUnion { /// The union's value as a double precision floating point variable [FieldOffset(0)] public double Double; /// The union's value as a long [FieldOffset(0)] public long Long; /// The union's value as an unsigned long [FieldOffset(0)] public ulong ULong; } #endregion // struct DoubleLongUnion /// Compares two floating point values for equality /// First floating point value to be compared /// Second floating point value t be compared /// /// Maximum number of representable floating point values that are allowed to /// be between the left and the right floating point values /// /// True if both numbers are equal or close to being equal /// /// /// Floating point values can only represent a limited series of natural numbers. /// For example, the values 2.00000000 and 2.00000024 can be stored in a float, /// but nothing inbetween them. /// /// /// This comparison will count how many possible floating point values are between /// the left and the right number. If the number of possible values between both /// numbers is less than or equal to maxUlps, then the numbers are considered as /// being equal. /// /// /// Implementation partially follows the code outlined here: /// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ /// /// public static bool AreAlmostEqual(float left, float right, int maxUlps) { FloatIntUnion leftUnion = new FloatIntUnion(); FloatIntUnion rightUnion = new FloatIntUnion(); leftUnion.Float = left; rightUnion.Float = right; uint leftSignMask = (leftUnion.UInt >> 31); uint rightSignMask = (rightUnion.UInt >> 31); uint leftTemp = ((0x80000000 - leftUnion.UInt) & leftSignMask); leftUnion.UInt = leftTemp | (leftUnion.UInt & ~leftSignMask); uint rightTemp = ((0x80000000 - rightUnion.UInt) & rightSignMask); rightUnion.UInt = rightTemp | (rightUnion.UInt & ~rightSignMask); return (Math.Abs(leftUnion.Int - rightUnion.Int) <= maxUlps); } /// Compares two double precision floating point values for equality /// First double precision floating point value to be compared /// Second double precision floating point value t be compared /// /// Maximum number of representable double precision floating point values that are /// allowed to be between the left and the right double precision floating point values /// /// True if both numbers are equal or close to being equal /// /// /// Double precision floating point values can only represent a limited series of /// natural numbers. For example, the values 2.0000000000000000 and 2.0000000000000004 /// can be stored in a double, but nothing inbetween them. /// /// /// This comparison will count how many possible double precision floating point /// values are between the left and the right number. If the number of possible /// values between both numbers is less than or equal to maxUlps, then the numbers /// are considered as being equal. /// /// /// Implementation partially follows the code outlined here: /// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ /// /// public static bool AreAlmostEqual(double left, double right, long maxUlps) { DoubleLongUnion leftUnion = new DoubleLongUnion(); DoubleLongUnion rightUnion = new DoubleLongUnion(); leftUnion.Double = left; rightUnion.Double = right; ulong leftSignMask = (leftUnion.ULong >> 63); ulong rightSignMask = (rightUnion.ULong >> 63); ulong leftTemp = ((0x8000000000000000 - leftUnion.ULong) & leftSignMask); leftUnion.ULong = leftTemp | (leftUnion.ULong & ~leftSignMask); ulong rightTemp = ((0x8000000000000000 - rightUnion.ULong) & rightSignMask); rightUnion.ULong = rightTemp | (rightUnion.ULong & ~rightSignMask); return (Math.Abs(leftUnion.Long - rightUnion.Long) <= maxUlps); } /// /// Reinterprets the memory contents of a floating point value as an integer value /// /// /// Floating point value whose memory contents to reinterpret /// /// /// The memory contents of the floating point value interpreted as an integer /// public static int ReinterpretAsInt(float value) { FloatIntUnion union = new FloatIntUnion(); union.Float = value; return union.Int; } /// /// Reinterprets the memory contents of a double precision floating point /// value as an integer value /// /// /// Double precision floating point value whose memory contents to reinterpret /// /// /// The memory contents of the double precision floating point value /// interpreted as an integer /// public static long ReinterpretAsLong(double value) { DoubleLongUnion union = new DoubleLongUnion(); union.Double = value; return union.Long; } /// /// Reinterprets the memory contents of an integer as a floating point value /// /// Integer value whose memory contents to reinterpret /// /// The memory contents of the integer value interpreted as a floating point value /// public static float ReinterpretAsFloat(int value) { FloatIntUnion union = new FloatIntUnion(); union.Int = value; return union.Float; } /// /// Reinterprets the memory contents of an integer value as a double precision /// floating point value /// /// Integer whose memory contents to reinterpret /// /// The memory contents of the integer interpreted as a double precision /// floating point value /// public static double ReinterpretAsDouble(long value) { DoubleLongUnion union = new DoubleLongUnion(); union.Long = value; return union.Double; } } } // namespace Nuclex.Support