-
Notifications
You must be signed in to change notification settings - Fork 161
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
Updating the generic math draft proposal to include changes proposed for .NET 7 #257
Updating the generic math draft proposal to include changes proposed for .NET 7 #257
Conversation
public interface INumberBase<TSelf> | ||
: IAdditionOperators<TSelf, TSelf, TSelf>, | ||
IAdditiveIdentity<TSelf, TSelf>, | ||
IComparisonOperators<TSelf, TSelf>, // implies IEqualityOperators<TSelf, TSelf> | ||
IEqualityOperators<TSelf, TSelf>, // implies IEquatable<TSelf> | ||
IMultiplyOperators<TSelf, TSelf>, | ||
ISubtractionOperators<TSelf, TSelf, TSelf>, | ||
IUnaryPlusOperators<TSelf, TSelf> | ||
where TSelf : INumberBase<TSelf> | ||
{ | ||
// Alias for AdditiveIdentity | ||
static abstract TSelf Zero { get; } | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For right now, this effectively mirrors the Numerics
protocol in Swift
. Noting that the missing bits would be:
TMagnitude Magnitude { get; }
as there are no associated types and soTSelf Abs(TSelf value)
is the closest alternative (https://developer.apple.com/documentation/swift/numeric/2884876-magnitude)TSelf init(IntegerLiteralType)
, again due to no associated types (https://developer.apple.com/documentation/swift/expressiblebyintegerliteral)TSelf? init(TBinaryInteger)
, as there are some open questions here that were raised on Adding a rough draft of the "minimum viable product" for the .NET Libraries APIs to support generic math #205 (https://developer.apple.com/documentation/swift/numeric/2886795-init)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going to call out each of the remaining interfaces/members on INumber
and discuss why they exist. People should weigh in on why they shouldn't be on INumberBase
(name is a placeholder atm).
First lets cover the ones that make sense to leave off:
IComparisonOperators
- This one actually makes sense to leave off as it can't be supported by things likeComplex
IModulusOperators
- This one probably makes sense to leave off as it doesn't make a lot of sense forComplex
The ones that probably make sense to have:
IDecrementOperators
- This is, for many reasons, simplyx - 1
. It is a general nicety in .NET and something often overlookedIDivisionOperators
- Complex numbers support division. What scenarios are people wanting where division isn't possible but something is still a "number"IIncrementOperators
- This is, for many reasons, simplyx + 1
. It is a general nicety in .NET and something often overlookedIMultiplicativeIdentity
- Complex numbers have a multiplicative identity and it is1
IUnaryNegationOperators
- This is logicallyZero - x
andINumberBase
supports subtraction, zero, and the additive identity
The "controversial" ones:
ISpanFormattable
- All types supportToString
. Supporting culture aware formatting of your data is a "best practice" and helps support globally aware apps. In the worst case, you implement this as=> ToString();
ISpanParseable
- Parsing can get complex in some cases but it is likewise a "best practice" for number types to support something built in. This helps support all kinds of programs, roundtripping of the format strings, and both culture aware/invariant data processing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are also some individual APIs:
Abs
- Potentially makes sense, would be "inefficient" for types likeComplex
DimRem
- Doesn't make sense withoutIModulusOperators
Max
,Min
, andClamp
- Doesn't make sense for types withoutIComparisonOperators
Sign
- May or may not make sense, it can get complicated when dealing with things likeComplex
on what the sign is
Also there is: Create
, CreateSaturating
, and CreateTruncating
. As on #205 these exist because there are a lot of scenarios that get cut off when you can't convert from a T
to a U
. For example, you can't correctly account for or handle overflow or take in user-defined values.
You'd have to specialize on each and every T
you want to support (keeping in mind overloading on generic constraints isn't possible) and ultimately throw anyways if you can't get the data you need back out. For something like Swift
, you can only create a number from a BinaryInteger
and so you can't for example go from float->double
or float->int
, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Abs
is actually the same as Magnitude
. It makes sense for Complex
, but returns a double
(which ought to be be an associated type). It can be made to return Complex for complex as well (akin to the function Trunc
which returns double
and not int
for double
argument).
I would not include Abs
into the general INumber
or INumberBase
interface, because there are useful number abstractions (like integers modulo n
which cannot define Magnitude in a useful way). If we would include Abs
into INumber
, the users would be unable to represent such structures as an INumber
.
DivRem
is usually considered only for discrete types, but can be naturally extended to doubles (div = Math.Trunc(a / b); rem = a - b * div
) and even to Complex using the same computation (Trunc
on a Complex should internally truncate both components, bringing the original Complex to the 1x1 grid). This way we can support IModulusOperators<T, T, T>
on Complex too, because modulus is defined through Trunc
.
Sign
is actually c / |c|
= a complex with the same argument and magnitude of 1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Abs is actually the same as Magnitude
In the strictest sense of the definition of "magnitude" vs "absolute value", yes. In terms of how I was referring to them above, no.
Abs
in .NET returns the same type as is input and so you have int Abs(int)
and it may fail for int.MinValue
. Where-as Magnitude
in swift returns an associated type which is unsigned for two's complement values and so it would be uint Magnitude { get; }
. We don't have associated types and will most likely not be getting them before generic math officially releases so they should be considered not a possible solution here.
I would not include Abs into the general INumber or INumberBase interface, because there are useful number abstractions (like integers modulo n which cannot define Magnitude in a useful way). If we would include Abs into INumber, the users would be unable to represent such structures as an INumber.
.NET integers already default to two's complement modular arithmetic. They wrap around on overflow by default and so int.MaxValue + 1 == int.MinValue
. I don't believe its worth cutting off absolute value to support increasingly esoteric use cases and instead such scenarios can decide upon a correct behavior for that scenario instead (e.g. Abs(int.MinValue)
will throw; Abs(uint)
just returns itself, etc).
Also noting that swift exposes Magnitude
on its numeric protocol and so likewise doesn't support a scenario where you can't get the absolute value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.NET integers already default to two's complement modular arithmetic. They wrap around on overflow by default and so int.MaxValue + 1 == int.MinValue. I don't believe its worth cutting off absolute value to support increasingly esoteric use cases and instead such scenarios can decide upon a correct behavior for that scenario instead (e.g. Abs(int.MinValue) will throw; Abs(uint) just returns itself, etc).
Well, Abs
is already pointless not only on esoteric types, but actually on more mundane types like unsigned everything. Okay, esoteric types can just implement fine-grained interfaces and not the generic INumberBase
, which solves the problem. But having Abs
on unsigned types feels impure.
From the other point of view, if Abs
returns the same type as the argument, Complex
case is easy (we just return the mathematical magnitude), which is a Complex
itself).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But having Abs on unsigned types feels impure.
Logically such an operation makes sense and it is simply a no-op
, same as it would be for any signed input that is already positive.
In the context of concrete types, you can manually avoid the operation knowing that its always positive.
In the context of generic math, unless you explicitly constrain to ISignedNumber
, then you don't know if the number you have could be negative. You can't overload on generic constraint and duplicating tons of logic just to differ on Abs
isn't an ideal setup anyways; nor is explicitly checking if a type could be negative. Instead, just expose the operation, have it do nothing (just like operator +(TSelf value)
) and let the JIT or other compiler correctly optimize it.
@@ -667,21 +671,30 @@ The numeric interfaces build upon the base interfaces by defining the core abstr | |||
```csharp | |||
namespace System | |||
{ | |||
public interface INumber<TSelf> | |||
public interface INumberBase<TSelf> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that one of the prime examples was to support Complex
, this would likely be something like (given the current setup, not covering potential changes called out in https://github.com/dotnet/designs/pull/257/files#r755361498):
public interface IComplexNumber<TSelf, TNumber>
: IDivisionOperators<TSelf, TSelf, TSelf>,
IMultiplicativeIdentity<TSelf, TSelf>,
INumberBase<TSelf>,
ISpanFormattable<TSelf>,
IUnaryNegationOperators<TSelf, TSelf>
where TSelf : IComplexNumber<TSelf, TNumber>
where TNumber : INumber<TNumber>
{
public static TSelf ImaginaryOne { get; }
public static TSelf Infinity { get; }
public static TSelf Nan { get; }
public static TSelf One { get; }
public TNumber Imaginary { get; }
public TNumber Magnitude { get; }
public TNumber Phase { get; }
public TNumber Real { get; }
public static TNumber Abs(TSelf value);
public static TSelf Conjugate(TSelf value);
// Acos, Asin, Atan
// Cos, Sin, Tan
// Cosh, Sinh, Tanh
// Exp, Log, Log10. Pow
// Reciprocal, Sqrt
// IsFinite, IsInfinity, IsNaN
}
Where struct Complex : IComplexNumber<Complex, double>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't cover things like IAdditionOperators<TSelf, TNumber, TSelf>
It doesn't cover splitting out functions like Sin
/Cos
/Tan
into some interface that can be shared between IComplexNumber
and IFloatingPoint
It doesn't cover creation or conversion APIs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IAdditionOperators<TSelf, TSelf, TNumber>
Did you mean IAdditionOperators<TSelf, TNumber, TSelf>
? Perhaps it would be better to just declare an implicit conversion from TNumber
to TSelf
in IComplexNumber
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean IAdditionOperators<TSelf, TNumber, TSelf>
Yes. Fixed above.
I tagged a few of the most active participants on #205 asking them to give additional feedback here. |
I actually don't like the name And I would add increment and decrement only to |
As covered in the #205, this is effectively a non-starter. What is currently
C#, F#, and other .NET languages already expose and support increment/decrement on the primitive number types. Such operators are already fairly standard as is the behavior they have. |
Why does See also dotnet/runtime#62293 Also On a final note. Is the mixing of textual (parsing, printing text representations) and math/arithmetic concerns in
After giving this some more thought my main concern with the proposed design is not so much that numbers should implement My main concern with the current proposal is that the API is divided between very fine grained Some questions I have when seeing the many "IXXXXOperators" interfaces:
NOTE: Division is a bit harder, because of "division with truncation toward zero" (integer division in C) and "division with truncation toward negative infinity (division by flooring), but I am honestly in doubt if .NET has both definitions avaliable today. Anyway important in applications of Euclid’s theorem and other number theory fundamentals. Many more such questions are easy to come up with, because the current proposal has on one hand a set of (not very usable) independent interfaces (often with only a single operator), and another set of number interfaces, that seem to target only a subset of possible numeric types, because there a too many operators (functions) in each of the too few abstractions. It would be better to come up with a design in the middle between the "too many" |
Conversion to/from
The operator interfaces may support many things that are outside the realm of Even within the realm of number like types; there are cases like |
I've been on vacation for the past month and will be working on addressing some of these issues and updating the proposals accordingly. Part of this came down to time limitations in getting the .NET 6 preview out. |
I fully understand that the target audience is the general public. Besides, the type system isn't rich enough to capture all the nuances of algebraic structures even if we wanted to. (Example: matrix multiplication with rectangular matrices.) The super fine-grained interfaces seem at odds with that idea. Who in the general public can name a kind of number that does not have a multiplicative identity? I think it's reasonable to assume that anyone who uses such a type should be aware of this fact. So I don't think it's necessary to have a separate Base interfacesAdditionLet's start with the group (no pun intended) of addition and related operations. There are really two kinds of numbers:
Normal numbers have a 0 and can be added, subtracted, negated between themselves: 0, T+T, T-T, -T. These 3 (types of) sets are mutually exclusive. To me it would make much more sense to group the operations in this way than to have the current 4 interfaces grouped by operation. Also, I don't know of any examples where addition is not commutative. More generally, I can't think of any non-contrived examples where you have 3 different types (T+U->V). Even if there are, would you need to create an abstraction that can generically use addition in this way? MultiplicationA similar argument can be made for multiplication but it's trickier because there are many more subtleties with division. Two other points of note:
Checked operationsChecked context is only relevant for integer operations. Non-integer types shouldn't be burdened with having to implement them. Generic method type parametersIn several places, an interface contains a method that includes a generic type parameter. Examples are: conversions, bit shift operations... This creates an expectation that (for example) general conversions are supported. It may be better to define conversions separately and include a minimal set in the numeric interfaces (say Numeric interfacesRather than try to squeeze everything into an
Capability queriesI think it might be useful to add a method to query whether a type supports an operation. For example: |
Most of the "operator" interfaces are there to support more than just the generic math scenario. There are hundreds of thousands of types across all .NET code in existence and the actual contract for these operators is a generic one that can accept all kinds of designs and shapes that don't fit the normal model for mathematics, numerics, or even recommended practices. Even looking in the BCL, there are concepts like
Concatenation (such as for strings) uses the addition operator and is non-commutative
While many libraries expose both
There is no trivial way to restrict these to just integer types as we can't know who will implement the interfaces ahead of time. Likewise, there are a plethora of scenarios where
There is no good way to provide or expose this. Functionally speaking, you will have an arbitrary unknown type and need to convert it to another type (possibly known and possibly unknown) in many scenarios. We have real world limitations on the number of interfaces we can expose on the primitive types. There are possibly some better ways this could be exposed; and I am in the process of investigating them, but I expect this will ultimately be a pain point for different users in different ways. Even just considering the primitive types, there are ~14 types that can all convert between each other (10 integer types, 3 floating-point types, and decimal). There then also exists things like When all of those are considered; the initial preview decided on generic It's not the only solution, but it is the one that we landed on for the .NET 6 preview and I will continue working on figuring out the best solution here before we finalize the feature. |
What about |
There is always some going to be happy about some name we choose. |
Fair enough, and happy new year. I am probably repeating myself below. No pun intended. Just want to help make this feature work the best. I have a lot of duplication wrt computations using both double and decimal (primarily) because it is not always clear when it is best to use base2/hardware-based computation and when to use base10/software-based computations in my application domain (money, and other "things" that cannot be rounded outside my control).
I am perfectly fine with
(Strings, +) is not a group (it is a monoid). You cannot undo concatenation (
You mention Calendar related things (date and time). The calender is highly(!) irregular (you can add/subtract days, but nobody wants to work with day offsets) and culture dependent (Gregorian calendar is not used everywhere, Daylight saving time is different around the globe). So in my opinion date (and probably also time = Is So what exactly is "APIs to support generic math"? what exactly are the types behind "number"? Are we talking about numeric types only? Are the Do I have to stay away from the very generic Are we going to have more/better number abstractions than (This comment #257 (comment) are also touching upon the lack of number interfaces) This is something I cannot wrap my head around at the moment. By having an "atomic/disconnected" bag of "small" interfaces, because we are considering too many "things" (strings, date and time, integers, reals, complex numbers, vectors, matrices, ...., maybe more,....., please stop me:-)), then it is extremely difficult to find "Math" and "Algebra" in the "mess". CoreLib types such After playing with the new interfaces for a while, I could not substitute small portions of my existing generic math API's, because the algorithms I was trying to convert over was based on Euclidean division (division by truncating, actually both Truncation-division -- C# In the first proposal there was i BIG graph/tree of interfaces and concrete "number" types. It was a bit hard to read. Maybe a table with the BCL types that will get "1. class" support (what ever that is) for generic math APIs would be appropriate. Then it would be easier for me to grasp how big a problem we are trying to solve with this proposal. At the moment it is not clear to me what exactly we are trying to solve. It seems a bit like "1. class support for "system programming" (C-like) integers and "IEEE floats", and "second class support" for decimal. I do not work with polynomials (complex numbers) or SIMD instruction vectors so I cannot evaluate support in that space, but I guess the IEEE float interfaces are working great for SIMD abtractions. |
Static abstracts in interfaces extends to more than just numbers as do the applications for many of the operator interfaces. Likewise, many concepts from mathematics (like rings and fields) do not have clear 1-to-1 mappings in programming.
|
I would argue that the |
Of course this extends to everything. But at the Library level, doesn't it complicate this feature a lot, to focus broadly on Polymorphism using (stateless, static) functions in general. But for "generic math" besides There is danger in treating
I have focused on "numbers" and "math" above. |
Most users will be expected to use more constrained interfaces, such as Higher level interfaces, such as
There may be a slightly different split, but it is not likely to grow more significantly. The BCL will provide the "core" interfaces allowing other users to build their own types and cover common algorithms and type interchange scenarios. Users interested in more specialized scenarios can then define their own interfaces built on top of the interfaces we provide or even their own hierarchies.
I don't agree. The BCL provides many primitive building blocks which are only useful when combined with the other building blocks. Many of the operator and numeric interfaces being exposed as part of this proposal are in the same vein. They are simply the tools/building blocks on which more complex and useful types can be built.
Decimal is a bit of an odd type in the first place in that it is a non-standardized type and doesn't fit in well with many of the concepts you might want to support for a
We certainly could update it to meet that contract and to ensure that
The actual markdown already contains this independent of the graph. However, just considering what the BCL will do/expose is not sufficient. These are primitive building blocks that any .NET library could use for their own scenarios and which could fulfill a completely different contract from the one that another user expects. That is one reason why there are the "operator" interfaces and the "numeric" interfaces. Both play into generic math, but the former can be extended well beyond numerics. |
@tannergooding Thank you for the clarifications.
What is the added value of the base interfaces ( You give two motivating examples. Both involve numbers. (As an aside, I think it would be instructive to write out the second example in terms of generic math instead of relying on LINQ's generic Sum.) It's true that operators can be used for lots of other stuff besides numbers, but that doesn't justify why there should be such fine-grained interfaces. Why isn't it good enough that you can define your own interfaces that can declare operator contracts? When you define an interface, that must mean something. The same is not true for operators. Does it make sense to define a single interface that covers both generic math operations and operations "that don't fit the normal model for mathematics, numerics, or even recommended practices"? In addition, for generic operands (like |
I am not sure, but I think .NET 1.0 brought Decimal over from oleautomation (VB 6, VBA, COM). And IEEE 754 (2008) created decimal types, based on the IEEE idioms of normal and subnormal numbers (denormalized numbers around zero and NaN, +inf and -inf). @tannergooding You know more about IEEE 1985, 2008 and 2019 than I do, so please correct me if am wrong. Both IEEE decimal and .NET decimal are "Currency" type (base 10). Since it is unlikely that the BCL will support IEEE 754 decimals, I urge you to consider giving System.Decimal (that does not have the "IEEE idioms") a place in the INumber hierarchy (probably a common base interface of IFloatingPoint, because both are fractional numbers). This was what dotnet/runtime#62293 was all about (I don't think I stated that very clearly, I am afraid) |
They form the building blocks on which other interfaces and hierarchies can be built. There are many, many, many types where addition get used ranging from strings to quaternions to dates/times to events and lists. Some of these are C# specific and some are specific to other languages; but in all cases you are "adding" two things. This doesn't have to mean you are performing arithmetic based addition; but the general use cases for it remain the same; which is taking some value and adding an additional value. That may change the magnitude, the items in the set, or something else; but the algorithm you are doing with it remains generally the same. You have a There are many other languages outside of .NET that allow you to define shapes/traits/roles/etc around individual methods or operators and they always extend far beyond just numerics.
The second example uses generic math and it is a completely valid use case of it showing a likely real world scenario. There are other examples elsewhere, such as in the blog post: https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/ There is a balance between providing a couple simple examples to relay the idea and providing something that is "complete" and fully describes the idea, use cases, and scenarios. If I spend all my time doing the latter, then I have less time to actually implement the feature and ensure it can ship.
Yes; and the same applies to all of the operator interfaces exposed :) The difference is that
Yes, because the operator interfaces are not restricted to numerics. They are general building blocks. The numeric interfaces take those interfaces, build on top of them, and give them well-defined semantics related to numbers.
There are many things that go into this, including back-compat; existing generic constraints, and the intended usage scenarios for the types. |
Users can't define their own hierarchies. That would be very painful to wrap all the primitive number types. |
Yes
No. IEEE 754 (2008) merely merged the IEEE 754 (1985) and IEEE 854 (1987) standards. The core principles around "base-10 IEEE floating-point" have been around for a long time.
No. While base 10 numbers can generally be used for currency; the intent of The IEEE decimal types, on the other hand, were explicitly designed for general purpose usage including in domains where the representation of values such as
|
Like you said. .NET/C# is not one of those languages. If I built a "different" number hierachy, how can I extend the BCL types using "static abstract in interfaces"? |
Users can and will define their own hierarchies and interfaces. In some cases, this will be intentionally not related to numerics at all and will build on the operator interfaces. In others, it will likely extend the baseline functionality that we provide for the numeric interfaces with additional concepts specific to their library or domain. |
In our code (that is unfortunately closed source) we have "division with remainder" defined as a single function working for both integers and fractional numbers. I would personally prefer that. |
Interesting. I use decimal for a lot of other things, besides money. I am not talking "numerical methods", but as long as your "rational number" (fraction, significand) have a denominator with a power of 2, 5 or 10, you are good, right? @tannergooding You are probably right here, like I said I am not an IEEE expert. Does it have to do with the reasons that base 2 IEEE 754 numbers are (often) great:
Are IEEE 754 decimals (plural, there are more sizes/precisions, compared to C# decimal) somehow better than |
They support They support and expose They have more formats (
The IEEE decimal types also have a format that is "more trivial" to perform certain operations on; making them typically faster than
|
|
||
static abstract TResult checked operator %(TSelf left, TOther right); | ||
static abstract TResult operator checked -(TSelf left, TOther right); | ||
} | ||
|
||
public interface IUnaryNegationOperators<TSelf, TResult> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this should be named IUnaryMinusOperators
to match IUnaryPlusOperators
. That's what the operator is called: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/arithmetic-operators#unary-plus-and-minus-operators
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These match the IL names, not the C# names. The IL name is op_UnaryNegation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, ok. Although I still think most languages would call it unary minus. Most people don't know IL and don't write it, so if this name exists only in IL, I'm not sure that's a good source to go by.
|
||
// Logical right shift | ||
static abstract TResult operator >>>(TSelf value, TOther shiftAmount); | ||
static abstract TResult AdditiveIdentity { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be TSelf
as opposed to TResult
? I think it would be really weird if a number type had a static AdditiveIdentity
property that is of a different type. And conceptually, this doesn't feel like a result anyway, but rather like a value that you use as an input to the + operator, and in IAdditionOperators
, one of the parameters is actually of type TSelf
. Same with MultiplicativeIdentity
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider things like DateTime + TimeSpan
where TSelf
is DateTime
but the additive identity is a TimeSpan
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, you're right. It's still not a result though, so the name TResult
feels wrong. Maybe I would call this TOther
- in DateTime
, TimeSpan
is the TOther
in the operator.
@tannergooding I watched the API review (https://www.youtube.com/watch?v=Qo40CuHxSwk) and have a few thoughts and suggestions.
I hope at least some of these ideas might be useful. 😅 |
Thanks for the feedback!
I agree this isn't the best name and doesn't necessarily cover types like "Rational" or "FixedPoint". There, unfortunately, isn't a lot of names to choose from and there probably isn't a good name that encompasses everything people want.
If someone does have name suggestions, I'm open to hearing them. Noting that
I will raise this one in API review but there is currently no plans to expose or support any concept of a non-binary integer. Unlike with
These are explicitly left-off today because there are no plans to ever expose these on It may be worth pulling these into their own
The plan is to move
For most of these,
The code represented was pseudo-code. The actual API surface should get reviewed next Tuesday.
This is invalid for many reasons, primarily because doing a Additionally, it presumes that the implementation and behavior here is "well-defined" for The general purpose fallback path is going to involve using the This still leaves a gap where two libraries that aren't familiar with eachother can't have their types convert between eachother and is why we fundamentally require the ability to define some external converter (
#262 covers moving some of these APIs around where they make sense and it is valid for it to be a |
Yes, |
@Neme12 I also requested an |
@tannergooding I suppose As @Neme12 's Question shows there is a Need for comparing the (often squared) Magnitude of mathematical entities to determine whether the difference between two is 'small'. The Magnitude would be an IComparable Number. I actually have good experience just using |
@Neme12 Any Details why the Thumbs down? |
No.
These are well-established names set by the IEEE 754 standard and which have been adopted by other languages.
This is not feasible due to not having something like "associated types".
Alternatively someone could define some These interfaces cannot cover "every" type people may want to support and no language that provides similar interfaces does either. The goal is to cover a majority of needs for users around common types and programming patterns. It is explicitly not a design goal to fit any mathematical model, as computer math rarely actually matches "real math". It's instead only a rough approximation with various limitations and additional rules. |
If we want a default implementation, it could be added to INumber - the interface that most users would presumably implement. Those only implementing INumberBase would need to provide a specialized implementation, but it still shouldn't be an issue for them. |
Because of the points that @tannergooding raised. Although I still think the absolute value could be made useful even on public static bool IsReal(TSelf value); // INumber defaults this to true
public static IComparer<TSelf> RealValueComparer { get; } // Alternative name: RealPartComparer. Compares the real part only. INumber defaults this as well. and/or this: public static IComparer<TSelf> AbsoluteValueComparer { get; } but this would require introducing the notion of a comparer - |
@Neme12 I would like to see this implemented on the existing primitive Types, so I don't have to write Functions to treat them special. I could e.g. write |
@SpocWeb Why do you want |
You're thinking about this too much from the abstract mathematical sense. Various types may have compounding error over many operations or other issues (such as having different epsilons between values based on the input with the largest magnitude). While you can write many types of generic algorithms using these interfaces, it doesn't remove the need to consider types, kinds, or limits. These all still need some minimal amount of consideration. |
Not quite as @tannergooding explained above, Instead, 'Norm' should always return an Therefore I think a primitive Number Type like |
@SpocWeb I don't it should be a fixed type like For |
@Neme12 Doing such a conversion to |
|
@Neme12 Ah, that's an Idea, so you propose to to something like this:
Yes, I presume that would work, although the Equality-Test looks awkward (and may be prone to subtle changes if not returning an exact copy of one of its arguments) compared to the same with a proper Norm:
Thanks for the idea! |
So I would be happy to have at least |
Although of course you can easily define both
If it wasn't for the Fact that |
It isn't sufficient to define the implementation like that because it doesn't correctly take into account things like |
Yes, but that can surely be extended I imagine, depending on how |
Hopping into this late because I recently had to work with generic numerics. What I would have liked would be an IOperators<+, -, *, /, %, ==, !=, >, <, <=, =<> interface which would describe which operators was implemented and enforce their implementation, this way you could ensure that a generic type always had a specific operator. I don't know if something like this would be possible with how interfaces currently works, but if it's possible I imagine it would be a much more humanly readable way to implement this. |
This updates the generic math proposal based on feedback received so far, including from #205, https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/, and other sources over social media and from the community.