Skip to content

Commit 79c817a

Browse files
authored
Improve min max performance and IEEE compliance (#151)
***NO_CI***
1 parent f516ba5 commit 79c817a

File tree

15 files changed

+562
-199
lines changed

15 files changed

+562
-199
lines changed

Tests/MathUnitTests/MathUnitTest.cs

Lines changed: 14 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -148,20 +148,20 @@ public static void Clamp_Float()
148148
[TestMethod]
149149
public static void Clamp_MinGreaterThanMax_ThrowsArgumentException()
150150
{
151-
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((sbyte)1, (sbyte)2, (sbyte)1));
152-
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((byte)1, (byte)2, (byte)1));
153-
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((short)1, (short)2, (short)1));
154-
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((ushort)1, (ushort)2, (ushort)1));
151+
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((sbyte)1, (sbyte)2, (sbyte)1));
152+
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((byte)1, (byte)2, (byte)1));
153+
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((short)1, (short)2, (short)1));
154+
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((ushort)1, (ushort)2, (ushort)1));
155155

156156
// keeping cast on purpose to be able to test the method
157157
#pragma warning disable IDE0004
158158
#pragma warning disable S1905
159-
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((int)1, (int)2, (int)1));
160-
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((uint)1, (uint)2, (uint)1));
161-
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((long)1, (long)2, (long)1));
162-
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((ulong)1, (ulong)2, (ulong)1));
163-
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((float)1, (float)2, (float)1));
164-
Assert.Throws(typeof(ArgumentException), () => Math.Clamp((double)1, (double)2, (double)1));
159+
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((int)1, (int)2, (int)1));
160+
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((uint)1, (uint)2, (uint)1));
161+
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((long)1, (long)2, (long)1));
162+
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((ulong)1, (ulong)2, (ulong)1));
163+
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((float)1, (float)2, (float)1));
164+
Assert.ThrowsException(typeof(ArgumentException), () => Math.Clamp((double)1, (double)2, (double)1));
165165
#pragma warning restore S1905
166166
#pragma warning restore IDE0004
167167
}
@@ -201,22 +201,23 @@ public static void Test_Not_Numbers()
201201
double pos_inf = (3.0 / 0.0);
202202
double neg_inf = (-3.0 / 0.0);
203203

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

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

209210
//--//
210211

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

215216
//--//
216217

217-
Assert.IsTrue(double.IsNegativeInfinity(neg_inf), "NegativeInfinity was not correctly identified");
218+
Assert.IsFalse(double.IsNaN(neg_inf), "NegativeInfinity Infinity was incorrectly identified as double.NaN");
218219
Assert.IsFalse(double.IsPositiveInfinity(neg_inf), "NegativeInfinity Infinity was incorrectly identified as Positive Infinity");
219-
Assert.IsFalse(double.IsNaN(neg_inf), "NegativeInfinity Infinity was incorrectly identified asdouble.NaN");
220+
Assert.IsTrue(double.IsNegativeInfinity(neg_inf), "NegativeInfinity was not correctly identified");
220221
}
221222

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

668-
[TestMethod]
669-
public static void Test_Max_2()
670-
{
671-
//
672-
// Summary:
673-
// Returns the larger of two double-precision floating-point numbers.
674-
//
675-
// Parameters:
676-
// val1:
677-
// The first of two double-precision floating-point numbers to compare.
678-
//
679-
// val2:
680-
// The second of two double-precision floating-point numbers to compare.
681-
//
682-
// Returns:
683-
// Parameter val1 or val2, whichever is larger. If val1 OR both val1
684-
// and val2 are equal to System.Double.NaN, System.Double.NaN is returned.
685-
double[] x = new double[] { 6.00000000000000000000, 6.50000000000000000000, 7.00000000000000000000, 7.50000000000000000000, -0.50000000000000000000, -1.00000000000000000000, -1.50000000000000000000, -2.00000000000000000000 };
686-
double[] y = new double[] { 1, 1.140238321, 1.600286858, 2.509178479, 4.121836054, 6.890572365, 11.59195328, -0.50000000000000000000 };
687-
688-
double[] answer = new double[] { 6.00000000000000000000, 6.50000000000000000000, 7.00000000000000000000, 7.50000000000000000000, 4.12183605386995000000, 6.89057236497588000000, 11.59195328, -0.50000000000000000000 };
689-
double res;
690-
691-
for (int i = 0; i < x.Length; i++)
692-
{
693-
res = Math.Max(x[i], y[i]);
694-
695-
Assert.IsFalse((answer[i] - res) > 0.0001d || (answer[i] - res) < -0.0001d, $"Max(...{x[i]}, {y[i]}) -- FAILED AT: {res}");
696-
}
697-
698-
res = Math.Max(10, double.NaN);
699-
Assert.IsTrue(double.IsNaN(res), $"Max(...10,double.NaN) -- FAILED AT: {res}");
700-
701-
res = Math.Max(double.NaN, 10);
702-
Assert.IsTrue(double.IsNaN(res), $"Max(...NaN, 10) -- FAILED AT: {res}");
703-
704-
res = Math.Max(double.NaN, double.NaN);
705-
Assert.IsTrue(double.IsNaN(res), $"Max(...NaN,double.NaN) -- FAILED AT: {res}");
706-
}
707-
708-
[TestMethod]
709-
public static void Test_Min_2()
710-
{
711-
//
712-
// Summary:
713-
// Returns the smaller of two double-precision floating-point numbers.
714-
//
715-
// Parameters:
716-
// val1:
717-
// The first of two double-precision floating-point numbers to compare.
718-
//
719-
// val2:
720-
// The second of two double-precision floating-point numbers to compare.
721-
//
722-
// Returns:
723-
// Parameter val1 or val2, whichever is smaller. If val1, val2, or both val1
724-
// and val2 are equal to System.Double.NaN, System.Double.NaN is returned.
725-
double[] x = new double[] { 6.00000000000000000000, 6.50000000000000000000, 7.00000000000000000000, 7.50000000000000000000, -0.50000000000000000000, -1.00000000000000000000, -1.50000000000000000000, -2.00000000000000000000 };
726-
double[] y = new double[] { 1, 1.140238321, 1.600286858, 2.509178479, 4.121836054, 6.890572365, 11.59195328, -0.50000000000000000000 };
727-
728-
double[] answer = new double[] { 1, 1.140238321, 1.600286858, 2.509178479, -0.50000000000000000000, -1.00000000000000000000, -1.50000000000000000000, -2.00000000000000000000 };
729-
double res;
730-
731-
for (int i = 0; i < x.Length; i++)
732-
{
733-
res = Math.Min(x[i], y[i]);
734-
735-
Assert.IsFalse((answer[i] - res) > 0.0001d || (answer[i] - res) < -0.0001d, $"Min(...{x[i]}, {y[i]}) -- FAILED AT: {res}");
736-
}
737-
738-
res = Math.Min(10, double.NaN);
739-
Assert.IsTrue(double.IsNaN(res), $"Min(...10,double.NaN) -- FAILED AT: {res}");
740-
741-
res = Math.Min(double.NaN, 10);
742-
Assert.AreEqual(res, 10, $"Min(...NaN, 10) -- FAILED AT: {res}");
743-
744-
res = Math.Min(double.NaN, double.NaN);
745-
Assert.IsTrue(double.IsNaN(res), $"Min(...NaN,double.NaN) -- FAILED AT: {res}");
746-
}
747-
748669
[TestMethod]
749670
public static void Test_Pow_2()
750671
{

Tests/MathUnitTests/MathUnitTests.nfproj

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,26 @@
2727
<Import Project="$(NanoFrameworkProjectSystemPath)NFProjectSystem.props" Condition="Exists('$(NanoFrameworkProjectSystemPath)NFProjectSystem.props')" />
2828
<ItemGroup>
2929
<Compile Include="MathUnitTest.cs" />
30+
<Compile Include="Max_Tests.cs" />
31+
<Compile Include="Min_Tests.cs" />
3032
<Compile Include="Properties\AssemblyInfo.cs" />
3133
</ItemGroup>
3234
<ItemGroup>
33-
<Reference Include="mscorlib, Version=1.14.3.0, Culture=neutral, PublicKeyToken=c07d481e9758c731">
35+
<Content Include="packages.lock.json" />
36+
</ItemGroup>
37+
<ItemGroup>
38+
<Reference Include="mscorlib">
3439
<HintPath>..\..\packages\nanoFramework.CoreLibrary.1.14.2\lib\mscorlib.dll</HintPath>
35-
<Private>True</Private>
3640
</Reference>
37-
<Reference Include="nanoFramework.TestFramework, Version=2.1.85.0, Culture=neutral, PublicKeyToken=c07d481e9758c731">
41+
<Reference Include="nanoFramework.TestFramework">
3842
<HintPath>..\..\packages\nanoFramework.TestFramework.2.1.85\lib\nanoFramework.TestFramework.dll</HintPath>
39-
<Private>True</Private>
4043
</Reference>
41-
<Reference Include="nanoFramework.UnitTestLauncher, Version=0.0.0.0, Culture=neutral, PublicKeyToken=c07d481e9758c731">
44+
<Reference Include="nanoFramework.UnitTestLauncher">
4245
<HintPath>..\..\packages\nanoFramework.TestFramework.2.1.85\lib\nanoFramework.UnitTestLauncher.exe</HintPath>
43-
<Private>True</Private>
4446
</Reference>
4547
</ItemGroup>
4648
<ItemGroup>
49+
<None Include="nano.runsettings" />
4750
<None Include="packages.config" />
4851
</ItemGroup>
4952
<ItemGroup>

Tests/MathUnitTests/Max_Tests.cs

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
using System;
2+
using nanoFramework.TestFramework;
3+
4+
namespace MathUnitTests
5+
{
6+
[TestClass]
7+
public class Max_Tests
8+
{
9+
[TestMethod]
10+
public void Max_Double_returns_greater_value()
11+
{
12+
var val1 = new[]
13+
{
14+
6.00000000000000000000d, 6.50000000000000000000d, 7.00000000000000000000d, 7.50000000000000000000d,
15+
-0.50000000000000000000d, -1.00000000000000000000d, -1.50000000000000000000d, -2.00000000000000000000d
16+
};
17+
var val2 = new[]
18+
{
19+
1d, 1.140238321d, 1.600286858d, 2.509178479d, 4.121836054d, 6.890572365d, 11.59195328d, -0.50000000000000000000d
20+
};
21+
22+
var expected = new[]
23+
{
24+
val1[0], val1[1], val1[2], val1[3], val2[4], val2[5], val2[6], val2[7]
25+
};
26+
27+
for (var i = 0; i < val1.Length; i++)
28+
{
29+
var actual = Math.Max(val1[i], val2[i]);
30+
31+
Assert.IsFalse((expected[i] - actual) > 0.0001d || (expected[i] - actual) < -0.0001d);
32+
}
33+
}
34+
35+
[TestMethod]
36+
public void Max_Double_returns_NaN_if_both_val1_and_val2_are_NaN()
37+
{
38+
var actual = Math.Max(double.NaN, double.NaN);
39+
40+
Assert.IsTrue(double.IsNaN(actual));
41+
}
42+
43+
[TestMethod]
44+
public void Max_Double_returns_NaN_if_val1_is_NaN()
45+
{
46+
var actual = Math.Max(double.NaN, Math.PI);
47+
48+
Assert.IsTrue(double.IsNaN(actual));
49+
}
50+
51+
[TestMethod]
52+
public void Max_Double_returns_NaN_if_val2_is_NaN()
53+
{
54+
var actual = Math.Max(Math.PI, double.NaN);
55+
56+
Assert.IsTrue(double.IsNaN(actual));
57+
}
58+
59+
[TestMethod]
60+
public void Max_Double_treats_positive_zero_as_greater_than_negative_zero()
61+
{
62+
// 00-00-00-00-00-00-00-00
63+
var positiveZero = 0.0d;
64+
65+
// 00-00-00-00-00-00-00-80
66+
var negativeZero = -0.0d;
67+
68+
var result1 = Math.Max(positiveZero, negativeZero);
69+
var result2 = Math.Max(negativeZero, positiveZero);
70+
71+
Console.WriteLine(BitConverter.ToString(BitConverter.GetBytes(result1)));
72+
Console.WriteLine(BitConverter.ToString(BitConverter.GetBytes(result2)));
73+
74+
Assert.AreEqual(positiveZero, result1);
75+
Assert.AreEqual(positiveZero, result2);
76+
}
77+
78+
[TestMethod]
79+
public void Max_Float_returns_greater_value()
80+
{
81+
var val1 = new[]
82+
{
83+
6.00000000000000000000f, 6.50000000000000000000f, 7.00000000000000000000f, 7.50000000000000000000f,
84+
-0.50000000000000000000f, -1.00000000000000000000f, -1.50000000000000000000f, -2.00000000000000000000f
85+
};
86+
var val2 = new[]
87+
{
88+
1f, 1.140238321f, 1.600286858f, 2.509178479f, 4.121836054f, 6.890572365f, 11.59195328f, -0.50000000000000000000f
89+
};
90+
91+
var expected = new[]
92+
{
93+
val1[0], val1[1], val1[2], val1[3], val2[4], val2[5], val2[6], val2[7]
94+
};
95+
96+
for (var i = 0; i < val1.Length; i++)
97+
{
98+
var actual = Math.Max(val1[i], val2[i]);
99+
100+
Assert.AreEqual(expected[i], actual);
101+
}
102+
}
103+
104+
[TestMethod]
105+
public void Max_Float_returns_NaN_if_both_val1_and_val2_are_NaN()
106+
{
107+
var actual = Math.Max(float.NaN, float.NaN);
108+
109+
Assert.IsTrue(float.IsNaN(actual));
110+
}
111+
112+
[TestMethod]
113+
public void Max_Float_returns_NaN_if_val1_is_NaN()
114+
{
115+
var actual = Math.Max(float.NaN, (float)Math.PI);
116+
117+
Assert.IsTrue(float.IsNaN(actual));
118+
}
119+
120+
[TestMethod]
121+
public void Max_Float_returns_NaN_if_val2_is_NaN()
122+
{
123+
var actual = Math.Max((float)Math.PI, float.NaN);
124+
125+
Assert.IsTrue(float.IsNaN(actual));
126+
}
127+
128+
[TestMethod]
129+
public void Max_Float_treats_positive_zero_as_greater_than_negative_zero()
130+
{
131+
// 00-00-00-00-00-00-00-00
132+
var positiveZero = 0.0f;
133+
134+
// 00-00-00-00-00-00-00-80
135+
var negativeZero = -0.0f;
136+
137+
var result1 = Math.Max(positiveZero, negativeZero);
138+
var result2 = Math.Max(negativeZero, positiveZero);
139+
140+
Console.WriteLine(BitConverter.ToString(BitConverter.GetBytes(result1)));
141+
Console.WriteLine(BitConverter.ToString(BitConverter.GetBytes(result2)));
142+
143+
Assert.AreEqual(positiveZero, result1);
144+
Assert.AreEqual(positiveZero, result2);
145+
}
146+
147+
[TestMethod]
148+
public void Max_Int_returns_greater_value()
149+
{
150+
var expect = 12345678;
151+
var lower = expect / 2;
152+
153+
Assert.AreEqual(expect, Math.Max(expect, lower));
154+
Assert.AreEqual(expect, Math.Max(lower, expect));
155+
}
156+
}
157+
}

0 commit comments

Comments
 (0)