Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API Proposal]: Add more granular Interfaces to System.INumber<T> #65807

Closed
SpocWeb opened this issue Feb 23, 2022 · 3 comments
Closed

[API Proposal]: Add more granular Interfaces to System.INumber<T> #65807

SpocWeb opened this issue Feb 23, 2022 · 3 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Numerics

Comments

@SpocWeb
Copy link

SpocWeb commented Feb 23, 2022

Background and motivation

I have developed extensive Math Libraries for the last 30 years in Java, C++ and C# and I am very happy about your native Types to implement the common Interfaces of System.INumber< T>.

Unfortunately it implements all binary XxxOperators< T,T,T> with the same Type.

I have found it useful in my work to define many intermediate Interfaces that model mathematical Semi-Groups, Groups, Rings, Fields and ultimately Scalar Numbers, i.e. INumber< T>.

In fitting your new C#11 APIs to my Designs I have found the Need for at least these two new, intermediate Interfaces:

  • INumber<TSelf, TResult> and
  • IScalarNumber<TSelf, TResult>

This also results in a simpler Definition of INumber< T> and the opportunity to apply these Interfaces to Complex and Quaternion for many Algorithms (e.g. linear Algebra):

public interface INumber<TSelf> : IScalarNumber<TSelf, TSelf> {  }

public struct Complex : INumber<Complex,Complex> { ... }

public struct Quaternion : INumber<Quaternion,Quaternion> { ... }

The lesser specialized TResult Type allows to return the 'smallest' Type and keep Operations efficient.
You could e.g. subtract two Complex Numbers with same imaginary Components and return a double instead of another Complex Number with 0 imaginary Component.

I would like to start a Pull-Request with the Definition. In Fact I have already implement them e.g. in System.Numerics.Complex and in System.Numerics.Quaternion as displayed below.

API Proposal

namespace System
{
	/// <summary> General 'Number' without total Order, including <see cref="Complex"/>, <see cref="Quaternion"/> etc. </summary>
	public interface INumber<TSelf, That> : IEqualityOperators<TSelf, That>,
		IAdditionOperators<TSelf, That, That>,
		IAdditiveIdentity<TSelf, That>,
		ISubtractionOperators<TSelf, That, That>,

		IDecrementOperators<TSelf>,
		IIncrementOperators<TSelf>,

		IMultiplicativeIdentity<TSelf, That>,
		IMultiplyOperators<TSelf, That, That>,
		IDivisionOperators<TSelf, That, That>,

		IModulusOperators<TSelf, That, That>,

		ISpanFormattable,
		ISpanParseable<TSelf>,

		IUnaryNegationOperators<TSelf, That>,
		IUnaryPlusOperators<TSelf, That>

		where TSelf : INumber<TSelf, That>
	{
		static abstract TSelf One { get; }

		static abstract TSelf Zero { get; }

		static abstract (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right);

		static abstract That Create<TOther>(TOther value) where TOther : INumber<TOther>;

		static abstract bool TryCreate<TOther>(TOther value, out That result) where TOther : INumber<TOther>;

		static abstract That Parse(string s, NumberStyles style, IFormatProvider? provider);

		static abstract That Parse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider);

		static abstract bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out That result);

		static abstract bool TryParse(
			ReadOnlySpan<char> s,
			NumberStyles style,
			IFormatProvider? provider,
			out That result);
	}

	/// <summary> Number with total Order => Min, Max and Sign are defined </summary>
	public interface IScalarNumber<TSelf, That> : INumber<TSelf, That>, IComparisonOperators<TSelf, That>
		where TSelf : IScalarNumber<TSelf, That>
	{

		static abstract TSelf Max(TSelf x, TSelf y);

		static abstract TSelf Min(TSelf x, TSelf y);

		static abstract TSelf Abs(TSelf value);

		static abstract TSelf Sign(TSelf value);

		static abstract TSelf Clamp(TSelf value, TSelf min, TSelf max);

		static abstract TSelf CreateSaturating<TOther>(TOther value) where TOther : INumber<TOther>;

		static abstract TSelf CreateTruncating<TOther>(TOther value) where TOther : INumber<TOther>;

	}

	/// <summary> Existing INumber{T} can be defined in these Terms without any Changes: </summary>
	public interface INumber<TSelf> : IScalarNumber<TSelf, TSelf> { }
}

IOperations

API Usage

Existing API would be unchanged, but Developers would be able to define generic Algorithms e.g. for Complex and Quaternion.

public struct Complex : INumber<Complex,Complex> { ... }

public struct Quaternion : INumber<Quaternion,Quaternion> { ... }

I have actually working Implementations of Complex and Quaternion Numbers based on this Design.

Alternative Designs

I would propose an even more granular Design that involves additive and multiplicative Semi-Groups, Groups, Rings and Fields to align closer with long-standing mathematical Models, if you are ready to consider this.

This would greatly increase the usefulness for Library Developers, because they have fewer Methods to implement and a more granular Framework to embed their Designs.

It does not the affect long-term Evolvability, because these are stable and proven mathematical Structures.

Risks

None, the API is in Preview and this Design is inspired from a long History of Math Library Development.

@SpocWeb SpocWeb added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Feb 23, 2022
@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Numerics untriaged New issue has not been triaged by the area owner labels Feb 23, 2022
@ghost
Copy link

ghost commented Feb 23, 2022

Tagging subscribers to this area: @dotnet/area-system-numerics
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and motivation

System.INumber implements all binary Operators<T,T,T> with the same Type.
I have developed extensive Math Libraries for the last 30 years in Java, C++ and C#
and have found it useful to define many intermediate Interfaces that model mathematical Semi-Groups, Groups, Rings, Fields and ultimately Scalar Numbers, i.e. INumber.

In fitting your new C#11 APIs to this Design I have found the Need for at least these two new Interfaces:

  • INumber<TSelf, TResult> and
  • IScalarNumber<TSelf, TResult>

API Proposal

namespace System
{
	/// <summary> General 'Number' without total Order, including <see cref="Complex"/>, <see cref="Quaternion"/> etc. </summary>
	public interface INumber<TSelf, That> :
		IAdditionOperators<TSelf, That, That>,
		IAdditiveIdentity<TSelf, That>,
		ISubtractionOperators<TSelf, That, That>,

		IDecrementOperators<TSelf>,
		IIncrementOperators<TSelf>,

		IMultiplicativeIdentity<TSelf, That>,
		IMultiplyOperators<TSelf, That, That>,
		IDivisionOperators<TSelf, That, That>,

		IModulusOperators<TSelf, That, That>,

		ISpanFormattable,
		ISpanParseable<TSelf>,

		IUnaryNegationOperators<TSelf, That>,
		IUnaryPlusOperators<TSelf, That>

		where TSelf : INumber<TSelf, That>
	{
		static abstract TSelf One { get; }

		static abstract TSelf Zero { get; }

		static abstract TSelf Create<TOther>(TOther value) where TOther : INumber<TOther>;

		static abstract bool TryCreate<TOther>(TOther value, out TSelf result) where TOther : INumber<TOther>;

		static abstract (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right);

		static abstract TSelf Parse(string s, NumberStyles style, IFormatProvider? provider);

		static abstract TSelf Parse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider);

		static abstract bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out TSelf result);

		static abstract bool TryParse(
			ReadOnlySpan<char> s,
			NumberStyles style,
			IFormatProvider? provider,
			out TSelf result);
	}

	/// <summary> Number with total Order => Min, Max and Sign are defined </summary>
	public interface IScalarNumber<TSelf, That> : INumber<TSelf, That>, IComparisonOperators<TSelf, That>
		where TSelf : IScalarNumber<TSelf, That>
	{

		static abstract TSelf Max(TSelf x, TSelf y);

		static abstract TSelf Min(TSelf x, TSelf y);

		static abstract TSelf Abs(TSelf value);

		static abstract TSelf Sign(TSelf value);

		static abstract TSelf Clamp(TSelf value, TSelf min, TSelf max);

		static abstract TSelf CreateSaturating<TOther>(TOther value) where TOther : INumber<TOther>;

		static abstract TSelf CreateTruncating<TOther>(TOther value) where TOther : INumber<TOther>;

	}

	/// <summary> Existing INumber{T} can be defined in these Terms without any Changes: </summary>
	public interface INumber<TSelf> : IScalarNumber<TSelf, TSelf> { }
}

API Usage

Usage would be unchanged, but Developers would be able to define generic Algorithms e.g. for Complex and Quaternion.

public struct Complex : INumber<Complex,Complex> { ... }

public struct Quaternion : INumber<Quaternion,Quaternion> { ... }

Alternative Designs

I would propose an even more granular Design that involves additive and multiplicative Semi-Groups, Groups, Rings and Fields to align closer with long-standing mathematical Models, but that may be harder to communicate.

Risks

None, the API is in Preview and this Design is inspired from a long History of Math Library Development.

Author: SpocWeb
Assignees: -
Labels:

api-suggestion, area-System.Numerics, untriaged

Milestone: -

@tannergooding
Copy link
Member

Thanks for your interest in this space, the current design is being covered in the dotnet/designs repo and there is an active work-in-progress PR that is covering some appropriate changes: dotnet/designs#257

I have found it useful in my work to define many intermediate Interfaces that model mathematical Semi-Groups, Groups, Rings, Fields and ultimately Scalar Numbers, i.e. INumber< T>.

The current design is explicitly not looking at or considering groups/rings/fields/etc. There are a lot of expectations around these terms and many of the real-world use cases don't work well with them as the contract required becomes too strict and doesn't integrate well with a variety of languages. The terms themselves, while making sense from a mathematical perspective, are also highly conflicting in the more general programming space.

Instead, the design is targeting a set of interfaces and definitions that map well to .NET, the broader framework design guidelines, and considering scenarios around common programming patterns encountered in languages like C#, F#, and other languages where it is expected to be used.

In fitting your new C#11 APIs to my Designs I have found the Need for at least these two new, intermediate Interfaces:
INumber<TSelf, TResult> and
IScalarNumber<TSelf, TResult>

The proposed update will be splitting the current INumber interface into a couple different interfaces, namely INumberBase (name not finalized) and INumber. The former represents the broader number base that is also usable by things like Complex.

Notably, there are some practical problems that come about from trying to represent TResult as different for something as broad as INumber where it's not some "simple" operation. This primarily surfaces in usability of the types and conversions between input/targets. It also surfaces in how APIs are typically designed where it is incredibly uncommon for something like Complex.

When considering single operations (like IAdditionOperators), it becomes possible/desirable to model the type that defines the operation, the secondary type used for the operation, and the result type. Most frequently 2/3 of the types will be the same and it's generally expected that will always be the case. However, which two match will often differ, sometimes its TSelf = TSelf op TOther and other times its TOther = TSelf op TOther and so all three need to be specifiable to account for both scenarios.

However, when considering more complex scenarios, like Complex, it becomes impractical to chain together many operations and the expected scenario for exposed APIs is that the result type for a number is itself. So Complex operations take and return Complex. Scenarios like Complex then have an additional consideration in that it is a number type that is composed of two different values (a real value and an imaginary value). Due to this, it is quite a bit different from the typical things you might thing of as "scalar" values and some of the additional operations it supports, such as Complex * double would need to be represented via their own interface. Whether this interface is something more generic or specific to Complex is to be determined and input is welcome. -- A similar consideration will exist for Vector2, Vector3, Vector4, etc. A similar consideration may even exist for things like a Rational type where from a mathematical perspective its a "single value", but from a programmatic perspective it is two distinct fields and there are optimizations and other considerations to be had in how data is exposed.

@SpocWeb
Copy link
Author

SpocWeb commented Mar 2, 2022

Thank you for this Info. I wasn't aware of the discussions.
I know I'm late to the party but just did not want to miss the chance to add some ideas that worked well for me.
I'll have a look at the Discussion for now.

@ghost ghost locked as resolved and limited conversation to collaborators Apr 3, 2022
@tannergooding tannergooding removed the untriaged New issue has not been triaged by the area owner label Jun 24, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Numerics
Projects
None yet
Development

No branches or pull requests

2 participants