diff --git a/src/_common/Math/NullMath.cs b/src/_common/Math/NullMath.cs
index f82656be5..7004f0408 100644
--- a/src/_common/Math/NullMath.cs
+++ b/src/_common/Math/NullMath.cs
@@ -1,7 +1,9 @@
+using System.Runtime.CompilerServices;
+
namespace Skender.Stock.Indicators;
///
-/// Nullable System. functions.
+/// Nullable System. functions.
///
///
/// System.Math infamously does not allow
@@ -16,10 +18,13 @@ public static class NullMath
///
/// The nullable double value.
/// The absolute value, or null if the input is null.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double? Abs(this double? value)
- => value is null
- ? null
- : value < 0 ? (double)-value : (double)value;
+ => value.HasValue
+ ? (value.GetValueOrDefault() < 0
+ ? -value.GetValueOrDefault()
+ : value)
+ : null;
///
/// Rounds a nullable decimal value to a specified number of fractional digits.
@@ -27,10 +32,11 @@ public static class NullMath
/// The nullable decimal value.
/// The number of fractional digits.
/// The rounded value, or null if the input is null.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static decimal? Round(this decimal? value, int digits)
- => value is null
- ? null
- : Math.Round((decimal)value, digits);
+ => value.HasValue
+ ? Math.Round(value.GetValueOrDefault(), digits)
+ : null;
///
/// Rounds a nullable double value to a specified number of fractional digits.
@@ -38,26 +44,31 @@ public static class NullMath
/// The nullable double value.
/// The number of fractional digits.
/// The rounded value, or null if the input is null.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double? Round(this double? value, int digits)
- => value is null
- ? null
- : Math.Round((double)value, digits);
+ => value.HasValue
+ ? Math.Round(value.GetValueOrDefault(), digits)
+ : null;
///
/// Rounds a double value to a specified number of fractional digits.
+ /// It is an extension alias of
///
/// The double value.
/// The number of fractional digits.
/// The rounded value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Round(this double value, int digits)
=> Math.Round(value, digits);
///
/// Rounds a decimal value to a specified number of fractional digits.
+ /// It is an extension alias of
///
/// The decimal value.
/// The number of fractional digits.
/// The rounded value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static decimal Round(this decimal value, int digits)
=> Math.Round(value, digits);
@@ -66,26 +77,29 @@ public static decimal Round(this decimal value, int digits)
///
/// The nullable double value.
/// The value, or NaN if the input is null.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Null2NaN(this double? value)
- => value ?? double.NaN;
+ => value.GetValueOrDefault(double.NaN);
///
/// Converts a nullable decimal value to NaN if it is null.
///
/// The nullable decimal value.
/// The value as a double, or NaN if the input is null.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Null2NaN(this decimal? value)
- => value is null
- ? double.NaN
- : (double)value;
+ => value.HasValue
+ ? (double)value.GetValueOrDefault()
+ : double.NaN;
///
/// Converts a nullable double value to null if it is NaN.
///
/// The nullable double value.
/// The value, or null if the input is NaN.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double? NaN2Null(this double? value)
- => value is double.NaN
+ => value.HasValue && double.IsNaN(value.GetValueOrDefault())
? null
: value;
@@ -94,6 +108,7 @@ public static double Null2NaN(this decimal? value)
///
/// The double value.
/// The value, or null if the input is NaN.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double? NaN2Null(this double value)
=> double.IsNaN(value)
? null
diff --git a/tests/indicators/_common/Math/NullMath.Tests.cs b/tests/indicators/_common/Math/NullMath.Tests.cs
new file mode 100644
index 000000000..86b72ab03
--- /dev/null
+++ b/tests/indicators/_common/Math/NullMath.Tests.cs
@@ -0,0 +1,72 @@
+namespace Utilities;
+
+#pragma warning disable CA1805 // Do not initialize unnecessarily
+
+[TestClass]
+public class NullMathTests : TestBase
+{
+ private readonly double? dblPos = 100.12345;
+ private readonly double? dblNeg = -200.98765;
+ private readonly double? dblNul = null;
+ private readonly decimal? decPos = 10.12345m;
+ private readonly decimal? decNeg = -20.98765m;
+ private readonly decimal? decNul = null;
+
+ [TestMethod]
+ public void AbsDouble()
+ {
+ dblPos.Abs().Should().Be(100.12345d);
+ dblNeg.Abs().Should().Be(200.98765d);
+ dblNul.Abs().Should().BeNull();
+ }
+
+ [TestMethod]
+ public void RoundDecimal()
+ {
+ decPos.Round(2).Should().Be(10.12m);
+ decNeg.Round(2).Should().Be(-20.99m);
+ decNul.Round(2).Should().BeNull();
+
+ 10.12345m.Round(2).Should().Be(10.12m);
+ }
+
+ [TestMethod]
+ public void RoundDouble()
+ {
+ dblPos.Round(2).Should().Be(100.12d);
+ dblNeg.Round(2).Should().Be(-200.99d);
+ dblNul.Round(2).Should().BeNull();
+
+ 100.12345d.Round(2).Should().Be(100.12d);
+ }
+
+ [TestMethod]
+ public void Null2NaN()
+ {
+ // doubles
+ dblPos.Null2NaN().Should().Be(100.12345d);
+ dblNeg.Null2NaN().Should().Be(-200.98765d);
+ dblNul.Null2NaN().Should().Be(double.NaN);
+
+ // decimals ยป doubles
+ decPos.Null2NaN().Should().Be(10.12345d);
+ decNeg.Null2NaN().Should().Be(-20.98765d);
+ decNul.Null2NaN().Should().Be(double.NaN);
+ }
+
+ [TestMethod]
+ public void NaN2Null()
+ {
+ // double (nullable)
+ double? dblNaNul = double.NaN;
+ dblNaNul.NaN2Null().Should().BeNull();
+ dblPos.NaN2Null().Should().Be(100.12345d);
+ dblNeg.NaN2Null().Should().Be(-200.98765d);
+
+ // double (non-nullable)
+ double dblNaN = double.NaN;
+ dblNaN.NaN2Null().Should().BeNull();
+ 100.12345d.NaN2Null().Should().Be(100.12345d);
+ (-200.98765d).NaN2Null().Should().Be(-200.98765d);
+ }
+}
diff --git a/tests/performance/Perf.Utility.NullMath.cs b/tests/performance/Perf.Utility.NullMath.cs
new file mode 100644
index 000000000..2096d1af3
--- /dev/null
+++ b/tests/performance/Perf.Utility.NullMath.cs
@@ -0,0 +1,64 @@
+namespace Tests.Performance;
+
+#pragma warning disable CA1805 // Do not initialize unnecessarily
+
+[ShortRunJob]
+public class UtilityNullMath
+{
+ private static readonly double? dblVal = 54321.0123456789d;
+ private static readonly double? dblNul = null;
+ private static readonly decimal? decVal = 54321.0123456789m;
+ private static readonly decimal? decNul = null;
+ private static readonly double? nulNaN = double.NaN;
+ private const double dblNaN = double.NaN;
+
+ // Abs()
+
+ [Benchmark]
+ public double? AbsDblVal() => dblVal.Abs();
+
+ [Benchmark]
+ public double? AbsDblNul() => dblNul.Abs();
+
+ // Round()
+
+ [Benchmark]
+ public decimal? RoundDecVal() => decVal.Round(2);
+
+ [Benchmark]
+ public decimal? RoundDecNul() => decNul.Round(2);
+
+ [Benchmark]
+ public double? RoundDblVal() => dblVal.Round(2);
+
+ [Benchmark]
+ public double? RoundDblNul() => dblNul.Round(2);
+
+ // Null2NaN()
+
+ [Benchmark]
+ public double Null2NaNDecVal() => decVal.Null2NaN();
+
+ [Benchmark]
+ public double Null2NaNDecNul() => decNul.Null2NaN();
+
+ [Benchmark]
+ public double Null2NaNDblVal() => dblVal.Null2NaN();
+
+ [Benchmark]
+ public double Null2NaNDblNul() => dblNul.Null2NaN();
+
+ // Nan2Null()
+
+ [Benchmark]
+ public double? NaN2NullDblVal() => dblVal.NaN2Null();
+
+ [Benchmark]
+ public double? NaN2NullDblNul() => dblNul.NaN2Null();
+
+ [Benchmark]
+ public double? NaN2NullNaNVal() => dblNaN.NaN2Null();
+
+ [Benchmark]
+ public double? NaN2NullNanNul() => nulNaN.NaN2Null();
+}
diff --git a/tests/performance/Perf.Utility.Maths.cs b/tests/performance/Perf.Utility.StdDev.cs
similarity index 94%
rename from tests/performance/Perf.Utility.Maths.cs
rename to tests/performance/Perf.Utility.StdDev.cs
index a8f4acab7..09e763653 100644
--- a/tests/performance/Perf.Utility.Maths.cs
+++ b/tests/performance/Perf.Utility.StdDev.cs
@@ -3,7 +3,7 @@ namespace Performance;
// INTERNAL UTILITIES
[ShortRunJob]
-public class UtilityMaths
+public class UtilityStdDev
{
[Params(20, 50, 250, 1000)]
public int Periods;