diff --git a/UnitsNet.Tests/GenericMathExtensionsTests.cs b/UnitsNet.Tests/GenericMathExtensionsTests.cs index 820f338f0a..a35bf44e47 100644 --- a/UnitsNet.Tests/GenericMathExtensionsTests.cs +++ b/UnitsNet.Tests/GenericMathExtensionsTests.cs @@ -2,6 +2,8 @@ // Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). 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 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 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 values = []; + + Assert.Throws(() => GenericMathExtensions.Average(values)); + } + + [Fact] + public void Average_OneQuantity_ReturnsTheSameQuantity() + { + IEnumerable values = [Length.FromCentimeters(100)]; + + Length averageOfQuantities = GenericMathExtensions.Average(values); + + Assert.Equal(Length.FromCentimeters(100), averageOfQuantities); + } + + [Fact] + public void Average_TwoQuantities_ReturnsTheExpectedAverage() + { + IEnumerable values = [Length.FromCentimeters(100), Length.FromCentimeters(200)]; + + Length averageOfQuantities = GenericMathExtensions.Average(values); + + Assert.Equal(Length.FromCentimeters(150), averageOfQuantities); } } diff --git a/UnitsNet/GenericMath/GenericMathExtensions.cs b/UnitsNet/GenericMath/GenericMathExtensions.cs index 9ef41a69e4..2cd4c2623f 100644 --- a/UnitsNet/GenericMath/GenericMathExtensions.cs +++ b/UnitsNet/GenericMath/GenericMathExtensions.cs @@ -2,67 +2,81 @@ // Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). 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; /// -/// Provides generic math operations to test out the new generic math interfaces implemented in .NET7 for UnitsNet -/// quantities using 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. /// public static class GenericMathExtensions { /// - /// Returns the sum of values. + /// Returns the sum of a sequence of vector quantities, such as Mass and Length. /// + /// The values. + /// The type of the quantity elements in the source sequence. + /// The sum of the quantities, using the unit of the first element in the sequence. /// - /// This method is experimental and intended to test out the new generic math interfaces implemented in .NET7 for - /// UnitsNet quantities.
- /// Generic math interfaces might replace .
- /// 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: /// - /// API Proposal: Generic LINQ Numeric Operators · Issue #64031 · dotnet/runtime + /// API Proposal: Generic LINQ Numeric Operators · Issue + /// #64031 · dotnet/runtime /// ///
- /// The values. - /// The type of value. - /// The sum. - public static T Sum(this IEnumerable source) - where T : IAdditionOperators, IAdditiveIdentity + public static TQuantity Sum(this IEnumerable source) + where TQuantity : IQuantity, IAdditionOperators, IAdditiveIdentity { - // 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 e = source.GetEnumerator(); + if (!e.MoveNext()) + { + return TQuantity.AdditiveIdentity; + } + + TQuantity result = e.Current; + while (e.MoveNext()) + { + result += e.Current; + } + + return result; } /// - /// Returns the average of values. + /// Calculates the arithmetic average of a sequence of vector quantities, such as Mass and Length. /// + /// The values. + /// The type of the quantity elements in the source sequence. + /// The average of the quantities, using the unit of the first element in the sequence. + /// Thrown when the sequence is empty. /// - /// This method is experimental and intended to test out the new generic math interfaces implemented in .NET7 for - /// UnitsNet quantities.
- /// Generic math interfaces might replace .
- /// 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: /// /// API Proposal: Generic LINQ Numeric Operators · Issue /// #64031 · dotnet/runtime /// ///
- /// The values. - /// The value type. - /// The average. - public static T Average(this IEnumerable source) - where T : IAdditionOperators, IAdditiveIdentity, IDivisionOperators + public static TQuantity Average(this IEnumerable source) + where TQuantity : IQuantity, IAdditionOperators, IAdditiveIdentity, + IDivisionOperators { - // 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 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