Skip to content

Experimental: Generic Math and INumber

Andreas Gullberg Larsen edited this page Feb 17, 2023 · 2 revisions

Generic math was introduced in .NET 7.
https://learn.microsoft.com/en-us/dotnet/standard/generics/math

We wanted to see what works and what doesn't for Units.NET, so we added some experimental support for the generic math interfaces in
Generic math for UnitsNet in .NET 7 · Pull Request #1164.

What can you do

Sum and Average, for now.

    [Fact]
    public void CanCalcSum()
    {
        Length[] values = { Length.FromCentimeters(100), Length.FromCentimeters(200) };


        Assert.Equal(Length.FromCentimeters(300), values.Sum());
    }


    [Fact]
    public void CanCalcAverage_ForQuantitiesWithDoubleValueType()
    {
        Length[] values = { Length.FromCentimeters(100), Length.FromCentimeters(200) };


        Assert.Equal(Length.FromCentimeters(150), values.Average());
    }

It seems there are no implementations shipped with .NET yet, so we provide these two extension methods as a proof of concept. We can add more if there is a need for it.

https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GenericMath/GenericMathExtensions.cs

    public static T Sum<T>(this IEnumerable<T> source)
        where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>
    {
        // 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);
    }

    public static T Average<T>(this IEnumerable<T> source)
        where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>, IDivisionOperators<T, double, T>
    {
        // 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));


        return result.value / result.count;
    }

Some quirks so far

INumber.Min/Max not well defined

UnitsNet does not provide Min/Max values for quantities, since you quickly run into overflow exceptions when converting to other units. Also the Min/Max could change when introducing bigger/smaller units.

IAdditiveIdentity (Zero) not intuitive for quantities like Temperature

Temperature has its own quirks with arithmetic in general.

0 Celsius != 0 Fahrenheit != 0 Kelvin.

So for example 20 °C + 5 °C is ambiguous. It could mean 25 °C, or it could mean 293.15 K + 278.15 K.

This made it hard to implement IAdditiveIdentity in a way that is intuitive, which is essential to arithmetic like Average().

Clone this wiki locally