Skip to content

Improve min max performance and IEEE compliance #151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 14 additions & 93 deletions Tests/MathUnitTests/MathUnitTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,20 +148,20 @@ public static void Clamp_Float()
[TestMethod]
public static void Clamp_MinGreaterThanMax_ThrowsArgumentException()
{
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((sbyte)1, (sbyte)2, (sbyte)1));
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((byte)1, (byte)2, (byte)1));
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((short)1, (short)2, (short)1));
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((ushort)1, (ushort)2, (ushort)1));
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((sbyte)1, (sbyte)2, (sbyte)1));
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((byte)1, (byte)2, (byte)1));
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((short)1, (short)2, (short)1));
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((ushort)1, (ushort)2, (ushort)1));

// keeping cast on purpose to be able to test the method
#pragma warning disable IDE0004
#pragma warning disable S1905
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((int)1, (int)2, (int)1));
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((uint)1, (uint)2, (uint)1));
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((long)1, (long)2, (long)1));
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((ulong)1, (ulong)2, (ulong)1));
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((float)1, (float)2, (float)1));
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((double)1, (double)2, (double)1));
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((int)1, (int)2, (int)1));
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((uint)1, (uint)2, (uint)1));
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((long)1, (long)2, (long)1));
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((ulong)1, (ulong)2, (ulong)1));
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((float)1, (float)2, (float)1));
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((double)1, (double)2, (double)1));
#pragma warning restore S1905
#pragma warning restore IDE0004
}
Expand Down Expand Up @@ -201,22 +201,23 @@ public static void Test_Not_Numbers()
double pos_inf = (3.0 / 0.0);
double neg_inf = (-3.0 / 0.0);

Console.WriteLine($"Expect nan={nan}, pos_inf={pos_inf}, neg_inf={neg_inf}");

Assert.IsTrue(double.IsNaN(nan), "NaN was not correctly identified");
Assert.IsFalse(double.IsPositiveInfinity(nan), "NaN was incorrectly identified as Positive Infinity");
Assert.IsFalse(double.IsNegativeInfinity(nan), "NaN was incorrectly identified as Negative Infinity");

//--//

Assert.IsFalse(double.IsNaN(pos_inf), "Positive Infinity was incorrectly identified as double.NaN");
Assert.IsTrue(double.IsPositiveInfinity(pos_inf), "Positive Infinity was not correctly identified");
Assert.IsFalse(double.IsNaN(pos_inf), "Positive Infinity was incorrectly identified asdouble.NaN");
Assert.IsFalse(double.IsNegativeInfinity(pos_inf), "Positive Infinity was incorrectly identified as Negative Infinity");

//--//

Assert.IsTrue(double.IsNegativeInfinity(neg_inf), "NegativeInfinity was not correctly identified");
Assert.IsFalse(double.IsNaN(neg_inf), "NegativeInfinity Infinity was incorrectly identified as double.NaN");
Assert.IsFalse(double.IsPositiveInfinity(neg_inf), "NegativeInfinity Infinity was incorrectly identified as Positive Infinity");
Assert.IsFalse(double.IsNaN(neg_inf), "NegativeInfinity Infinity was incorrectly identified asdouble.NaN");
Assert.IsTrue(double.IsNegativeInfinity(neg_inf), "NegativeInfinity was not correctly identified");
}

[TestMethod]
Expand Down Expand Up @@ -665,86 +666,6 @@ public static void Test_Log10()
Assert.IsTrue(double.IsNaN(res), $"Log10(...NegativeInfinity) -- FAILED AT: {res}");
}

[TestMethod]
public static void Test_Max_2()
{
//
// Summary:
// Returns the larger of two double-precision floating-point numbers.
//
// Parameters:
// val1:
// The first of two double-precision floating-point numbers to compare.
//
// val2:
// The second of two double-precision floating-point numbers to compare.
//
// Returns:
// Parameter val1 or val2, whichever is larger. If val1 OR both val1
// and val2 are equal to System.Double.NaN, System.Double.NaN is returned.
double[] x = new double[] { 6.00000000000000000000, 6.50000000000000000000, 7.00000000000000000000, 7.50000000000000000000, -0.50000000000000000000, -1.00000000000000000000, -1.50000000000000000000, -2.00000000000000000000 };
double[] y = new double[] { 1, 1.140238321, 1.600286858, 2.509178479, 4.121836054, 6.890572365, 11.59195328, -0.50000000000000000000 };

double[] answer = new double[] { 6.00000000000000000000, 6.50000000000000000000, 7.00000000000000000000, 7.50000000000000000000, 4.12183605386995000000, 6.89057236497588000000, 11.59195328, -0.50000000000000000000 };
double res;

for (int i = 0; i < x.Length; i++)
{
res = Math.Max(x[i], y[i]);

Assert.IsFalse((answer[i] - res) > 0.0001d || (answer[i] - res) < -0.0001d, $"Max(...{x[i]}, {y[i]}) -- FAILED AT: {res}");
}

res = Math.Max(10, double.NaN);
Assert.IsTrue(double.IsNaN(res), $"Max(...10,double.NaN) -- FAILED AT: {res}");

res = Math.Max(double.NaN, 10);
Assert.IsTrue(double.IsNaN(res), $"Max(...NaN, 10) -- FAILED AT: {res}");

res = Math.Max(double.NaN, double.NaN);
Assert.IsTrue(double.IsNaN(res), $"Max(...NaN,double.NaN) -- FAILED AT: {res}");
}

[TestMethod]
public static void Test_Min_2()
{
//
// Summary:
// Returns the smaller of two double-precision floating-point numbers.
//
// Parameters:
// val1:
// The first of two double-precision floating-point numbers to compare.
//
// val2:
// The second of two double-precision floating-point numbers to compare.
//
// Returns:
// Parameter val1 or val2, whichever is smaller. If val1, val2, or both val1
// and val2 are equal to System.Double.NaN, System.Double.NaN is returned.
double[] x = new double[] { 6.00000000000000000000, 6.50000000000000000000, 7.00000000000000000000, 7.50000000000000000000, -0.50000000000000000000, -1.00000000000000000000, -1.50000000000000000000, -2.00000000000000000000 };
double[] y = new double[] { 1, 1.140238321, 1.600286858, 2.509178479, 4.121836054, 6.890572365, 11.59195328, -0.50000000000000000000 };

double[] answer = new double[] { 1, 1.140238321, 1.600286858, 2.509178479, -0.50000000000000000000, -1.00000000000000000000, -1.50000000000000000000, -2.00000000000000000000 };
double res;

for (int i = 0; i < x.Length; i++)
{
res = Math.Min(x[i], y[i]);

Assert.IsFalse((answer[i] - res) > 0.0001d || (answer[i] - res) < -0.0001d, $"Min(...{x[i]}, {y[i]}) -- FAILED AT: {res}");
}

res = Math.Min(10, double.NaN);
Assert.IsTrue(double.IsNaN(res), $"Min(...10,double.NaN) -- FAILED AT: {res}");

res = Math.Min(double.NaN, 10);
Assert.AreEqual(res, 10, $"Min(...NaN, 10) -- FAILED AT: {res}");

res = Math.Min(double.NaN, double.NaN);
Assert.IsTrue(double.IsNaN(res), $"Min(...NaN,double.NaN) -- FAILED AT: {res}");
}

[TestMethod]
public static void Test_Pow_2()
{
Expand Down
15 changes: 9 additions & 6 deletions Tests/MathUnitTests/MathUnitTests.nfproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,26 @@
<Import Project="$(NanoFrameworkProjectSystemPath)NFProjectSystem.props" Condition="Exists('$(NanoFrameworkProjectSystemPath)NFProjectSystem.props')" />
<ItemGroup>
<Compile Include="MathUnitTest.cs" />
<Compile Include="Max_Tests.cs" />
<Compile Include="Min_Tests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="mscorlib, Version=1.14.3.0, Culture=neutral, PublicKeyToken=c07d481e9758c731">
<Content Include="packages.lock.json" />
</ItemGroup>
<ItemGroup>
<Reference Include="mscorlib">
<HintPath>..\..\packages\nanoFramework.CoreLibrary.1.14.2\lib\mscorlib.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nanoFramework.TestFramework, Version=2.1.85.0, Culture=neutral, PublicKeyToken=c07d481e9758c731">
<Reference Include="nanoFramework.TestFramework">
<HintPath>..\..\packages\nanoFramework.TestFramework.2.1.85\lib\nanoFramework.TestFramework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nanoFramework.UnitTestLauncher, Version=0.0.0.0, Culture=neutral, PublicKeyToken=c07d481e9758c731">
<Reference Include="nanoFramework.UnitTestLauncher">
<HintPath>..\..\packages\nanoFramework.TestFramework.2.1.85\lib\nanoFramework.UnitTestLauncher.exe</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="nano.runsettings" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
Expand Down
157 changes: 157 additions & 0 deletions Tests/MathUnitTests/Max_Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using System;
using nanoFramework.TestFramework;

namespace MathUnitTests
{
[TestClass]
public class Max_Tests
{
[TestMethod]
public void Max_Double_returns_greater_value()
{
var val1 = new[]
{
6.00000000000000000000d, 6.50000000000000000000d, 7.00000000000000000000d, 7.50000000000000000000d,
-0.50000000000000000000d, -1.00000000000000000000d, -1.50000000000000000000d, -2.00000000000000000000d
};
var val2 = new[]
{
1d, 1.140238321d, 1.600286858d, 2.509178479d, 4.121836054d, 6.890572365d, 11.59195328d, -0.50000000000000000000d
};

var expected = new[]
{
val1[0], val1[1], val1[2], val1[3], val2[4], val2[5], val2[6], val2[7]
};

for (var i = 0; i < val1.Length; i++)
{
var actual = Math.Max(val1[i], val2[i]);

Assert.IsFalse((expected[i] - actual) > 0.0001d || (expected[i] - actual) < -0.0001d);
}
}

[TestMethod]
public void Max_Double_returns_NaN_if_both_val1_and_val2_are_NaN()
{
var actual = Math.Max(double.NaN, double.NaN);

Assert.IsTrue(double.IsNaN(actual));
}

[TestMethod]
public void Max_Double_returns_NaN_if_val1_is_NaN()
{
var actual = Math.Max(double.NaN, Math.PI);

Assert.IsTrue(double.IsNaN(actual));
}

[TestMethod]
public void Max_Double_returns_NaN_if_val2_is_NaN()
{
var actual = Math.Max(Math.PI, double.NaN);

Assert.IsTrue(double.IsNaN(actual));
}

[TestMethod]
public void Max_Double_treats_positive_zero_as_greater_than_negative_zero()
{
// 00-00-00-00-00-00-00-00
var positiveZero = 0.0d;

// 00-00-00-00-00-00-00-80
var negativeZero = -0.0d;

var result1 = Math.Max(positiveZero, negativeZero);
var result2 = Math.Max(negativeZero, positiveZero);

Console.WriteLine(BitConverter.ToString(BitConverter.GetBytes(result1)));
Console.WriteLine(BitConverter.ToString(BitConverter.GetBytes(result2)));

Assert.AreEqual(positiveZero, result1);
Assert.AreEqual(positiveZero, result2);
}

[TestMethod]
public void Max_Float_returns_greater_value()
{
var val1 = new[]
{
6.00000000000000000000f, 6.50000000000000000000f, 7.00000000000000000000f, 7.50000000000000000000f,
-0.50000000000000000000f, -1.00000000000000000000f, -1.50000000000000000000f, -2.00000000000000000000f
};
var val2 = new[]
{
1f, 1.140238321f, 1.600286858f, 2.509178479f, 4.121836054f, 6.890572365f, 11.59195328f, -0.50000000000000000000f
};

var expected = new[]
{
val1[0], val1[1], val1[2], val1[3], val2[4], val2[5], val2[6], val2[7]
};

for (var i = 0; i < val1.Length; i++)
{
var actual = Math.Max(val1[i], val2[i]);

Assert.AreEqual(expected[i], actual);
}
}

[TestMethod]
public void Max_Float_returns_NaN_if_both_val1_and_val2_are_NaN()
{
var actual = Math.Max(float.NaN, float.NaN);

Assert.IsTrue(float.IsNaN(actual));
}

[TestMethod]
public void Max_Float_returns_NaN_if_val1_is_NaN()
{
var actual = Math.Max(float.NaN, (float)Math.PI);

Assert.IsTrue(float.IsNaN(actual));
}

[TestMethod]
public void Max_Float_returns_NaN_if_val2_is_NaN()
{
var actual = Math.Max((float)Math.PI, float.NaN);

Assert.IsTrue(float.IsNaN(actual));
}

[TestMethod]
public void Max_Float_treats_positive_zero_as_greater_than_negative_zero()
{
// 00-00-00-00-00-00-00-00
var positiveZero = 0.0f;

// 00-00-00-00-00-00-00-80
var negativeZero = -0.0f;

var result1 = Math.Max(positiveZero, negativeZero);
var result2 = Math.Max(negativeZero, positiveZero);

Console.WriteLine(BitConverter.ToString(BitConverter.GetBytes(result1)));
Console.WriteLine(BitConverter.ToString(BitConverter.GetBytes(result2)));

Assert.AreEqual(positiveZero, result1);
Assert.AreEqual(positiveZero, result2);
}

[TestMethod]
public void Max_Int_returns_greater_value()
{
var expect = 12345678;
var lower = expect / 2;

Assert.AreEqual(expect, Math.Max(expect, lower));
Assert.AreEqual(expect, Math.Max(lower, expect));
}
}
}
Loading