-
Notifications
You must be signed in to change notification settings - Fork 385
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Constrained the GenericMathExtensions to IQuantity (#1448)
- replaced the aggregation with an iterator based version (slightly faster) - the Average extension now throws an `InvalidOperationException` when the source is empty
- Loading branch information
Showing
2 changed files
with
95 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,8 @@ | |
// Copyright 2013 Andreas Gullberg Larsen ([email protected]). Maintained at https://github.com/angularsen/UnitsNet. | ||
|
||
#if NET7_0_OR_GREATER | ||
using System; | ||
using System.Collections.Generic; | ||
using UnitsNet.GenericMath; | ||
using Xunit; | ||
|
||
|
@@ -10,19 +12,59 @@ namespace UnitsNet.Tests; | |
public class GenericMathExtensionsTests | ||
{ | ||
[Fact] | ||
public void CanCalcSum() | ||
public void Sum_Empty_ReturnsTheAdditiveIdentity() | ||
{ | ||
Length[] values = { Length.FromCentimeters(100), Length.FromCentimeters(200) }; | ||
Length[] values = []; | ||
|
||
Assert.Equal(Length.FromCentimeters(300), values.Sum()); | ||
Assert.Equal(Length.Zero, GenericMathExtensions.Sum(values)); | ||
} | ||
|
||
[Fact] | ||
public void CanCalcAverage_ForQuantitiesWithDoubleValueType() | ||
public void Sum_OneQuantity_ReturnsTheSameQuantity() | ||
{ | ||
Length[] values = { Length.FromCentimeters(100), Length.FromCentimeters(200) }; | ||
IEnumerable<Length> values = [Length.FromCentimeters(100)]; | ||
|
||
Assert.Equal(Length.FromCentimeters(150), values.Average()); | ||
Length sumOfQuantities = GenericMathExtensions.Sum(values); | ||
|
||
Assert.Equal(Length.FromCentimeters(100), sumOfQuantities); | ||
} | ||
|
||
[Fact] | ||
public void Sum_TwoQuantities_ReturnsTheExpectedSum() | ||
{ | ||
IEnumerable<Length> values = [Length.FromCentimeters(100), Length.FromCentimeters(200)]; | ||
|
||
Length sumOfQuantities = GenericMathExtensions.Sum(values); | ||
|
||
Assert.Equal(Length.FromCentimeters(300), sumOfQuantities); | ||
} | ||
|
||
[Fact] | ||
public void Average_Empty_ThrowsInvalidOperationException() | ||
{ | ||
IEnumerable<Length> values = []; | ||
|
||
Assert.Throws<InvalidOperationException>(() => GenericMathExtensions.Average(values)); | ||
} | ||
|
||
[Fact] | ||
public void Average_OneQuantity_ReturnsTheSameQuantity() | ||
{ | ||
IEnumerable<Length> values = [Length.FromCentimeters(100)]; | ||
|
||
Length averageOfQuantities = GenericMathExtensions.Average(values); | ||
|
||
Assert.Equal(Length.FromCentimeters(100), averageOfQuantities); | ||
} | ||
|
||
[Fact] | ||
public void Average_TwoQuantities_ReturnsTheExpectedAverage() | ||
{ | ||
IEnumerable<Length> values = [Length.FromCentimeters(100), Length.FromCentimeters(200)]; | ||
|
||
Length averageOfQuantities = GenericMathExtensions.Average(values); | ||
|
||
Assert.Equal(Length.FromCentimeters(150), averageOfQuantities); | ||
} | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,67 +2,81 @@ | |
// Copyright 2013 Andreas Gullberg Larsen ([email protected]). Maintained at https://github.com/angularsen/UnitsNet. | ||
|
||
#if NET7_0_OR_GREATER | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Numerics; | ||
|
||
namespace UnitsNet.GenericMath; | ||
|
||
/// <summary> | ||
/// Provides generic math operations to test out the new generic math interfaces implemented in .NET7 for UnitsNet | ||
/// quantities using <see cref="double" /> as the internal value type, which is the majority of quantities. | ||
/// Provides generic math operations using the generic math interfaces implemented in .NET7 for UnitsNet. | ||
/// </summary> | ||
public static class GenericMathExtensions | ||
{ | ||
/// <summary> | ||
/// Returns the sum of values. | ||
/// Returns the sum of a sequence of vector quantities, such as Mass and Length. | ||
/// </summary> | ||
/// <param name="source">The values.</param> | ||
/// <typeparam name="TQuantity">The type of the quantity elements in the source sequence.</typeparam> | ||
/// <returns>The sum of the quantities, using the unit of the first element in the sequence.</returns> | ||
/// <remarks> | ||
/// This method is experimental and intended to test out the new generic math interfaces implemented in .NET7 for | ||
/// UnitsNet quantities.<br /> | ||
/// Generic math interfaces might replace <see cref="UnitMath" />.<br /> | ||
/// Generic math LINQ support is still missing in the BCL, but is being worked on: | ||
/// Note that the generic math LINQ support is still missing in the BCL, but is being worked on: | ||
/// <a href="https://github.com/dotnet/runtime/issues/64031"> | ||
/// API Proposal: Generic LINQ Numeric Operators · Issue #64031 · dotnet/runtime | ||
/// API Proposal: Generic LINQ Numeric Operators · Issue | ||
/// #64031 · dotnet/runtime | ||
/// </a> | ||
/// </remarks> | ||
/// <param name="source">The values.</param> | ||
/// <typeparam name="T">The type of value.</typeparam> | ||
/// <returns>The sum.</returns> | ||
public static T Sum<T>(this IEnumerable<T> source) | ||
where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T> | ||
public static TQuantity Sum<TQuantity>(this IEnumerable<TQuantity> source) | ||
where TQuantity : IQuantity, IAdditionOperators<TQuantity, TQuantity, TQuantity>, IAdditiveIdentity<TQuantity, TQuantity> | ||
{ | ||
// Put accumulator on right hand side of the addition operator to construct quantities with the same unit as the values. | ||
// The addition operator implementation picks the unit from the left hand side, and the additive identity (e.g. Length.Zero) is always the base unit. | ||
return source.Aggregate(T.AdditiveIdentity, (acc, item) => item + acc); | ||
using IEnumerator<TQuantity> e = source.GetEnumerator(); | ||
if (!e.MoveNext()) | ||
{ | ||
return TQuantity.AdditiveIdentity; | ||
} | ||
|
||
TQuantity result = e.Current; | ||
while (e.MoveNext()) | ||
{ | ||
result += e.Current; | ||
} | ||
|
||
return result; | ||
} | ||
|
||
/// <summary> | ||
/// Returns the average of values. | ||
/// Calculates the arithmetic average of a sequence of vector quantities, such as Mass and Length. | ||
/// </summary> | ||
/// <param name="source">The values.</param> | ||
/// <typeparam name="TQuantity">The type of the quantity elements in the source sequence.</typeparam> | ||
/// <returns>The average of the quantities, using the unit of the first element in the sequence.</returns> | ||
/// <exception cref="InvalidOperationException">Thrown when the sequence is empty.</exception> | ||
/// <remarks> | ||
/// This method is experimental and intended to test out the new generic math interfaces implemented in .NET7 for | ||
/// UnitsNet quantities.<br /> | ||
/// Generic math interfaces might replace <see cref="UnitMath" />.<br /> | ||
/// Generic math LINQ support is still missing in the BCL, but is being worked on: | ||
/// Note that the generic math LINQ support is still missing in the BCL, but is being worked on: | ||
/// <a href="https://github.com/dotnet/runtime/issues/64031"> | ||
/// API Proposal: Generic LINQ Numeric Operators · Issue | ||
/// #64031 · dotnet/runtime | ||
/// </a> | ||
/// </remarks> | ||
/// <param name="source">The values.</param> | ||
/// <typeparam name="T">The value type.</typeparam> | ||
/// <returns>The average.</returns> | ||
public static T Average<T>(this IEnumerable<T> source) | ||
where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>, IDivisionOperators<T, double, T> | ||
public static TQuantity Average<TQuantity>(this IEnumerable<TQuantity> source) | ||
where TQuantity : IQuantity, IAdditionOperators<TQuantity, TQuantity, TQuantity>, IAdditiveIdentity<TQuantity, TQuantity>, | ||
IDivisionOperators<TQuantity, double, TQuantity> | ||
{ | ||
// Put accumulator on right hand side of the addition operator to construct quantities with the same unit as the values. | ||
// The addition operator implementation picks the unit from the left hand side, and the additive identity (e.g. Length.Zero) is always the base unit. | ||
(T value, int count) result = source.Aggregate( | ||
(value: T.AdditiveIdentity, count: 0), | ||
(acc, item) => (value: item + acc.value, count: acc.count + 1)); | ||
using IEnumerator<TQuantity> e = source.GetEnumerator(); | ||
if (!e.MoveNext()) | ||
{ | ||
throw new InvalidOperationException("Sequence contains no elements"); | ||
} | ||
|
||
TQuantity result = e.Current; | ||
var nbQuantities = 1; | ||
while (e.MoveNext()) | ||
{ | ||
result += e.Current; | ||
nbQuantities++; | ||
} | ||
|
||
return result.value / result.count; | ||
return result / nbQuantities; | ||
} | ||
} | ||
#endif |