diff --git a/src/Numerics.Tests/SpecialFunctionsTests/StabilityTests.cs b/src/Numerics.Tests/SpecialFunctionsTests/StabilityTests.cs
new file mode 100644
index 000000000..04fdae635
--- /dev/null
+++ b/src/Numerics.Tests/SpecialFunctionsTests/StabilityTests.cs
@@ -0,0 +1,170 @@
+//
+// Math.NET Numerics, part of the Math.NET Project
+// https://numerics.mathdotnet.com
+// https://github.com/mathnet/mathnet-numerics
+//
+// Copyright (c) 2009-$CURRENT_YEAR$ 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 NUnit.Framework;
+
+namespace MathNet.Numerics.Tests.SpecialFunctionsTests
+{
+ ///
+ /// Special functions tests.
+ ///
+ [TestFixture, Category("Functions")]
+ public class StabilityTests
+ {
+ ///
+ /// Hypotenuse function (double).
+ ///
+ /// Input X value.
+ /// Input Y value.
+ /// Function result.
+ [TestCase(double.NaN, 0, double.NaN)]
+ [TestCase(0, double.NaN, double.NaN)]
+ [TestCase(double.NaN, double.NaN, double.NaN)]
+ [TestCase(double.NegativeInfinity, double.NaN, double.NaN)]
+ [TestCase(double.NaN, double.NegativeInfinity, double.NaN)]
+ [TestCase(double.PositiveInfinity, double.NaN, double.NaN)]
+ [TestCase(double.NaN, double.PositiveInfinity, double.NaN)]
+ [TestCase(double.NegativeInfinity, 0, double.PositiveInfinity)]
+ [TestCase(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity)]
+ [TestCase(double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity)]
+ [TestCase(0, double.NegativeInfinity, double.PositiveInfinity)]
+ [TestCase(double.PositiveInfinity, 0, double.PositiveInfinity)]
+ [TestCase(double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity)]
+ [TestCase(double.PositiveInfinity, double.NegativeInfinity, double.PositiveInfinity)]
+ [TestCase(0, double.NegativeInfinity, double.PositiveInfinity)]
+ [TestCase(0, double.MaxValue, double.MaxValue)]
+ [TestCase(double.MaxValue, 0, double.MaxValue)]
+ [TestCase(0, -double.MinValue, -double.MinValue)]
+ [TestCase(0, double.MinValue, -double.MinValue)]
+ [TestCase(-double.MinValue, 0, -double.MinValue)]
+ [TestCase(double.MinValue, 0, -double.MinValue)]
+ [TestCase(0, double.Epsilon, double.Epsilon)]
+ [TestCase(0, -double.Epsilon, double.Epsilon)]
+ [TestCase(double.Epsilon, 0, double.Epsilon)]
+ [TestCase(-double.Epsilon, 0, double.Epsilon)]
+ [TestCase(0, 0, 0)]
+ [TestCase(0, 7, 7)]
+ [TestCase(0, -7, 7)]
+ [TestCase(7, 0, 7)]
+ [TestCase(-7, 0, 7)]
+ [TestCase(3, 4, 5)]
+ [TestCase(3, -4, 5)]
+ [TestCase(-3, 4, 5)]
+ [TestCase(-3, -4, 5)]
+ [TestCase(4, 3, 5)]
+ [TestCase(4, -3, 5)]
+ [TestCase(-4, 3, 5)]
+ [TestCase(-4, -3, 5)]
+ [TestCase(1, 2.98023223876953125E-8, 1.000000000000000444089210)] // TwoToThePowerOfMinus25 = 2.98023223876953125E-8;
+ [TestCase(1, -2.98023223876953125E-8, 1.000000000000000444089210)]
+ [TestCase(-1, 2.98023223876953125E-8, 1.000000000000000444089210)]
+ [TestCase(-1, -2.98023223876953125E-8, 1.000000000000000444089210)]
+ [TestCase(2.98023223876953125E-8, 1, 1.000000000000000444089210)]
+ [TestCase(2.98023223876953125E-8, -1, 1.000000000000000444089210)]
+ [TestCase(-2.98023223876953125E-8, 1, 1.000000000000000444089210)]
+ [TestCase(-2.98023223876953125E-8, -1, 1.000000000000000444089210)]
+ public void Hypotenuse(double x, double y, double f)
+ {
+ var h = SpecialFunctions.Hypotenuse(x, y);
+ if (double.IsNaN(f))
+ {
+ Assert.IsTrue(double.IsNaN(h));
+ }
+ else
+ {
+ Assert.AreEqual(f, h);
+ }
+ }
+
+ ///
+ /// Hypotenuse function (float).
+ ///
+ /// Input X value.
+ /// Input Y value.
+ /// Function result.
+ [TestCase(float.NaN, 0, float.NaN)]
+ [TestCase(0, float.NaN, float.NaN)]
+ [TestCase(float.NaN, float.NaN, float.NaN)]
+ [TestCase(float.NegativeInfinity, float.NaN, float.NaN)]
+ [TestCase(float.NaN, float.NegativeInfinity, float.NaN)]
+ [TestCase(float.PositiveInfinity, float.NaN, float.NaN)]
+ [TestCase(float.NaN, float.PositiveInfinity, float.NaN)]
+ [TestCase(float.NegativeInfinity, 0, float.PositiveInfinity)]
+ [TestCase(float.NegativeInfinity, float.NegativeInfinity, float.PositiveInfinity)]
+ [TestCase(float.NegativeInfinity, float.PositiveInfinity, float.PositiveInfinity)]
+ [TestCase(0, float.NegativeInfinity, float.PositiveInfinity)]
+ [TestCase(float.PositiveInfinity, 0, float.PositiveInfinity)]
+ [TestCase(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity)]
+ [TestCase(float.PositiveInfinity, float.NegativeInfinity, float.PositiveInfinity)]
+ [TestCase(0, float.NegativeInfinity, float.PositiveInfinity)]
+ [TestCase(0, float.MaxValue, float.MaxValue)]
+ [TestCase(float.MaxValue, 0, float.MaxValue)]
+ [TestCase(0, -float.MinValue, -float.MinValue)]
+ [TestCase(0, float.MinValue, -float.MinValue)]
+ [TestCase(-float.MinValue, 0, -float.MinValue)]
+ [TestCase(float.MinValue, 0, -float.MinValue)]
+ [TestCase(0, float.Epsilon, float.Epsilon)]
+ [TestCase(0, -float.Epsilon, float.Epsilon)]
+ [TestCase(float.Epsilon, 0, float.Epsilon)]
+ [TestCase(-float.Epsilon, 0, float.Epsilon)]
+ [TestCase(0, 0, 0)]
+ [TestCase(0, 7, 7)]
+ [TestCase(0, -7, 7)]
+ [TestCase(7, 0, 7)]
+ [TestCase(-7, 0, 7)]
+ [TestCase(3, 4, 5)]
+ [TestCase(3, -4, 5)]
+ [TestCase(-3, 4, 5)]
+ [TestCase(-3, -4, 5)]
+ [TestCase(4, 3, 5)]
+ [TestCase(4, -3, 5)]
+ [TestCase(-4, 3, 5)]
+ [TestCase(-4, -3, 5)]
+ [TestCase(1, 0.00048828125f, 1.000000119209282445354739f)] // TwoToThePowerOfMinus11 = 0.00048828125f
+ [TestCase(1, -0.00048828125f, 1.000000119209282445354739f)]
+ [TestCase(-1, 0.00048828125f, 1.000000119209282445354739f)]
+ [TestCase(-1, -0.00048828125f, 1.000000119209282445354739f)]
+ [TestCase(0.00048828125f, 1, 1.000000119209282445354739f)]
+ [TestCase(0.00048828125f, -1, 1.000000119209282445354739f)]
+ [TestCase(-0.00048828125f, 1, 1.000000119209282445354739f)]
+ [TestCase(-0.00048828125f, -1, 1.000000119209282445354739f)]
+ public void Hypotenuse(float x, float y, float f)
+ {
+ var h = SpecialFunctions.Hypotenuse(x, y);
+ if (float.IsNaN(f))
+ {
+ Assert.IsTrue(float.IsNaN(h));
+ }
+ else
+ {
+ Assert.AreEqual(f, h);
+ }
+ }
+ }
+}
diff --git a/src/Numerics/SpecialFunctions/Stability.cs b/src/Numerics/SpecialFunctions/Stability.cs
index 6692f5c99..9dc969928 100644
--- a/src/Numerics/SpecialFunctions/Stability.cs
+++ b/src/Numerics/SpecialFunctions/Stability.cs
@@ -91,25 +91,26 @@ public static Complex32 Hypotenuse(Complex32 a, Complex32 b)
/// Returns sqrt(a2 + b2) without underflow/overflow.
public static double Hypotenuse(double a, double b)
{
- if (double.IsNaN(a) || double.IsNaN(b))
- {
- return double.NaN;
- }
-
- if (Math.Abs(a) > Math.Abs(b))
- {
- double r = b/a;
- return Math.Abs(a)*Math.Sqrt(1 + (r*r));
- }
-
- if (b != 0.0)
- {
- // NOTE (ruegg): not "!b.AlmostZero()" to avoid convergence issues (e.g. in SVD algorithm)
- double r = a/b;
- return Math.Abs(b)*Math.Sqrt(1 + (r*r));
- }
-
- return 0d;
+ var min = Math.Abs(a);
+ var max = Math.Abs(b);
+ if (min > max)
+ {
+ (min, max) = (max, min);
+ }
+ if (min == 0)
+ {
+ return max;
+ }
+ else if (double.IsInfinity(min + max))
+ {
+ return double.PositiveInfinity;
+ }
+ else
+ {
+ // NOTE (ruegg): not "!b.AlmostZero()" to avoid convergence issues (e.g. in SVD algorithm)
+ var r = min / max;
+ return max * Math.Sqrt(1 + r * r);
+ }
}
///
@@ -120,20 +121,26 @@ public static double Hypotenuse(double a, double b)
/// Returns sqrt(a2 + b2) without underflow/overflow.
public static float Hypotenuse(float a, float b)
{
- if (Math.Abs(a) > Math.Abs(b))
- {
- float r = b/a;
- return Math.Abs(a)*(float)Math.Sqrt(1 + (r*r));
- }
-
- if (b != 0.0)
- {
- // NOTE (ruegg): not "!b.AlmostZero()" to avoid convergence issues (e.g. in SVD algorithm)
- float r = a/b;
- return Math.Abs(b)*(float)Math.Sqrt(1 + (r*r));
- }
-
- return 0f;
+ var min = Math.Abs(a);
+ var max = Math.Abs(b);
+ if (min > max)
+ {
+ (min, max) = (max, min);
+ }
+ if (min == 0)
+ {
+ return max;
+ }
+ else if (float.IsInfinity(min + max))
+ {
+ return float.PositiveInfinity;
+ }
+ else
+ {
+ // NOTE (ruegg): not "!b.AlmostZero()" to avoid convergence issues (e.g. in SVD algorithm)
+ var r = min / max;
+ return max * (float)Math.Sqrt(1 + r * r);
+ }
}
}
}