Skip to content

Commit

Permalink
Merge branch 'v3' into 1062-idea-increment-approach-for-adx
Browse files Browse the repository at this point in the history
  • Loading branch information
DaveSkender authored Nov 11, 2024
2 parents b4d724e + a034f48 commit 54b391b
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 16 deletions.
45 changes: 30 additions & 15 deletions src/_common/Math/NullMath.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Runtime.CompilerServices;

namespace Skender.Stock.Indicators;

/// <summary>
/// Nullable System.<see cref="Math"/> functions.
/// Nullable <c>System.<see cref="Math"/></c> functions.
/// </summary>
/// <remarks>
/// <c>System.Math</c> infamously does not allow
Expand All @@ -16,48 +18,57 @@ public static class NullMath
/// </summary>
/// <param name="value">The nullable double value.</param>
/// <returns>The absolute value, or null if the input is null.</returns>
[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;

/// <summary>
/// Rounds a nullable decimal value to a specified number of fractional digits.
/// </summary>
/// <param name="value">The nullable decimal value.</param>
/// <param name="digits">The number of fractional digits.</param>
/// <returns>The rounded value, or null if the input is null.</returns>
[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;

/// <summary>
/// Rounds a nullable double value to a specified number of fractional digits.
/// </summary>
/// <param name="value">The nullable double value.</param>
/// <param name="digits">The number of fractional digits.</param>
/// <returns>The rounded value, or null if the input is null.</returns>
[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;

/// <summary>
/// Rounds a double value to a specified number of fractional digits.
/// It is an extension alias of <see cref="Math.Round(double, int)"/>
/// </summary>
/// <param name="value">The double value.</param>
/// <param name="digits">The number of fractional digits.</param>
/// <returns>The rounded value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Round(this double value, int digits)
=> Math.Round(value, digits);

/// <summary>
/// Rounds a decimal value to a specified number of fractional digits.
/// It is an extension alias of <see cref="Math.Round(decimal, int)"/>
/// </summary>
/// <param name="value">The decimal value.</param>
/// <param name="digits">The number of fractional digits.</param>
/// <returns>The rounded value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static decimal Round(this decimal value, int digits)
=> Math.Round(value, digits);

Expand All @@ -66,26 +77,29 @@ public static decimal Round(this decimal value, int digits)
/// </summary>
/// <param name="value">The nullable double value.</param>
/// <returns>The value, or NaN if the input is null.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Null2NaN(this double? value)
=> value ?? double.NaN;
=> value.GetValueOrDefault(double.NaN);

/// <summary>
/// Converts a nullable decimal value to NaN if it is null.
/// </summary>
/// <param name="value">The nullable decimal value.</param>
/// <returns>The value as a double, or NaN if the input is null.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Null2NaN(this decimal? value)
=> value is null
? double.NaN
: (double)value;
=> value.HasValue
? (double)value.GetValueOrDefault()
: double.NaN;

/// <summary>
/// Converts a nullable double value to null if it is NaN.
/// </summary>
/// <param name="value">The nullable double value.</param>
/// <returns>The value, or null if the input is NaN.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double? NaN2Null(this double? value)
=> value is double.NaN
=> value.HasValue && double.IsNaN(value.GetValueOrDefault())
? null
: value;

Expand All @@ -94,6 +108,7 @@ public static double Null2NaN(this decimal? value)
/// </summary>
/// <param name="value">The double value.</param>
/// <returns>The value, or null if the input is NaN.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double? NaN2Null(this double value)
=> double.IsNaN(value)
? null
Expand Down
72 changes: 72 additions & 0 deletions tests/indicators/_common/Math/NullMath.Tests.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
64 changes: 64 additions & 0 deletions tests/performance/Perf.Utility.NullMath.cs
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ namespace Performance;
// INTERNAL UTILITIES

[ShortRunJob]
public class UtilityMaths
public class UtilityStdDev
{
[Params(20, 50, 250, 1000)]
public int Periods;
Expand Down

0 comments on commit 54b391b

Please sign in to comment.