diff --git a/src/Managed.UnitTests/Managed.UnitTests.csproj b/src/Managed.UnitTests/Managed.UnitTests.csproj index 5826c19e..c5560bed 100644 --- a/src/Managed.UnitTests/Managed.UnitTests.csproj +++ b/src/Managed.UnitTests/Managed.UnitTests.csproj @@ -60,6 +60,7 @@ + diff --git a/src/Managed.UnitTests/PrecisionTest.cs b/src/Managed.UnitTests/PrecisionTest.cs new file mode 100644 index 00000000..c9569cda --- /dev/null +++ b/src/Managed.UnitTests/PrecisionTest.cs @@ -0,0 +1,936 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://mathnet.opensourcedotnet.info +// +// Copyright (c) 2009 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using MbUnit.Framework; + +namespace MathNet.Numerics.UnitTests +{ + [TestFixture] + public sealed class PrecisionTest + { + private const double mAcceptableError = 1e-12; + private readonly double mDoublePrecision = System.Math.Pow(2, -53); + + [Test] + public void Magnitude() + { + Assert.AreEqual(0, Precision.Magnitude(0)); + + Assert.AreEqual(0, Precision.Magnitude(1)); + Assert.AreEqual(1, Precision.Magnitude(10)); + Assert.AreEqual(2, Precision.Magnitude(100)); + Assert.AreEqual(3, Precision.Magnitude(1000)); + Assert.AreEqual(4, Precision.Magnitude(10000)); + Assert.AreEqual(5, Precision.Magnitude(100000)); + Assert.AreEqual(6, Precision.Magnitude(1000000)); + + Assert.AreEqual(7, Precision.Magnitude(1e7)); + Assert.AreEqual(8, Precision.Magnitude(1e8)); + Assert.AreEqual(9, Precision.Magnitude(1e9)); + Assert.AreEqual(10, Precision.Magnitude(1e10)); + Assert.AreEqual(11, Precision.Magnitude(1e11)); + Assert.AreEqual(12, Precision.Magnitude(1e12)); + + Assert.AreEqual(5, Precision.Magnitude(1.1e5)); + Assert.AreEqual(-5, Precision.Magnitude(2.2e-5)); + Assert.AreEqual(9, Precision.Magnitude(3.3e9)); + Assert.AreEqual(-11, Precision.Magnitude(4.4e-11)); + } + + // Bug fix test: Magnitude of negative numbers returns zero + [Test] + public void MagnitudeWithNegativeValues() + { + Assert.AreEqual(0, Precision.Magnitude(-1)); + Assert.AreEqual(1, Precision.Magnitude(-10)); + Assert.AreEqual(2, Precision.Magnitude(-100)); + Assert.AreEqual(3, Precision.Magnitude(-1000)); + Assert.AreEqual(4, Precision.Magnitude(-10000)); + Assert.AreEqual(5, Precision.Magnitude(-100000)); + Assert.AreEqual(6, Precision.Magnitude(-1000000)); + + Assert.AreEqual(7, Precision.Magnitude(-1e7)); + Assert.AreEqual(8, Precision.Magnitude(-1e8)); + Assert.AreEqual(9, Precision.Magnitude(-1e9)); + Assert.AreEqual(10, Precision.Magnitude(-1e10)); + Assert.AreEqual(11, Precision.Magnitude(-1e11)); + Assert.AreEqual(12, Precision.Magnitude(-1e12)); + + Assert.AreEqual(5, Precision.Magnitude(-1.1e5)); + Assert.AreEqual(-5, Precision.Magnitude(-2.2e-5)); + Assert.AreEqual(9, Precision.Magnitude(-3.3e9)); + Assert.AreEqual(-11, Precision.Magnitude(-4.4e-11)); + } + + [Test] + public void Value() + { + Assert.AreApproximatelyEqual(0, Precision.Value(0), mAcceptableError); + + Assert.AreApproximatelyEqual(1, Precision.Value(1), mAcceptableError); + Assert.AreApproximatelyEqual(1, Precision.Value(10), mAcceptableError); + Assert.AreApproximatelyEqual(1, Precision.Value(100), mAcceptableError); + Assert.AreApproximatelyEqual(1, Precision.Value(1000), mAcceptableError); + Assert.AreApproximatelyEqual(1, Precision.Value(10000), mAcceptableError); + Assert.AreApproximatelyEqual(1, Precision.Value(100000), mAcceptableError); + Assert.AreApproximatelyEqual(1, Precision.Value(1000000), mAcceptableError); + + Assert.AreApproximatelyEqual(1.1, Precision.Value(1.1e5), mAcceptableError); + Assert.AreApproximatelyEqual(2.2, Precision.Value(2.2e-5), mAcceptableError); + Assert.AreApproximatelyEqual(3.3, Precision.Value(3.3e9), mAcceptableError); + Assert.AreApproximatelyEqual(4.4, Precision.Value(4.4e-11), mAcceptableError); + } + + // Bug fix test: Magnitude of negative numbers returns zero + [Test] + public void ValueWithNegativeValues() + { + Assert.AreApproximatelyEqual(0, Precision.Value(0), mAcceptableError); + + Assert.AreApproximatelyEqual(-1, Precision.Value(-1), mAcceptableError); + Assert.AreApproximatelyEqual(-1, Precision.Value(-10), mAcceptableError); + Assert.AreApproximatelyEqual(-1, Precision.Value(-100), mAcceptableError); + Assert.AreApproximatelyEqual(-1, Precision.Value(-1000), mAcceptableError); + Assert.AreApproximatelyEqual(-1, Precision.Value(-10000), mAcceptableError); + Assert.AreApproximatelyEqual(-1, Precision.Value(-100000), mAcceptableError); + Assert.AreApproximatelyEqual(-1, Precision.Value(-1000000), mAcceptableError); + + Assert.AreApproximatelyEqual(-1.1, Precision.Value(-1.1e5), mAcceptableError); + Assert.AreApproximatelyEqual(-2.2, Precision.Value(-2.2e-5), mAcceptableError); + Assert.AreApproximatelyEqual(-3.3, Precision.Value(-3.3e9), mAcceptableError); + Assert.AreApproximatelyEqual(-4.4, Precision.Value(-4.4e-11), mAcceptableError); + } + + [Test] + public void IncrementAtZero() + { + double x = -2 * double.Epsilon; + Assert.AreEqual(-2 * double.Epsilon, x); + x = Precision.Increment(x); + x = Precision.Increment(x); + Assert.AreEqual(0.0, x); + x = Precision.Increment(x); + x = Precision.Increment(x); + Assert.AreEqual(2 * double.Epsilon, x); + } + + [Test] + public void DecrementAtZero() + { + double x = 2 * double.Epsilon; + Assert.AreEqual(2 * double.Epsilon, x); + x = Precision.Decrement(x); + x = Precision.Decrement(x); + Assert.AreEqual(0.0, x); + x = Precision.Decrement(x); + x = Precision.Decrement(x); + Assert.AreEqual(-2 * double.Epsilon, x); + } + + [Test] + public void IncrementAtMinMax() + { + double x = double.MaxValue; + x = Precision.Increment(x); + Assert.AreEqual(double.PositiveInfinity,x); + + x = double.MinValue; + Assert.AreEqual(double.MinValue, x); + x = Precision.Increment(x); + Assert.GreaterThan(x, double.MinValue); + } + + [Test] + public void DecrementAtMinMax() + { + double x = double.MaxValue; + Assert.AreEqual(double.MaxValue, x); + x = Precision.Decrement(x); + Assert.GreaterThan(double.MaxValue, x); + + x = double.MinValue; + Assert.AreEqual(double.MinValue, x); + x = Precision.Decrement(x); + Assert.AreEqual(double.NegativeInfinity, x); + } + + [Test] + public void CoerceZero() + { + Assert.AreEqual(0.0, Precision.CoerceZero(0d)); + Assert.AreEqual(0.0, Precision.CoerceZero(Precision.Increment(0.0))); + Assert.AreEqual(0.0, Precision.CoerceZero(Precision.Decrement(0.0))); + + Assert.AreEqual(1.0, Precision.CoerceZero(1d)); + Assert.AreEqual(-1.0, Precision.CoerceZero(-1d)); + Assert.AreEqual(0.5, Precision.CoerceZero(0.5d)); + Assert.AreEqual(-0.5, Precision.CoerceZero(-0.5d)); + + Assert.AreEqual(double.PositiveInfinity, Precision.CoerceZero(double.PositiveInfinity)); + Assert.AreEqual(double.NegativeInfinity, Precision.CoerceZero(double.NegativeInfinity)); + Assert.AreEqual(double.MaxValue, Precision.CoerceZero(double.MaxValue)); + Assert.AreEqual(double.MinValue, Precision.CoerceZero(double.MinValue)); + Assert.AreEqual(double.NaN, Precision.CoerceZero(double.NaN)); + } + + [Test] + public void CoerceZeroBasedOnMaxNumbersBetween() + { + Assert.AreEqual(0.0, Precision.CoerceZero(5 * double.Epsilon, 5)); + Assert.AreEqual(0.0, Precision.CoerceZero(-5 * double.Epsilon, 5)); + + Assert.AreNotEqual(0.0, Precision.CoerceZero(5 * double.Epsilon, 4)); + Assert.AreNotEqual(0.0, Precision.CoerceZero(-5 * double.Epsilon, 4)); + } + + [Test] + public void CoerceZeroBasedOnRelativeTolerance() + { + Assert.AreEqual(0.0, Precision.CoerceZero(1e-6, 1e-5)); + Assert.AreEqual(0.0, Precision.CoerceZero(-1e-6, 1e-5)); + Assert.AreEqual(0.0, Precision.CoerceZero(1e+4, 1e+5)); + Assert.AreEqual(0.0, Precision.CoerceZero(-1e+4, 1e+5)); + + Assert.AreNotEqual(0.0, Precision.CoerceZero(1e-4, 1e-5)); + Assert.AreNotEqual(0.0, Precision.CoerceZero(1e-5, 1e-5)); + Assert.AreNotEqual(0.0, Precision.CoerceZero(-1e-4, 1e-5)); + Assert.AreNotEqual(0.0, Precision.CoerceZero(-1e-5, 1e-5)); + Assert.AreNotEqual(0.0, Precision.CoerceZero(1e+6, 1e+5)); + Assert.AreNotEqual(0.0, Precision.CoerceZero(1e+5, 1e+5)); + Assert.AreNotEqual(0.0, Precision.CoerceZero(-1e+6, 1e+5)); + Assert.AreNotEqual(0.0, Precision.CoerceZero(-1e+5, 1e+5)); + } + + [Test] + public void RangeOfMatchingFloatingPointNumbersWithNegativeUlps() + { + double max; + double min; + Assert.Throws(() => Precision.RangeOfMatchingFloatingPointNumbers(10, -1, out min, out max)); + } + + [Test] + public void RangeOfMatchingFloatingPointNumbersWithValueAtInfinity() + { + double max; + double min; + + Precision.RangeOfMatchingFloatingPointNumbers(double.PositiveInfinity, 1, out min, out max); + Assert.AreEqual(double.PositiveInfinity, min); + Assert.AreEqual(double.PositiveInfinity, max); + + Precision.RangeOfMatchingFloatingPointNumbers(double.NegativeInfinity, 1, out min, out max); + Assert.AreEqual(double.NegativeInfinity, min); + Assert.AreEqual(double.NegativeInfinity, max); + } + + [Test] + public void RangeOfMatchingFloatingPointNumbersWithValueAtNaN() + { + double max; + double min; + + Precision.RangeOfMatchingFloatingPointNumbers(double.NaN, 1, out min, out max); + Assert.IsTrue(double.IsNaN(min)); + Assert.IsTrue(double.IsNaN(max)); + } + + // Numbers are calculated with the UlpsCalculator program which can be found in UlpsCalculator.cs + [Row(0, -10 * double.Epsilon, 10 * double.Epsilon)] + [Row(1.0, 0.99999999999999889, 1.0000000000000022)] + [Row(2.0, 1.9999999999999978, 2.0000000000000044)] + [Row(3.0, 2.9999999999999956, 3.0000000000000044)] + [Row(4.0, 3.9999999999999956, 4.0000000000000089)] + [Row(5.0, 4.9999999999999911, 5.0000000000000089)] + [Row(6.0, 5.9999999999999911, 6.0000000000000089)] + [Row(7.0, 6.9999999999999911, 7.0000000000000089)] + [Row(8.0, 7.9999999999999911, 8.0000000000000178)] + public void RangeOfMatchingFloatingPointNumbersWithPositiveValue(double input, double expectedMin, double expectedMax) + { + double max; + double min; + + Precision.RangeOfMatchingFloatingPointNumbers(input, 10, out min, out max); + Assert.AreEqual(expectedMin, min); + Assert.AreEqual(expectedMax, max); + } + + // Numbers are calculated with the UlpsCalculator program which can be found in UlpsCalculator.cs + [Row(0, -10 * double.Epsilon, 10 * double.Epsilon)] + [Row(-1.0, -1.0000000000000022, -0.99999999999999889)] + [Row(-2.0, -2.0000000000000044, -1.9999999999999978)] + [Row(-3.0, -3.0000000000000044, -2.9999999999999956)] + [Row(-4.0, -4.0000000000000089, -3.9999999999999956)] + [Row(-5.0, -5.0000000000000089, -4.9999999999999911)] + [Row(-6.0, -6.0000000000000089, -5.9999999999999911)] + [Row(-7.0, -7.0000000000000089, -6.9999999999999911)] + [Row(-8.0, -8.0000000000000178, -7.9999999999999911)] + public void RangeOfMatchingFloatingPointNumbersWithNegativeValue(double input, double expectedMin, double expectedMax) + { + double max; + double min; + + Precision.RangeOfMatchingFloatingPointNumbers(input, 10, out min, out max); + Assert.AreEqual(expectedMin, min); + Assert.AreEqual(expectedMax, max); + } + + [Row(0, 10 * double.Epsilon)] + [Row(1.0, 1.0000000000000022)] + [Row(2.0, 2.0000000000000044)] + [Row(3.0, 3.0000000000000044)] + [Row(4.0, 4.0000000000000089)] + [Row(5.0, 5.0000000000000089)] + [Row(6.0, 6.0000000000000089)] + [Row(7.0, 7.0000000000000089)] + [Row(8.0, 8.0000000000000178)] + public void MaximumMatchingFloatingPointNumberWithPositiveValue(double input, double expectedMax) + { + double max = Precision.MaximumMatchingFloatingPointNumber(input, 10); + Assert.AreEqual(expectedMax, max); + } + + [Row(0, 10 * double.Epsilon)] + [Row(-1.0, -0.99999999999999889)] + [Row(-2.0, -1.9999999999999978)] + [Row(-3.0, -2.9999999999999956)] + [Row(-4.0, -3.9999999999999956)] + [Row(-5.0, -4.9999999999999911)] + [Row(-6.0, -5.9999999999999911)] + [Row(-7.0, -6.9999999999999911)] + [Row(-8.0, -7.9999999999999911)] + public void MaximumMatchingFloatingPointNumberWithNegativeValue(double input, double expectedMax) + { + double max = Precision.MaximumMatchingFloatingPointNumber(input, 10); + Assert.AreEqual(expectedMax, max); + } + + [Row(0, -10 * double.Epsilon)] + [Row(1.0, 0.99999999999999889)] + [Row(2.0, 1.9999999999999978)] + [Row(3.0, 2.9999999999999956)] + [Row(4.0, 3.9999999999999956)] + [Row(5.0, 4.9999999999999911)] + [Row(6.0, 5.9999999999999911)] + [Row(7.0, 6.9999999999999911)] + [Row(8.0, 7.9999999999999911)] + public void MinimumMatchingFloatingPointNumberWithPositiveValue(double input, double expectedMin) + { + double min = Precision.MinimumMatchingFloatingPointNumber(input, 10); + Assert.AreEqual(expectedMin, min); + } + + [Row(0, -10 * double.Epsilon)] + [Row(-1.0, -1.0000000000000022)] + [Row(-2.0, -2.0000000000000044)] + [Row(-3.0, -3.0000000000000044)] + [Row(-4.0, -4.0000000000000089)] + [Row(-5.0, -5.0000000000000089)] + [Row(-6.0, -6.0000000000000089)] + [Row(-7.0, -7.0000000000000089)] + [Row(-8.0, -8.0000000000000178)] + public void MinimumMatchingFloatingPointNumberWithNegativeValue(double input, double expectedMin) + { + double min = Precision.MinimumMatchingFloatingPointNumber(input, 10); + Assert.AreEqual(expectedMin, min); + } + + [Test] + public void RangeOfMatchingUlpsWithNegativeRelativeDifference() + { + long min; + long max; + Assert.Throws(() => Precision.RangeOfMatchingNumbers(1, -1, out min, out max)); + } + + [Test] + public void RangeOfMatchingUlpsWithValueAtInfinity() + { + long min; + long max; + Assert.Throws(() => Precision.RangeOfMatchingNumbers(double.PositiveInfinity, -1, out min, out max)); + } + + [Test] + public void RangeOfMatchingUlpsWithValueAtNaN() + { + long min; + long max; + Assert.Throws(() => Precision.RangeOfMatchingNumbers(double.NaN, -1, out min, out max)); + } + + [Row(0, 10 * double.Epsilon, 10, 10)] + [Row(1.0, (1.0000000000000022 - 1.0) / 1.0, 20, 10)] + [Row(2.0, (2.0000000000000044 - 2.0) / 2.0, 20, 10)] + [Row(3.0, (3.0000000000000044 - 3.0) / 3.0, 10, 10)] + [Row(4.0, (4.0000000000000089 - 4.0) / 4.0, 20, 10)] + [Row(5.0, (5.0000000000000089 - 5.0) / 5.0, 10, 10)] + [Row(6.0, (6.0000000000000089 - 6.0) / 6.0, 10, 10)] + [Row(7.0, (7.0000000000000089 - 7.0) / 7.0, 10, 10)] + [Row(8.0, (8.0000000000000178 - 8.0) / 8.0, 20, 10)] + public void RangeOfMatchingUlpsWithPositiveValue(double input, double relativeDifference, long expectedMin, long expectedMax) + { + long min; + long max; + + Precision.RangeOfMatchingNumbers(input, relativeDifference, out min, out max); + Assert.AreEqual(expectedMin, min); + Assert.AreEqual(expectedMax, max); + } + + [Row(0, 10 * double.Epsilon, 10, 10)] + [Row(-1.0, (1.0000000000000022 - 1.0) / 1.0, 10, 20)] + [Row(-2.0, (2.0000000000000044 - 2.0) / 2.0, 10, 20)] + [Row(-3.0, (3.0000000000000044 - 3.0) / 3.0, 10, 10)] + [Row(-4.0, (4.0000000000000089 - 4.0) / 4.0, 10, 20)] + [Row(-5.0, (5.0000000000000089 - 5.0) / 5.0, 10, 10)] + [Row(-6.0, (6.0000000000000089 - 6.0) / 6.0, 10, 10)] + [Row(-7.0, (7.0000000000000089 - 7.0) / 7.0, 10, 10)] + [Row(-8.0, (8.0000000000000178 - 8.0) / 8.0, 10, 20)] + public void RangeOfMatchingUlpsWithNegativeValue(double input, double relativeDifference, long expectedMin, long expectedMax) + { + long min; + long max; + + Precision.RangeOfMatchingNumbers(input, relativeDifference, out min, out max); + Assert.AreEqual(expectedMin, min); + Assert.AreEqual(expectedMax, max); + } + + [Test] + public void TestNumbersBetween() + { + Assert.AreEqual(0, Precision.NumbersBetween(1.0, 1.0)); + Assert.AreEqual(0, Precision.NumbersBetween(0, 0)); + Assert.AreEqual(0, Precision.NumbersBetween(-1.0, -1.0)); + + Assert.AreEqual(1, Precision.NumbersBetween(0, double.Epsilon)); + Assert.AreEqual(1, Precision.NumbersBetween(0, -double.Epsilon)); + Assert.AreEqual(1, Precision.NumbersBetween(double.Epsilon, 0)); + Assert.AreEqual(1, Precision.NumbersBetween(-double.Epsilon, 0)); + + Assert.AreEqual(2, Precision.NumbersBetween(0, 2 * double.Epsilon)); + Assert.AreEqual(2, Precision.NumbersBetween(0, -2 * double.Epsilon)); + + Assert.AreEqual(3, Precision.NumbersBetween(-double.Epsilon, 2 * double.Epsilon)); + Assert.AreEqual(3, Precision.NumbersBetween(double.Epsilon, -2 * double.Epsilon)); + } + + // AlmostZero + [Test] + public void AlmostZeroWithMaxNumbersBetween() + { + Assert.IsTrue(Precision.AlmostZero(0, 1)); + Assert.IsTrue(Precision.AlmostZero(1 * double.Epsilon, 1)); + Assert.IsTrue(Precision.AlmostZero(10 * double.Epsilon, 10)); + + Assert.IsFalse(Precision.AlmostZero(double.NegativeInfinity, 1)); + Assert.IsFalse(Precision.AlmostZero(double.PositiveInfinity, 1)); + Assert.IsFalse(Precision.AlmostZero(double.NaN, 1)); + } + + [Test] + public void AlmostZeroWithTolerance() + { + Assert.IsTrue(Precision.AlmostZero(0)); + Assert.IsTrue(Precision.AlmostZero(1 * double.Epsilon, 2 * double.Epsilon)); + Assert.IsTrue(Precision.AlmostZero(10 * double.Epsilon, 20 * double.Epsilon)); + + Assert.IsFalse(Precision.AlmostZero(double.NegativeInfinity, 1.0)); + Assert.IsFalse(Precision.AlmostZero(double.PositiveInfinity, 1.0)); + Assert.IsFalse(Precision.AlmostZero(double.NaN, 1.0)); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AlmostEqualWithMaxNumbersBetweenWithLessThanOneNumber() + { + Precision.AlmostEqual(1, 2, 0); + } + + [Test] + public void AlmostEqualWithMaxNumbersBetween() + { + // compare zero and negative zero + Assert.IsTrue(Precision.AlmostEqual(0, -0, 1)); + + // compare two nearby numbers + Assert.IsFalse(Precision.AlmostEqual(1.0, 1.0 + 3 * mDoublePrecision, 1)); + Assert.IsTrue(Precision.AlmostEqual(1.0, 1.0 + mDoublePrecision, 1)); + Assert.IsTrue(Precision.AlmostEqual(1.0, 1.0 - mDoublePrecision, 1)); + Assert.IsFalse(Precision.AlmostEqual(1.0, 1.0 - 3 * mDoublePrecision, 1)); + + // compare with the two numbers reversed in compare order + Assert.IsFalse(Precision.AlmostEqual(1.0 + 3 * mDoublePrecision, 1.0, 1)); + Assert.IsTrue(Precision.AlmostEqual(1.0 + mDoublePrecision, 1.0, 1)); + Assert.IsTrue(Precision.AlmostEqual(1.0 - mDoublePrecision, 1.0, 1)); + Assert.IsFalse(Precision.AlmostEqual(1.0 - 3 * mDoublePrecision, 1.0, 1)); + + // compare two slightly more different numbers + Assert.IsFalse(Precision.AlmostEqual(1.0, 1.0 + 10 * mDoublePrecision, 1)); + Assert.IsTrue(Precision.AlmostEqual(1.0, 1.0 + 10 * mDoublePrecision, 10)); + Assert.IsTrue(Precision.AlmostEqual(1.0, 1.0 - 10 * mDoublePrecision, 10)); + Assert.IsFalse(Precision.AlmostEqual(1.0, 1.0 - 10 * mDoublePrecision, 1)); + + // compare different numbers + Assert.IsFalse(Precision.AlmostEqual(2.0, 1.0, 1)); + Assert.IsFalse(Precision.AlmostEqual(1.0, 2.0, 1)); + + // compare different numbers with large tolerance + Assert.IsFalse(Precision.AlmostEqual(1.0, 1.0 + 1e5 * mDoublePrecision, 1)); + Assert.IsTrue(Precision.AlmostEqual(1.0, 1.0 - 1e5 * mDoublePrecision, 200000)); + Assert.IsFalse(Precision.AlmostEqual(1.0, 1.0 - 1e5 * mDoublePrecision, 1)); + + // compare inf & inf + Assert.IsTrue(Precision.AlmostEqual(double.PositiveInfinity, double.PositiveInfinity, 1)); + Assert.IsTrue(Precision.AlmostEqual(double.NegativeInfinity, double.NegativeInfinity, 1)); + + // compare -inf and inf + Assert.IsFalse(Precision.AlmostEqual(double.PositiveInfinity, double.NegativeInfinity, 1)); + Assert.IsFalse(Precision.AlmostEqual(double.NegativeInfinity, double.PositiveInfinity, 1)); + + // compare inf and non-inf + Assert.IsFalse(Precision.AlmostEqual(double.PositiveInfinity, 1.0, 1)); + Assert.IsFalse(Precision.AlmostEqual(1.0, double.PositiveInfinity, 1)); + + Assert.IsFalse(Precision.AlmostEqual(double.NegativeInfinity, 1.0, 1)); + Assert.IsFalse(Precision.AlmostEqual(1.0, double.NegativeInfinity, 1)); + + // compare tiny numbers with opposite signs + Assert.IsFalse(Precision.AlmostEqual(double.Epsilon, -double.Epsilon, 1)); + Assert.IsFalse(Precision.AlmostEqual(-double.Epsilon, double.Epsilon, 1)); + + Assert.IsFalse(Precision.AlmostEqual(-2.0, 2.0, 1)); + Assert.IsFalse(Precision.AlmostEqual(2.0, -2.0, 1)); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AlmostEqualWithinDecimalPlacesWithDecimalPlacesLessThanOne() + { + Precision.AlmostEqualInDecimalPlaces(1, 2, 0); + } + + [Test] + public void AlmostEqualWithinDecimalPlaces() + { + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(1, double.NaN, 2)); + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(double.NaN, 2, 2)); + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(double.NaN, double.NaN, 2)); + + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(double.NegativeInfinity, 2, 2)); + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(1, double.NegativeInfinity, 2)); + + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(double.PositiveInfinity, 2, 2)); + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(1, double.PositiveInfinity, 2)); + + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(double.NegativeInfinity, double.PositiveInfinity, 2)); + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(double.PositiveInfinity, double.NegativeInfinity, 2)); + + Assert.IsTrue(Precision.AlmostEqualInDecimalPlaces(double.PositiveInfinity, double.PositiveInfinity, 2)); + Assert.IsTrue(Precision.AlmostEqualInDecimalPlaces(double.NegativeInfinity, double.NegativeInfinity, 2)); + + Assert.IsTrue(Precision.AlmostEqualInDecimalPlaces(1.0, 1.04, 2)); + Assert.IsTrue(Precision.AlmostEqualInDecimalPlaces(1.0, 0.96, 2)); + + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(1.0, 1.06, 2)); + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(1.0, 0.94, 2)); + + Assert.IsTrue(Precision.AlmostEqualInDecimalPlaces(100.0, 104.00, 2)); + Assert.IsTrue(Precision.AlmostEqualInDecimalPlaces(100.0, 96.000, 2)); + + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(100.0, 106.00, 2)); + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(100.0, 94.000, 2)); + + Assert.IsTrue(Precision.AlmostEqualInDecimalPlaces(0.0, 4 * mDoublePrecision, 12)); + Assert.IsTrue(Precision.AlmostEqualInDecimalPlaces(0.0, -4 * mDoublePrecision, 12)); + } + + [Test] + public void AlmostEqualWithSmallNumbersAndSmallNumberOfDecimalPlaces() + { + Assert.IsTrue(Precision.AlmostEqualInDecimalPlaces(0.0, 1e-12, 1)); + Assert.IsTrue(Precision.AlmostEqualInDecimalPlaces(0.0, -1e-12, 1)); + + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(0.0, 1e-12, 13)); + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(0.0, -1e-12, 13)); + + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(0.0, 1, 1)); + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(0.0, -1, 1)); + } + + [Test] + public void AlmostEqualWithDecimalPlacesWithSignRevert() + { + Assert.IsFalse(Precision.AlmostEqualInDecimalPlaces(0.5, 0.3, 1)); + } + + [Test] + public void IsLargerWithMaxNumbersBetween() + { + // compare zero and negative zero + Assert.IsFalse(Precision.IsLarger(0, -0, 1)); + + // compare two nearby numbers + Assert.IsFalse(Precision.IsLarger(1.0, 1.0 + 3 * mDoublePrecision, 1)); + Assert.IsFalse(Precision.IsLarger(1.0, 1.0 + mDoublePrecision, 1)); + Assert.IsFalse(Precision.IsLarger(1.0, 1.0 - mDoublePrecision, 1)); + Assert.IsTrue(Precision.IsLarger(1.0, 1.0 - 3 * mDoublePrecision, 1)); + + // compare with the two numbers reversed in compare order + Assert.IsTrue(Precision.IsLarger(1.0 + 3 * mDoublePrecision, 1.0, 1)); + Assert.IsFalse(Precision.IsLarger(1.0 + mDoublePrecision, 1.0, 1)); + Assert.IsFalse(Precision.IsLarger(1.0 - mDoublePrecision, 1.0, 1)); + Assert.IsFalse(Precision.IsLarger(1.0 - 3 * mDoublePrecision, 1.0, 1)); + + // compare two slightly more different numbers + Assert.IsFalse(Precision.IsLarger(1.0, 1.0 + 10 * mDoublePrecision, 1)); + Assert.IsFalse(Precision.IsLarger(1.0, 1.0 + 10 * mDoublePrecision, 10)); + Assert.IsFalse(Precision.IsLarger(1.0, 1.0 - 10 * mDoublePrecision, 10)); + Assert.IsTrue(Precision.IsLarger(1.0, 1.0 - 10 * mDoublePrecision, 1)); + + // compare different numbers + Assert.IsTrue(Precision.IsLarger(2.0, 1.0, 1)); + Assert.IsFalse(Precision.IsLarger(1.0, 2.0, 1)); + + // compare different numbers with large tolerance + Assert.IsFalse(Precision.IsLarger(1.0, 1.0 + 1e5 * mDoublePrecision, 1)); + Assert.IsFalse(Precision.IsLarger(1.0, 1.0 - 1e5 * mDoublePrecision, 200000)); + Assert.IsTrue(Precision.IsLarger(1.0, 1.0 - 1e5 * mDoublePrecision, 1)); + + // compare inf & inf + Assert.IsFalse(Precision.IsLarger(double.PositiveInfinity, double.PositiveInfinity, 1)); + Assert.IsFalse(Precision.IsLarger(double.NegativeInfinity, double.NegativeInfinity, 1)); + + // compare -inf and inf + Assert.IsTrue(Precision.IsLarger(double.PositiveInfinity, double.NegativeInfinity, 1)); + Assert.IsFalse(Precision.IsLarger(double.NegativeInfinity, double.PositiveInfinity, 1)); + + // compare inf and non-inf + Assert.IsTrue(Precision.IsLarger(double.PositiveInfinity, 1.0, 1)); + Assert.IsFalse(Precision.IsLarger(1.0, double.PositiveInfinity, 1)); + + Assert.IsFalse(Precision.IsLarger(double.NegativeInfinity, 1.0, 1)); + Assert.IsTrue(Precision.IsLarger(1.0, double.NegativeInfinity, 1)); + + // compare tiny numbers with opposite signs + Assert.IsTrue(Precision.IsLarger(double.Epsilon, -double.Epsilon, 1)); + Assert.IsFalse(Precision.IsLarger(-double.Epsilon, double.Epsilon, 1)); + } + + [Test] + public void IsLargerWithDecimalPlaces() + { + Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(1, double.NaN, 2)); + Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(double.NaN, 2, 2)); + Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(double.NaN, double.NaN, 2)); + + Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(double.NegativeInfinity, 2, 2)); + Assert.IsTrue(Precision.IsLargerWithDecimalPlaces(1, double.NegativeInfinity, 2)); + + Assert.IsTrue(Precision.IsLargerWithDecimalPlaces(double.PositiveInfinity, 2, 2)); + Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(1, double.PositiveInfinity, 2)); + + Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(double.NegativeInfinity, double.PositiveInfinity, 2)); + Assert.IsTrue(Precision.IsLargerWithDecimalPlaces(double.PositiveInfinity, double.NegativeInfinity, 2)); + + Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(double.PositiveInfinity, double.PositiveInfinity, 2)); + Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(double.NegativeInfinity, double.NegativeInfinity, 2)); + + Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(1.0, 1.04, 2)); + Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(1.0, 0.96, 2)); + + Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(1.0, 1.06, 2)); + Assert.IsTrue(Precision.IsLargerWithDecimalPlaces(1.0, 0.94, 2)); + + Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(100.0, 104.00, 2)); + Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(100.0, 96.000, 2)); + + Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(100.0, 106.00, 2)); + Assert.IsTrue(Precision.IsLargerWithDecimalPlaces(100.0, 94.000, 2)); + + double max = 4 * System.Math.Pow(10, Precision.Magnitude(mDoublePrecision)); + Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(0.0, max, -Precision.Magnitude(mDoublePrecision))); + Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(0.0, -max, -Precision.Magnitude(mDoublePrecision))); + + max = 6 * System.Math.Pow(10, Precision.Magnitude(mDoublePrecision)); + Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(0.0, max, -Precision.Magnitude(mDoublePrecision))); + Assert.IsTrue(Precision.IsLargerWithDecimalPlaces(0.0, -max, -Precision.Magnitude(mDoublePrecision))); + } + + [Test] + public void IsSmallerWithMaxNumbersBetween() + { + // compare zero and negative zero + Assert.IsFalse(Precision.IsSmaller(0, -0, 1)); + + // compare two nearby numbers + Assert.IsTrue(Precision.IsSmaller(1.0, 1.0 + 3 * mDoublePrecision, 1)); + Assert.IsFalse(Precision.IsSmaller(1.0, 1.0 + mDoublePrecision, 1)); + Assert.IsFalse(Precision.IsSmaller(1.0, 1.0 - mDoublePrecision, 1)); + Assert.IsFalse(Precision.IsSmaller(1.0, 1.0 - 3 * mDoublePrecision, 1)); + + // compare with the two numbers reversed in compare order + Assert.IsFalse(Precision.IsSmaller(1.0 + 3 * mDoublePrecision, 1.0, 1)); + Assert.IsFalse(Precision.IsSmaller(1.0 + mDoublePrecision, 1.0, 1)); + Assert.IsFalse(Precision.IsSmaller(1.0 - mDoublePrecision, 1.0, 1)); + Assert.IsTrue(Precision.IsSmaller(1.0 - 3 * mDoublePrecision, 1.0, 1)); + + // compare two slightly more different numbers + Assert.IsTrue(Precision.IsSmaller(1.0, 1.0 + 10 * mDoublePrecision, 1)); + Assert.IsFalse(Precision.IsSmaller(1.0, 1.0 + 10 * mDoublePrecision, 10)); + Assert.IsFalse(Precision.IsSmaller(1.0, 1.0 - 10 * mDoublePrecision, 10)); + Assert.IsFalse(Precision.IsSmaller(1.0, 1.0 - 10 * mDoublePrecision, 1)); + + // compare different numbers + Assert.IsFalse(Precision.IsSmaller(2.0, 1.0, 1)); + Assert.IsTrue(Precision.IsSmaller(1.0, 2.0, 1)); + + // compare different numbers with large tolerance + Assert.IsTrue(Precision.IsSmaller(1.0, 1.0 + 1e5 * mDoublePrecision, 1)); + Assert.IsFalse(Precision.IsSmaller(1.0, 1.0 - 1e5 * mDoublePrecision, 200000)); + Assert.IsFalse(Precision.IsSmaller(1.0, 1.0 - 1e5 * mDoublePrecision, 1)); + + // compare inf & inf + Assert.IsFalse(Precision.IsSmaller(double.PositiveInfinity, double.PositiveInfinity, 1)); + Assert.IsFalse(Precision.IsSmaller(double.NegativeInfinity, double.NegativeInfinity, 1)); + + // compare -inf and inf + Assert.IsFalse(Precision.IsSmaller(double.PositiveInfinity, double.NegativeInfinity, 1)); + Assert.IsTrue(Precision.IsSmaller(double.NegativeInfinity, double.PositiveInfinity, 1)); + + // compare inf and non-inf + Assert.IsFalse(Precision.IsSmaller(double.PositiveInfinity, 1.0, 1)); + Assert.IsTrue(Precision.IsSmaller(1.0, double.PositiveInfinity, 1)); + + Assert.IsTrue(Precision.IsSmaller(double.NegativeInfinity, 1.0, 1)); + Assert.IsFalse(Precision.IsSmaller(1.0, double.NegativeInfinity, 1)); + + // compare tiny numbers with opposite signs + Assert.IsFalse(Precision.IsSmaller(double.Epsilon, -double.Epsilon, 1)); + Assert.IsTrue(Precision.IsSmaller(-double.Epsilon, double.Epsilon, 1)); + } + + [Test] + public void IsSmallerWithDecimalPlaces() + { + Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(1, double.NaN, 2)); + Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(double.NaN, 2, 2)); + Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(double.NaN, double.NaN, 2)); + + Assert.IsTrue(Precision.IsSmallerWithDecimalPlaces(double.NegativeInfinity, 2, 2)); + Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(1, double.NegativeInfinity, 2)); + + Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(double.PositiveInfinity, 2, 2)); + Assert.IsTrue(Precision.IsSmallerWithDecimalPlaces(1, double.PositiveInfinity, 2)); + + Assert.IsTrue(Precision.IsSmallerWithDecimalPlaces(double.NegativeInfinity, double.PositiveInfinity, 2)); + Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(double.PositiveInfinity, double.NegativeInfinity, 2)); + + Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(double.PositiveInfinity, double.PositiveInfinity, 2)); + Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(double.NegativeInfinity, double.NegativeInfinity, 2)); + + Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(1.0, 1.04, 2)); + Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(1.0, 0.96, 2)); + + Assert.IsTrue(Precision.IsSmallerWithDecimalPlaces(1.0, 1.06, 2)); + Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(1.0, 0.94, 2)); + + Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(100.0, 104.00, 2)); + Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(100.0, 96.000, 2)); + + Assert.IsTrue(Precision.IsSmallerWithDecimalPlaces(100.0, 106.00, 2)); + Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(100.0, 94.000, 2)); + + double max = 4 * System.Math.Pow(10, Precision.Magnitude(mDoublePrecision)); + Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(0.0, max, -Precision.Magnitude(mDoublePrecision))); + Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(0.0, -max, -Precision.Magnitude(mDoublePrecision))); + + max = 6 * System.Math.Pow(10, Precision.Magnitude(mDoublePrecision)); + Assert.IsTrue(Precision.IsSmallerWithDecimalPlaces(0.0, max, -Precision.Magnitude(mDoublePrecision))); + Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(0.0, -max, -Precision.Magnitude(mDoublePrecision))); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void CompareToWithMaxNumbersBetweenWithNegativeNumber() + { + double value = 10.0; + Precision.CompareTo(value, value, -1); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void CompareToWithMaxNumbersBetweenWithZeroNumber() + { + double value = 10.0; + Precision.CompareTo(value, value, 0); + } + + [Test] + public void CompareToWithMaxNumbersBetweenWithInfinityValue() + { + Assert.AreEqual(0, Precision.CompareTo(double.PositiveInfinity, double.PositiveInfinity, 1)); + Assert.AreEqual(0, Precision.CompareTo(double.NegativeInfinity, double.NegativeInfinity, 1)); + + Assert.AreEqual(1, Precision.CompareTo(double.PositiveInfinity, double.NegativeInfinity, 1)); + Assert.AreEqual(-1, Precision.CompareTo(double.NegativeInfinity, double.PositiveInfinity, 1)); + + Assert.AreEqual(-1, Precision.CompareTo(1, double.PositiveInfinity, 1)); + Assert.AreEqual(1, Precision.CompareTo(double.PositiveInfinity, 1, 1)); + + Assert.AreEqual(1, Precision.CompareTo(1, double.NegativeInfinity, 1)); + Assert.AreEqual(-1, Precision.CompareTo(double.NegativeInfinity, 1, 1)); + } + + [Test] + public void CompareToWithMaxNumbersBetweenWithNaNValue() + { + // compare nan & not-nan + Assert.AreEqual(1, Precision.CompareTo(1, double.NaN, 1)); + Assert.AreEqual(0, Precision.CompareTo(double.NaN, double.NaN, 1)); + Assert.AreEqual(-1, Precision.CompareTo(double.NaN, 1, 1)); + } + + // Bug fix test: In the old system comparing -2 and 2 creates a long value that is equal + // to long.MinValue and you can't get the absolute value of that because long.MinValue is + // 1 bigger than long.MaxValue + + [Test] + public void CompareToWithMaxNumbersBetweenForValuesThatOverFlowALong() + { + Assert.AreEqual(1, Precision.CompareTo(2.0, -2.0, 1)); + Assert.AreEqual(-1, Precision.CompareTo(-2.0, 2.0, 1)); + } + + [Test] + public void CompareToWithMaxNumbersBetween() + { + // compare zero and negative zero + Assert.AreEqual(0, Precision.CompareTo(0, -0, 1)); + + // compare two nearby numbers + Assert.AreEqual(-1, Precision.CompareTo(1.0, 1.0 + 3 * mDoublePrecision, 1)); + Assert.AreEqual(0, Precision.CompareTo(1.0, 1.0 + mDoublePrecision, 1)); + Assert.AreEqual(0, Precision.CompareTo(1.0, 1.0 - mDoublePrecision, 1)); + Assert.AreEqual(1, Precision.CompareTo(1.0, 1.0 - 3 * mDoublePrecision, 1)); + + // compare with the two numbers reversed in compare order + Assert.AreEqual(1, Precision.CompareTo(1.0 + 3 * mDoublePrecision, 1.0, 1)); + Assert.AreEqual(0, Precision.CompareTo(1.0 + mDoublePrecision, 1.0, 1)); + Assert.AreEqual(0, Precision.CompareTo(1.0 - mDoublePrecision, 1.0, 1)); + Assert.AreEqual(-1, Precision.CompareTo(1.0 - 3 * mDoublePrecision, 1.0, 1)); + + // compare two slightly more different numbers + Assert.AreEqual(-1, Precision.CompareTo(1.0, 1.0 + 10 * mDoublePrecision, 1)); + Assert.AreEqual(0, Precision.CompareTo(1.0, 1.0 + 10 * mDoublePrecision, 10)); + Assert.AreEqual(0, Precision.CompareTo(1.0, 1.0 - 10 * mDoublePrecision, 10)); + Assert.AreEqual(1, Precision.CompareTo(1.0, 1.0 - 10 * mDoublePrecision, 1)); + + // compare different numbers + Assert.AreEqual(1, Precision.CompareTo(2.0, 1.0, 1)); + Assert.AreEqual(-1, Precision.CompareTo(1.0, 2.0, 1)); + + // compare different numbers with large tolerance + Assert.AreEqual(-1, Precision.CompareTo(1.0, 1.0 + 1e5 * mDoublePrecision, 1)); + Assert.AreEqual(0, Precision.CompareTo(1.0, 1.0 - 1e5 * mDoublePrecision, 200000)); + Assert.AreEqual(1, Precision.CompareTo(1.0, 1.0 - 1e5 * mDoublePrecision, 1)); + + // compare inf & inf + Assert.AreEqual(0, Precision.CompareTo(double.PositiveInfinity, double.PositiveInfinity, 1)); + Assert.AreEqual(0, Precision.CompareTo(double.NegativeInfinity, double.NegativeInfinity, 1)); + + // compare -inf and inf + Assert.AreEqual(1, Precision.CompareTo(double.PositiveInfinity, double.NegativeInfinity, 1)); + Assert.AreEqual(-1, Precision.CompareTo(double.NegativeInfinity, double.PositiveInfinity, 1)); + + // compare inf and non-inf + Assert.AreEqual(1, Precision.CompareTo(double.PositiveInfinity, 1.0, 1)); + Assert.AreEqual(-1, Precision.CompareTo(1.0, double.PositiveInfinity, 1)); + + Assert.AreEqual(-1, Precision.CompareTo(double.NegativeInfinity, 1.0, 1)); + Assert.AreEqual(1, Precision.CompareTo(1.0, double.NegativeInfinity, 1)); + + // compare tiny numbers with opposite signs + Assert.AreEqual(1, Precision.CompareTo(double.Epsilon, -double.Epsilon, 1)); + Assert.AreEqual(-1, Precision.CompareTo(-double.Epsilon, double.Epsilon, 1)); + } + + [Test] + public void CompareToWithDecimalPlaces() + { + // compare zero and negative zero + Assert.AreEqual(0, Precision.CompareToInDecimalPlaces(0, -0, 1)); + Assert.AreEqual(0, Precision.CompareToInDecimalPlaces(0, -0, Precision.NumberOfDecimalPlacesForDoubles)); + Assert.AreEqual(0, Precision.CompareToInDecimalPlaces(0, -0, Precision.NumberOfDecimalPlacesForFloats)); + + // compare two nearby numbers + Assert.AreEqual(-1, Precision.CompareToInDecimalPlaces(1.0, 1.0 + 10 * mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles)); + Assert.AreEqual(0, Precision.CompareToInDecimalPlaces(1.0, 1.0 + mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles)); + Assert.AreEqual(0, Precision.CompareToInDecimalPlaces(1.0, 1.0 - mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles)); + Assert.AreEqual(1, Precision.CompareToInDecimalPlaces(1.0, 1.0 - 10 * mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles)); + + // compare with the two numbers reversed in compare order + Assert.AreEqual(1, Precision.CompareToInDecimalPlaces(1.0 + 10 * mDoublePrecision, 1.0, Precision.NumberOfDecimalPlacesForDoubles)); + Assert.AreEqual(0, Precision.CompareToInDecimalPlaces(1.0 + mDoublePrecision, 1.0, Precision.NumberOfDecimalPlacesForDoubles)); + Assert.AreEqual(0, Precision.CompareToInDecimalPlaces(1.0 - mDoublePrecision, 1.0, Precision.NumberOfDecimalPlacesForDoubles)); + Assert.AreEqual(-1, Precision.CompareToInDecimalPlaces(1.0 - 10 * mDoublePrecision, 1.0, Precision.NumberOfDecimalPlacesForDoubles)); + + // compare two slightly more different numbers + Assert.AreEqual(-1, Precision.CompareToInDecimalPlaces(1.0, 1.0 + 50 * mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles)); + Assert.AreEqual(0, Precision.CompareToInDecimalPlaces(1.0, 1.0 + 50 * mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles - 2)); + Assert.AreEqual(0, Precision.CompareToInDecimalPlaces(1.0, 1.0 - 50 * mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles - 2)); + Assert.AreEqual(1, Precision.CompareToInDecimalPlaces(1.0, 1.0 - 50 * mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles)); + + // compare different numbers + Assert.AreEqual(1, Precision.CompareToInDecimalPlaces(2.0, 1.0, Precision.NumberOfDecimalPlacesForDoubles)); + Assert.AreEqual(-1, Precision.CompareToInDecimalPlaces(1.0, 2.0, Precision.NumberOfDecimalPlacesForDoubles)); + + // compare different numbers with large tolerance + Assert.AreEqual(-1, Precision.CompareToInDecimalPlaces(1.0, 1.0 + 1e5 * mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles)); + Assert.AreEqual(0, Precision.CompareToInDecimalPlaces(1.0, 1.0 - 1e5 * mDoublePrecision, 10)); + Assert.AreEqual(1, Precision.CompareToInDecimalPlaces(1.0, 1.0 - 1e5 * mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles)); + + // compare inf & inf + Assert.AreEqual(0, Precision.CompareToInDecimalPlaces(double.PositiveInfinity, double.PositiveInfinity, Precision.NumberOfDecimalPlacesForDoubles)); + Assert.AreEqual(0, Precision.CompareToInDecimalPlaces(double.NegativeInfinity, double.NegativeInfinity, Precision.NumberOfDecimalPlacesForDoubles)); + + // compare -inf and inf + Assert.AreEqual(1, Precision.CompareToInDecimalPlaces(double.PositiveInfinity, double.NegativeInfinity, Precision.NumberOfDecimalPlacesForDoubles)); + Assert.AreEqual(-1, Precision.CompareToInDecimalPlaces(double.NegativeInfinity, double.PositiveInfinity, Precision.NumberOfDecimalPlacesForDoubles)); + + // compare inf and non-inf + Assert.AreEqual(1, Precision.CompareToInDecimalPlaces(double.PositiveInfinity, 1.0, Precision.NumberOfDecimalPlacesForDoubles)); + Assert.AreEqual(-1, Precision.CompareToInDecimalPlaces(1.0, double.PositiveInfinity, Precision.NumberOfDecimalPlacesForDoubles)); + + Assert.AreEqual(-1, Precision.CompareToInDecimalPlaces(double.NegativeInfinity, 1.0, Precision.NumberOfDecimalPlacesForDoubles)); + Assert.AreEqual(1, Precision.CompareToInDecimalPlaces(1.0, double.NegativeInfinity, Precision.NumberOfDecimalPlacesForDoubles)); + } + } +} diff --git a/src/Managed/Managed.csproj b/src/Managed/Managed.csproj index b94d2721..cc1613c0 100644 --- a/src/Managed/Managed.csproj +++ b/src/Managed/Managed.csproj @@ -48,6 +48,7 @@ + True diff --git a/src/Managed/Precision.cs b/src/Managed/Precision.cs new file mode 100644 index 00000000..7139b15b --- /dev/null +++ b/src/Managed/Precision.cs @@ -0,0 +1,1047 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://mathnet.opensourcedotnet.info +// +// Copyright (c) 2009 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics +{ + /// + /// Utilities for working with floating point numbers. + /// + /// + /// + /// Useful links: + /// + /// + /// http://docs.sun.com/source/806-3568/ncg_goldberg.html#689 - What every computer scientist should know about floating-point arithmetic + /// + /// + /// http://en.wikipedia.org/wiki/Machine_epsilon - Gives the definition of machine epsilon + /// + /// + /// + /// + public static class Precision + { + #region Constants + + /// + /// The base number for binary values + /// + private const int BinaryBaseNumber = 2; + + /// + /// The number of binary digits used to represent the binary number for a double precision floating + /// point value. i.e. there are this many digits used to represent the + /// actual number, where in a number as: 0.134556 * 10^5 the digits are 0.134556 and the exponent is 5. + /// + private const int DoublePrecision = 53; + + /// + /// The number of binary digits used to represent the binary number for a single precision floating + /// point value. i.e. there are this many digits used to represent the + /// actual number, where in a number as: 0.134556 * 10^5 the digits are 0.134556 and the exponent is 5. + /// + private const int SinglePrecision = 24; + + #endregion + + #region Fields + + /// + /// The maximum relative precision of a double + /// + private static readonly double _doubleMachinePrecision = System.Math.Pow(BinaryBaseNumber, -DoublePrecision); + + /// + /// The maximum relative precision of a single + /// + private static readonly double _singleMachinePrecision = System.Math.Pow(BinaryBaseNumber, -SinglePrecision); + + /// + /// The number of significant figures that a double-precision floating point has. + /// + private static readonly int _numberOfDecimalPlacesForDoubles; + + /// + /// The number of significant figures that a single-precision floating point has. + /// + private static readonly int _numberOfDecimalPlacesForFloats; + + #endregion + + /// + /// Initializes the class. + /// + static Precision() + { + _numberOfDecimalPlacesForFloats = (int)System.Math.Ceiling(System.Math.Abs(System.Math.Log10(_singleMachinePrecision))); + _numberOfDecimalPlacesForDoubles = (int)System.Math.Ceiling(System.Math.Abs(System.Math.Log10(_doubleMachinePrecision))); + } + + /// + /// Gets the number of decimal places for floats. + /// + /// The number of decimal places for floats. + public static int NumberOfDecimalPlacesForFloats + { + get + { + return _numberOfDecimalPlacesForFloats; + } + } + + /// + /// Gets the number of decimal places for doubles. + /// + /// The number of decimal places for doubles. + public static int NumberOfDecimalPlacesForDoubles + { + get + { + return _numberOfDecimalPlacesForDoubles; + } + } + + /// + /// Returns the magnitude of the number. + /// + /// The value. + /// The magnitute of the number. + public static int Magnitude(double value) + { + // Can't do this with zero because the 10-log of zero doesn't exist. + if (value.Equals(0.0)) + { + return 0; + } + + // Note that we need the absolute value of the input because Log10 doesn't + // work for negative numbers (obviously). + double magnitude = System.Math.Log10(System.Math.Abs(value)); + + // To get the right number we need to know if the value is negative or positive + // truncating a positive number will always give use the correct magnitude + // truncating a negative number will give us a magnitude that is off by 1 + if (magnitude < 0) + { + return ((int)(System.Math.Truncate(magnitude - 1))); + } + return ((int)(System.Math.Truncate(magnitude))); + } + + /// + /// Returns the number divided by it's magnitude, effectively returning a number between -10 and 10. + /// + /// The value. + /// The value of the number. + public static double Value(double value) + { + if (value.Equals(0.0)) + { + return value; + } + + int magnitude = Magnitude(value); + return value * (System.Math.Pow(10, -magnitude)); + } + + private static long GetLongFromDouble(double value) + { + return BitConverter.DoubleToInt64Bits(value); + } + + /// + /// Returns a 'directional' long value. This is a long value which acts the same as a double, + /// e.g. a negative double value will return a negative double value starting at 0 and going + /// more negative as the double value gets more negative. + /// + /// The input double value. + /// A long value which is roughly the equivalent of the double value. + private static long GetDirectionalLongFromDouble(double value) + { + // Convert in the normal way. + long result = GetLongFromDouble(value); + // Now find out where we're at in the range + // If the value is larger/equal to zero then we can just return the value + // if the value is negative we subtract long.MinValue from it. + return (result >= 0) ? result : (long.MinValue - result); + } + + /// + /// Increments a floating point number to the next bigger number representable by the data type. + /// + /// + /// The incrementation step length depends on the provided value. + /// Increment(double.MaxValue) will return positive infinity. + /// + public static double Increment(double value) + { + if (double.IsInfinity(value) || double.IsNaN(value)) + { + return value; + } + + // Translate the bit pattern of the double to an integer. + // Note that this leads to: + // double > 0 --> long > 0, growing as the double value grows + // double < 0 --> long < 0, increasing in absolute magnitude as the double + // gets closer to zero! + // i.e. 0 - double.epsilon will give the largest long value! + long intValue = GetLongFromDouble(value); + if (intValue < 0) + { + intValue--; + } + else + { + intValue++; + } + + // Note that long.MinValue has the same bit pattern as -0.0. + if (intValue == long.MinValue) + { + return 0; + } + + // Note that not all long values can be translated into double values. There's a whole bunch of them + // which return weird values like infinity and NaN + return BitConverter.Int64BitsToDouble(intValue); + } + + /// + /// Decrements a floating point number to the next smaller number representable by the data type. + /// + /// + /// The decrementation step length depends on the provided value. + /// Decrement(double.MinValue) will return negative infinity. + /// + public static double Decrement(double value) + { + if (double.IsInfinity(value) || double.IsNaN(value)) + { + return value; + } + + // Translate the bit pattern of the double to an integer. + // Note that this leads to: + // double > 0 --> long > 0, growing as the double value grows + // double < 0 --> long < 0, increasing in absolute magnitude as the double + // gets closer to zero! + // i.e. 0 - double.epsilon will give the largest long value! + long intValue = GetLongFromDouble(value); + // If the value is zero then we'd really like the value to be -0. So we'll make it -0 + // and then everything else should work out. + if (intValue == 0) + { + // Note that long.MinValue has the same bit pattern as -0.0. + intValue = long.MinValue; + } + + if (intValue < 0) + { + intValue++; + } + else + { + intValue--; + } + + // Note that not all long values can be translated into double values. There's a whole bunch of them + // which return weird values like infinity and NaN + return BitConverter.Int64BitsToDouble(intValue); + } + + /// + /// Forces small numbers near zero to zero, according to the specified absolute accuracy. + /// + /// The real number to coerce to zero, if it is almost zero. + /// The maximum count of numbers between the zero and the number . + /// + /// Zero if || is fewer than numbers from zero, otherwise. + /// + public static double CoerceZero(double a, int maxNumbersBetween) + { + return CoerceZero(a, (long)maxNumbersBetween); + } + + /// + /// Forces small numbers near zero to zero, according to the specified absolute accuracy. + /// + /// The real number to coerce to zero, if it is almost zero. + /// The maximum count of numbers between the zero and the number . + /// + /// Zero if || is fewer than numbers from zero, otherwise. + /// + /// + /// Thrown if is smaller than zero. + /// + public static double CoerceZero(double a, long maxNumbersBetween) + { + if (maxNumbersBetween < 0) + { + throw new ArgumentOutOfRangeException("maxNumbersBetween"); + } + + if (double.IsInfinity(a) || double.IsNaN(a)) + { + return a; + } + + // We allow maxNumbersBetween between 0 and the number so + // we need to check if there a + if (NumbersBetween(0.0, a) <= (ulong)maxNumbersBetween) + { + return 0.0; + } + + return a; + } + + /// + /// Forces small numbers near zero to zero, according to the specified absolute accuracy. + /// + /// The real number to coerce to zero, if it is almost zero. + /// The absolute threshold for to consider it as zero. + /// Zero if || is smaller than , otherwise. + /// + /// Thrown if is smaller than zero. + /// + public static double CoerceZero(double a, double maximumAbsoluteError) + { + if (maximumAbsoluteError < 0) + { + throw new ArgumentOutOfRangeException("maximumAbsoluteError"); + } + + if (double.IsInfinity(a) || double.IsNaN(a)) + { + return a; + } + + if (Math.Abs(a) < maximumAbsoluteError) + { + return 0.0; + } + + return a; + } + + /// + /// Forces small numbers near zero to zero. + /// + /// The real number to coerce to zero, if it is almost zero. + /// Zero if || is smaller than 10*2^(-52) = 0.22e-14, otherwise. + public static double CoerceZero(double a) + { + return CoerceZero(a, _doubleMachinePrecision); + } + + /// + /// Determines the range of floating point numbers that will match the specified value with the given tolerance. + /// + /// The value. + /// The ulps difference. + /// The top range end. + /// The bottom range end. + /// + /// Thrown if is smaller than zero. + /// + public static void RangeOfMatchingFloatingPointNumbers(double value, long maxNumbersBetween, out double bottomRangeEnd, out double topRangeEnd) + { + // Make sure ulpDifference is non-negative + if (maxNumbersBetween < 1) + { + throw new ArgumentOutOfRangeException("ulpsDifference"); + } + + // If the value is infinity (positive or negative) just + // return the same infinity for the range. + if ((double.IsInfinity(value))) + { + topRangeEnd = value; + bottomRangeEnd = value; + return; + } + + // If the value is a NaN then the range is a NaN too. + if (double.IsNaN(value)) + { + topRangeEnd = double.NaN; + bottomRangeEnd = double.NaN; + return; + } + + // Translate the bit pattern of the double to an integer. + // Note that this leads to: + // double > 0 --> long > 0, growing as the double value grows + // double < 0 --> long < 0, increasing in absolute magnitude as the double + // gets closer to zero! + // i.e. 0 - double.epsilon will give the largest long value! + long intValue = GetLongFromDouble(value); + + // We need to protect against over- and under-flow of the intValue when + // we start to add the ulpsDifference. + if (intValue < 0) + { + // Note that long.MinValue has the same bit pattern as + // -0.0. Therefore we're working in opposite direction (i.e. add if we want to + // go more negative and subtract if we want to go less negative) + if (System.Math.Abs(long.MinValue - intValue) < maxNumbersBetween) + { + // Got underflow, which can be fixed by splitting the calculation into two bits + // first get the remainder of the intValue after subtracting it from the long.MinValue + // and add that to the ulpsDifference. That way we'll turn positive without underflow + topRangeEnd = BitConverter.Int64BitsToDouble(maxNumbersBetween + (long.MinValue - intValue)); + } + else + { + // No problems here, move along. + topRangeEnd = BitConverter.Int64BitsToDouble(intValue - maxNumbersBetween); + } + + if (System.Math.Abs(intValue) < maxNumbersBetween) + { + // Underflow, which means we'd have to go further than a long would allow us. + // Also we couldn't translate it back to a double, so we'll return -Double.MaxValue + bottomRangeEnd = -double.MaxValue; + } + else + { + // intValue is negative. Adding the positive ulpsDifference means that it gets less negative. + // However due to the conversion way this means that the actual double value gets more negative :-S + bottomRangeEnd = BitConverter.Int64BitsToDouble(intValue + maxNumbersBetween); + } + } + else + { + // IntValue is positive + if (long.MaxValue - intValue < maxNumbersBetween) + { + // Overflow, which means we'd have to go further than a long would allow us. + // Also we couldn't translate it back to a double, so we'll return Double.MaxValue + topRangeEnd = double.MaxValue; + } + else + { + // No troubles here + topRangeEnd = BitConverter.Int64BitsToDouble(intValue + maxNumbersBetween); + } + + // Check the bottom range end for underflows + if (intValue > maxNumbersBetween) + { + // No problems here. IntValue is larger than ulpsDifference so we'll end up with a + // positive number. + bottomRangeEnd = BitConverter.Int64BitsToDouble(intValue - maxNumbersBetween); + } + else + { + // Int value is bigger than zero but smaller than the ulpsDifference. So we'll need to deal with + // the reversal at the negative end + bottomRangeEnd = BitConverter.Int64BitsToDouble(long.MinValue + (maxNumbersBetween - intValue)); + } + } + } + + /// + /// Returns the floating point number that will match the value with the tolerance on the maximum size (i.e. the result is + /// always bigger than the value) + /// + /// The value. + /// The ulps difference. + /// + public static double MaximumMatchingFloatingPointNumber(double value, long maxNumbersBetween) + { + double topRangeEnd, bottomRangeEnd; + RangeOfMatchingFloatingPointNumbers(value, maxNumbersBetween, out bottomRangeEnd, out topRangeEnd); + return topRangeEnd; + } + + /// + /// Returns the floating point number that will match the value with the tolerance on the minimum size (i.e. the result is + /// always smaller than the value) + /// + /// The value. + /// The ulps difference. + /// + public static double MinimumMatchingFloatingPointNumber(double value, long maxNumbersBetween) + { + double topRangeEnd, bottomRangeEnd; + RangeOfMatchingFloatingPointNumbers(value, maxNumbersBetween, out bottomRangeEnd, out topRangeEnd); + return bottomRangeEnd; + } + + /// + /// Determines the range of ulps that will match the specified value with the given tolerance. + /// + /// The value. + /// The relative difference. + /// The number of ULPS between the value and the value + relativeDifference. + /// The number of ULPS between the value and the value - relativeDifference. + /// + /// Thrown if is smaller than zero. + /// + /// + /// Thrown if is double.PositiveInfinity or double.NegativeInfinity. + /// + /// + /// Thrown if is double.NaN. + /// + public static void RangeOfMatchingNumbers(double value, double relativeDifference, out long bottomRangeEnd, out long topRangeEnd) + { + // Make sure the relative is non-negative + if (relativeDifference < 0) + { + throw new ArgumentOutOfRangeException("relativeDifference"); + } + + // If the value is infinity (positive or negative) then + // we can't determine the range. + if ((double.IsInfinity(value))) + { + throw new ArgumentOutOfRangeException("value"); + } + + // If the value is a NaN then we can't determine the range. + if (double.IsNaN(value)) + { + throw new ArgumentOutOfRangeException("value"); + } + + // If the value is zero (0.0) then we can't calculate the relative difference + // so return the ulps counts for the difference. + if (value.Equals(0)) + { + topRangeEnd = GetLongFromDouble(relativeDifference); + bottomRangeEnd = topRangeEnd; + return; + } + + // Calculate the ulps for the maximum and minimum values + // Note that these can overflow + long max = GetDirectionalLongFromDouble(value + relativeDifference * System.Math.Abs(value)); + long min = GetDirectionalLongFromDouble(value - relativeDifference * System.Math.Abs(value)); + + // Calculate the ulps from the value + long intValue = GetDirectionalLongFromDouble(value); + + // Determine the ranges + topRangeEnd = System.Math.Abs(max - intValue); + bottomRangeEnd = System.Math.Abs(intValue - min); + } + + + /// + /// Evaluates the count of numbers between two double numbers + /// + /// + /// + /// The second number is included in the number, thus two equal numbers evaluate to zero and two neighbor numbers evaluate to one. Therefore, what is returned is actually the count of numbers between plus 1. + /// + /// Thrown if is double.PositiveInfinity or double.NegativeInfinity. + /// + /// + /// Thrown if is double.NaN. + /// + /// + /// Thrown if is double.PositiveInfinity or double.NegativeInfinity. + /// + /// + /// Thrown if is double.NaN. + /// + public static ulong NumbersBetween(double a, double b) + { + if (double.IsNaN(a) || double.IsInfinity(a)) + { + throw new ArgumentOutOfRangeException("a"); + } + + if (double.IsNaN(b) || double.IsInfinity(b)) + { + throw new ArgumentOutOfRangeException("b"); + } + + // Calculate the ulps for the maximum and minimum values + // Note that these can overflow + long intA = GetDirectionalLongFromDouble(a); + long intB = GetDirectionalLongFromDouble(b); + + // Now find the number of values between the two doubles. This should not overflow + // given that there are more long values than there are double values + return (a >= b) ? (ulong)(intA - intB) : (ulong)(intB - intA); + } + + /// + /// True if the given number is almost equal to zero, according to the specified absolute accuracy. + /// + /// The real number to check for being almost zero. + /// The maximum number of floating point values between the two values. Must be 1 or larger. + /// + /// True if || is less than steps from zero, False otherwise. + /// + public static bool AlmostZero(double a, long maxNumbersBetween) + { + if (maxNumbersBetween < 0) + { + throw new ArgumentOutOfRangeException("maxNumbersBetween"); + } + + if (double.IsNaN(a) || double.IsInfinity(a)) + { + return false; + } + + ulong realNumbersBetween = NumbersBetween(0.0, a); + return realNumbersBetween <= (ulong)maxNumbersBetween; + } + + /// + /// True if the given number is almost equal to zero, according to the specified absolute accuracy. + /// + /// The real number to check for being almost zero. + /// The absolute threshold for to consider it as zero. + /// + /// True if || is smaller than , False otherwise. + /// + public static bool AlmostZero(double a, double maximumAbsoluteError) + { + if (maximumAbsoluteError < 0) + { + throw new ArgumentOutOfRangeException("maximumAbsoluteError"); + } + + if (double.IsNaN(a) || double.IsInfinity(a)) + { + return false; + } + + return Math.Abs(a) < maximumAbsoluteError; + } + + /// + /// True if the given number is almost equal to zero. + /// + /// The real number to check for being almost zero. + /// + /// True if || is smaller than 10*2^(-52) = 0.22e-14, False otherwise. + /// + public static bool AlmostZero(double a) + { + return AlmostZero(a, _doubleMachinePrecision); + } + + /// + /// Compares two doubles and determines if they are equal to within the tolerance or not. Equality comparison is based on the binary representation. + /// + /// + /// + /// Determines the 'number' of floating point numbers between two values (i.e. the number of discrete steps + /// between the two numbers) and then checks if that is within the specified tolerance. So if a tolerance + /// of 1 is passed then the result will be true only if the two numbers have the same binary representation + /// OR if they are two adjacent numbers that only differ by one step. + /// + /// + /// The comparison method used is explained in http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm . The article + /// at http://www.extremeoptimization.com/resources/Articles/FPDotNetConceptsAndFormats.aspx explains how to transform the C code to + /// .NET enabled code without using pointers and unsafe code. + /// + /// + /// The first value. + /// The second value. + /// The maximum number of floating point values between the two values. Must be 1 or larger. + /// if both doubles are equal to each other within the specified tolerance; otherwise . + /// + /// Thrown if is smaller than one. + /// + public static bool AlmostEqual(double a, double b, long maxNumbersBetween) + { + // Make sure maxUlps is non-negative and small enough that the + // default NAN won't compare as equal to anything. + if (maxNumbersBetween < 1) + { + throw new ArgumentOutOfRangeException("maxUlps"); + } + + // If A or B 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(a)) || (double.IsInfinity(b))) + { + return (a == b); + } + + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves. + if (double.IsNaN(a) || double.IsNaN(b)) + { + return false; + } + + // Get the first double and convert it to an integer value (by using the binary representation) + long firstUlong = GetDirectionalLongFromDouble(a); + + // Get the second double and convert it to an integer value (by using the binary representation) + long secondUlong = GetDirectionalLongFromDouble(b); + + // Now compare the values. + // Note that this comparison can overflow so we'll approach this differently + // Do note that we could overflow this way too. We should probably check that we don't. + return (a > b) ? (secondUlong + maxNumbersBetween >= firstUlong) : (firstUlong + maxNumbersBetween >= secondUlong); + } + + /// + /// Compares two doubles and determines if they are equal to within the specified number of decimal places or not. If the numbers + /// are very close to zero an absolute difference is compared, otherwise the relative difference is compared. + /// + /// + /// + /// The values are equal if the difference between the two numbers is smaller than 10^(-numberOfDecimalPlaces). We divide by + /// two so that we have half the range on each side of the numbers, e.g. if decimalPlaces == 2, then 0.01 will equal between + /// 0.005 and 0.015, but not 0.02 and not 0.00 + /// + /// + /// The first value. + /// The second value. + /// The number of decimal places. + /// if both doubles are equal to each other within the specified number of decimal places; otherwise . + /// + /// Thrown if is smaller than zero. + /// + public static bool AlmostEqualInDecimalPlaces(double a, double b, int decimalPlaces) + { + if (decimalPlaces <= 0) + { + // Can't have a negative number of decimal places + throw new ArgumentOutOfRangeException("numberOfSignificantFigures"); + } + + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves. + if ((double.IsNaN(a)) || (double.IsNaN(b))) + { + return false; + } + + // If A or B 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(a)) || (double.IsInfinity(b))) + { + return (a == b); + } + + // If both numbers are equal, get out now. This should remove the possibility of both numbers being zero + // and any problems associated with that. + if (a.Equals(b)) + { + return true; + } + + if ((AlmostZero(a)) || (AlmostZero(b))) + { + return AlmostEqualWithAbsoluteDecimalPlaces(a, b, decimalPlaces); + } + return AlmostEqualWithRelativeDecimalPlaces(a, b, decimalPlaces); + } + + /// + /// Compares two doubles and determines if they are equal to within the specified number of decimal places or not. + /// + /// + /// + /// The values are equal if the difference between the two numbers is smaller than 10^(-numberOfDecimalPlaces). We divide by + /// two so that we have half the range on each side of the numbers, e.g. if decimalPlaces == 2, then 0.01 will equal between + /// 0.005 and 0.015, but not 0.02 and not 0.00 + /// + /// + /// The first value. + /// The second value. + /// The number of decimal places. + /// if both doubles are equal to each other within the specified number of decimal places; otherwise . + private static bool AlmostEqualWithRelativeDecimalPlaces(double a, double b, int decimalPlaces) + { + // If the magnitudes of the two numbers are equal to within one magnitude the numbers could potentially be equal + int magnitudeOfFirst = Magnitude(a); + int magnitudeOfSecond = Magnitude(b); + if (System.Math.Max(magnitudeOfFirst, magnitudeOfSecond) > (System.Math.Min(magnitudeOfFirst, magnitudeOfSecond) + 1)) + { + return false; + } + + // Get the power of the number of decimalPlaces + double decimalPlaceMagnitude = System.Math.Pow(10, -(decimalPlaces - 1)); + + // The values are equal if the difference between the two numbers is smaller than + // 10^(-numberOfDecimalPlaces). We divide by two so that we have half the range + // on each side of the numbers, e.g. if decimalPlaces == 2, + // then 0.01 will equal between 0.005 and 0.015, but not 0.02 and not 0.00 + double maxDifference = decimalPlaceMagnitude / 2.0; + if (a > b) + { + return (a * System.Math.Pow(10, -magnitudeOfFirst) - maxDifference < b * System.Math.Pow(10, -magnitudeOfFirst)); + } + else + { + return (b * System.Math.Pow(10, -magnitudeOfSecond) - maxDifference < a * System.Math.Pow(10, -magnitudeOfSecond)); + } + } + + /// + /// Compares two doubles and determines if they are equal to within the specified number of decimal places or not, using the + /// number of decimal places as an absolute measure. + /// + /// + /// + /// The values are equal if the difference between the two numbers is smaller than 10^(-numberOfDecimalPlaces). We divide by + /// two so that we have half the range on each side of the numbers, e.g. if decimalPlaces == 2, then 0.01 will equal between + /// 0.005 and 0.015, but not 0.02 and not 0.00 + /// + /// + /// The first value. + /// The second value. + /// The number of decimal places. + /// if both doubles are equal to each other within the specified number of decimal places; otherwise . + private static bool AlmostEqualWithAbsoluteDecimalPlaces(double a, double b, int decimalPlaces) + { + double decimalPlaceMagnitude = System.Math.Pow(10, -(decimalPlaces - 1)); + // The values are equal if the difference between the two numbers is smaller than + // 10^(-numberOfDecimalPlaces). We divide by two so that we have half the range + // on each side of the numbers, e.g. if decimalPlaces == 2, + // then 0.01 will equal between 0.005 and 0.015, but not 0.02 and not 0.00 + return (System.Math.Abs((a - b)) < decimalPlaceMagnitude / 2.0); + } + + /// + /// Compares two doubles and determines if the first value is larger than the second + /// value to within the tolerance or not. Equality comparison is based on the binary representation. + /// + /// The first value. + /// The second value. + /// The maximum number of floating point values for which the two values are considered equal. Must be 1 or larger. + /// true if the first value is larger than the second value; otherwise false. + public static bool IsLarger(double a, double b, long maxNumbersBetween) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if ((double.IsNaN(a)) || (double.IsNaN(b))) + { + return false; + } + + return (CompareTo(a, b, maxNumbersBetween) > 0); + } + + /// + /// Compares two doubles and determines if the first value is larger than the second + /// value to within the specified number of decimal places or not. + /// + /// + /// + /// The values are equal if the difference between the two numbers is smaller than 10^(-numberOfDecimalPlaces). We divide by + /// two so that we have half the range on each side of the numbers, e.g. if decimalPlaces == 2, then 0.01 will equal between + /// 0.005 and 0.015, but not 0.02 and not 0.00 + /// + /// + /// The first value. + /// The second value. + /// The number of decimal places. + /// true if the first value is larger than the second value; otherwise false. + public static bool IsLargerWithDecimalPlaces(double a, double b, int decimalPlaces) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if ((double.IsNaN(a)) || (double.IsNaN(b))) + { + return false; + } + + return (CompareToInDecimalPlaces(a, b, decimalPlaces) > 0); + } + + /// + /// Compares two doubles and determines if the first value is smaller than the second + /// value to within the tolerance or not. Equality comparison is based on the binary representation. + /// + /// The first value. + /// The second value. + /// The maximum number of floating point values for which the two values are considered equal. Must be 1 or larger. + /// true if the first value is smaller than the second value; otherwise false. + public static bool IsSmaller(double a, double b, long maxNumbersBetween) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if ((double.IsNaN(a)) || (double.IsNaN(b))) + { + return false; + } + + return (CompareTo(a, b, maxNumbersBetween) < 0); + } + + /// + /// Compares two doubles and determines if the first value is smaller than the second + /// value to within the specified number of decimal places or not. + /// + /// + /// + /// The values are equal if the difference between the two numbers is smaller than 10^(-numberOfDecimalPlaces). We divide by + /// two so that we have half the range on each side of the numbers, e.g. if decimalPlaces == 2, then 0.01 will equal between + /// 0.005 and 0.015, but not 0.02 and not 0.00 + /// + /// + /// The first value. + /// The second value. + /// The number of decimal places. + /// true if the first value is smaller than the second value; otherwise false. + public static bool IsSmallerWithDecimalPlaces(double a, double b, int decimalPlaces) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if ((double.IsNaN(a)) || (double.IsNaN(b))) + { + return false; + } + + return (CompareToInDecimalPlaces(a, b, decimalPlaces) < 0); + } + + /// + /// Compares two doubles and determines which double is bigger. + /// + /// The first value. + /// The second value. + /// The maximum error in terms of Units in Last Place (ulps), i.e. the maximum number of decimals that may be different. Must be 1 or larger. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// -1 + /// is smaller than by more than the tolerance. + /// + /// + /// 0 + /// is equal to within the tolerance. + /// + /// + /// 1 + /// is bigger than by more than the tolerance. + /// + /// + /// + public static int CompareTo(double a, double b, long maxNumbersBetween) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if ((double.IsNaN(a)) || (double.IsNaN(b))) + { + return a.CompareTo(b); + } + + // If A or B are infinity (positive or negative) then + // only return true if first is smaller + if ((double.IsInfinity(a)) || (double.IsInfinity(b))) + { + return a.CompareTo(b); + } + + // If the numbers are equal to within the tolerance then + // there's technically no difference + if (AlmostEqual(a, b, maxNumbersBetween)) + { + return 0; + } + + return a.CompareTo(b); + } + + /// + /// Compares two doubles and determines which double is bigger. + /// + /// The first value. + /// The second value. + /// The number of decimal places on which the values must be compared. Must be 1 or larger. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// -1 + /// is smaller than by more than a magnitude equal to . + /// + /// + /// 0 + /// is equal to within a magnitude equal to . + /// + /// + /// 1 + /// is bigger than by more than a magnitude equal to . + /// + /// + /// + public static int CompareToInDecimalPlaces(double a, double b, int decimalPlaces) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if ((double.IsNaN(a)) || (double.IsNaN(b))) + { + return a.CompareTo(b); + } + + // If A or B are infinity (positive or negative) then + // only return true if first is smaller + if ((double.IsInfinity(a)) || (double.IsInfinity(b))) + { + return a.CompareTo(b); + } + + // If the numbers are equal to within the number of decimal places + // then there's technically no difference + if (AlmostEqualInDecimalPlaces(a, b, decimalPlaces)) + { + return 0; + } + + // The numbers differ by more than the decimal places, so + // we can check the normal way to see if the first is + // larger than the second. + return a.CompareTo(b); + } + } +} \ No newline at end of file