question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[RFC] Stride.Math with the new .NET 7 System.Numerics interfaces

See original GitHub issue

Using the newly added abstract static method for interfaces we can reduce code repetition. This shouldn’t affect anything but number of lines of code for the vector implementations.

Some questions to answer before going forward with it :

  • Is it usable and comfortable ?
  • Is it viable performance wise ?
  • Can we do SIMD with it ?
  • Does it allow inlining ?

Implementation

using System;

namespace Stride.Maths

public struct Double2 : IVector2<Double2, double>
{
    public double X {get;set;}
    public double Y {get;set;}

    public Double2(double value)
    {
        X = value;
        Y = value;
    }
    public Double2(double x, double y)
    {
        X = x;
        Y = y;
    }

    public static double Distance(in Double2 value)
    {
        return (double)Math.Sqrt(value.X * value.X + value.Y * value.Y);
    }

    public static Double2 New(double value)
    {
        return new(value);
    }

    public static Double2 New(double x, double y)
    {
        return new(x,y);
    }

    public static void SquareRoot(in Double2 value, out Double2 result)
    {
        result = new((double)Math.Sqrt(value.X), (double)Math.Sqrt(value.Y));
    }
}

public struct Half2 : IVector2<Half2, Half>
{
    public Half X {get;set;}
    public Half Y {get;set;}

    public Half2(Half value)
    {
        X = value;
        Y = value;
    }
    public Half2(Half x, Half y)
    {
        X = x;
        Y = y;
    }

    public static Half Distance(in Half2 value)
    {
        return (Half)MathF.Sqrt((float)(value.X * value.X + value.Y * value.Y));
    }

    public static Half2 New(Half value)
    {
        return new(value);
    }

    public static Half2 New(Half x, Half y)
    {
        return new(x,y);
    }

    public static void SquareRoot(in Half2 value, out Half2 result)
    {
        result = new((Half)MathF.Sqrt((float)value.X), (Half)MathF.Sqrt((float)value.Y));
    }
}

Interface

using System.Numerics;

internal interface IVector2<T, Num>
    where T :
        struct,
        IVector2<T, Num>
    where Num : INumber<Num>
{
    public Num X { get; set; }
    public Num Y { get; set; }
    public T One => T.New(Num.One);
    public T Zero => T.New(Num.Zero);

    public static abstract T New(Num value);
    public static abstract T New(Num x, Num y);
    public static virtual void Abs(in T value, out T result)
    {
        result = T.New(Num.Abs(value.X), Num.Abs(value.Y));
    }

    public static virtual void Add(in T left, Num scalar, out T result)
    {
        result = T.New(
            left.X + scalar,
            left.Y + scalar
        );
    }
    public static virtual void Add(in T left, in T right, out T result)
    {
        result = T.New(
            left.X + right.X,
            left.Y + right.Y
        );
    }

    public static virtual void Clamp(in T value, in T min, in T max, out T result)
    {
        result = T.New(
            Num.MaxMagnitude(Num.MinMagnitude(value.X,max.X), min.X),
            Num.MaxMagnitude(Num.MinMagnitude(value.Y,max.Y), min.Y)
        );
    }
    public static abstract Num Distance(in T value);
    public static virtual Num DistanceSquared(in T value)
    {
        return value.X * value.X + value.Y * value.Y;
    }
    public static virtual void Divide(Num scalar, in T value, out T result)
    {
        result = T.New(
            scalar / value.X,
            scalar / value.Y
        );
    }
    public static virtual void Divide(in T value, Num scalar, out T result)
    {
        result = T.New(
            value.X / scalar,
            value.Y / scalar
        );
    }
    public static virtual void Divide(in T left, in T right, out T result)
    {
        result = T.New(
            left.X / right.X,
            left.Y / right.Y
        );
    }
    public static virtual Num Dot(in T left, in T right)
    {
        return left.X * right.X + left.Y * right.Y;
    }
    public static virtual void Lerp(in T value1, in T value2, Num amount, out T result)
    {
        T.Subtract(value2, value1, out var sub);
        T.Multiply(sub, amount, out var mul);
        T.Add(value1, mul, out result);
    }
    public static virtual void Multiply(in T value, Num scalar, out T result)
    {
        result = T.New(
            value.X * scalar,
            value.Y * scalar
        );
    }
    public static virtual void Multiply(in T left, in T right, out T result)
    {
        result = T.New(
            left.X * right.X,
            left.Y * right.Y
        );
    }
    public static virtual void Negate(in T value, out T result)
    {
        result = T.New(
            -value.X,
            -value.Y
        );
    }
    public static virtual void Normalize(in T value, out T result)
    {
        var x = value.X;
        var y = value.Y;
        result = T.New(
            x < -Num.One ? -Num.One : x > Num.One ? Num.One : x, 
            x < -Num.One ? -Num.One : x > Num.One ? Num.One : y 
        );
    }
    public static virtual void Reflect(in T vector, in T normal, out T result)
    {
        Num dot = T.Dot(vector,normal);
        result = T.New(
            vector.X - (Num.One + Num.One) * dot * normal.X,
            vector.Y - (Num.One + Num.One) * dot * normal.Y
        );
    }
    public static abstract void SquareRoot(in T value, out T result);
    
    public static virtual void Subtract(in T left, Num scalar, out T result)
    {
        result = T.New(
            left.X - scalar,
            left.Y - scalar
        );
    }
    public static virtual void Subtract(Num scalar, in T left, out T result)
    {
        result = T.New(
            scalar - left.X,
            scalar - left.Y
        );
    }
    public static virtual void Subtract(in T left, in T right, out T result)
    {
        result = T.New(
            left.X - right.X,
            left.Y - right.Y
        );
    }

    public static virtual void Transform(in T vector, in T transform, out T result)
    {
        T.Add(vector,transform, out result);
    }
    // public static virtual void TransformNormal(in T left, in T right, out T result);
}

Issue Analytics

  • State:open
  • Created 10 months ago
  • Reactions:1
  • Comments:5 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
ericwjcommented, Dec 8, 2022

Can I suggest not doing this unless it is part of a replacement for Stride.Math? The current implementation is up to at least 40x slower than necessary and the programming experience with it, and/or the availability of a large amount of duplicated operations is not quite right anymore and this also makes changing the code as opposed to rewriting a lot of work.

1reaction
manio143commented, Dec 4, 2022

Thanks for the reference to those issues. Yeah, I would be definitely for using those static interfaces to simplify things.

Have you seen this reddit question? https://www.reddit.com/r/csharp/comments/ti11e1/can_i_cast_an_inumber_or_similar_to_systemdouble/ You can use double.CreateChecked to cast onto a double and feed it to the Sqrt and then Num.CreateTruncating to convert it back onto a Num or something like that. Though my quick check of the source code didn’t find a conversion between Half and Double (maybe I wasn’t looking in the right place)…

Read more comments on GitHub >

github_iconTop Results From Across the Web

.NET 7 Preview 5 - Generic Math - .NET Blog
NET 7 Preview 5 includes significant improvements to the Generic Math ... IParsable; Moving all other new numeric interfaces to the System.
Read more >
.NET 7 Adds Generic Math
Along with Static Abstract Methods in interfaces comes support for generic math in .NET 7 via interfaces such as INumber .
Read more >
RFC 6716: Definition of the Opus Audio Codec
1. Range Decoder Initialization Let b0 be an 8-bit unsigned integer containing first input byte (or containing zero if there are no bytes...
Read more >
Foundations of python network programming john goerzen pdf
Python has made great strides since Apress released the first edition of this book back in the days of Python 2.3. The advances...
Read more >
2020 – 2021 Florida Department of Education Curriculum ...
This program offers a sequence of courses that provides coherent and rigorous content aligned with challenging academic standards and relevant.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found