From 7851cba2ada30cf1d2a6d2af0cf77734f2a96a8d Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 14 Aug 2026 21:23:54 +0000 Subject: [PATCH 1/3] Document the public members that a pragma was hiding, and drop the pragmas that hid nothing (#585) The library builds with TreatWarningsAsErrors and does not silence CS1591, so the public surface looks documented. It is documented except where a pragma turns the warning off, and there were twenty such regions outside the generated parser. Counting warnings said nothing; counting the suppressions did. Two of the twenty were suppressing nothing at all. The largest -- 588 lines of Entity.Omni.Classes.cs -- carried "TODO: it's only for records' parameters! Remove it once you can document records parameters", and the compiler stopped needing that some versions ago. Entity.Continuous.Calculus is the other. Both come out with no documentation written, because there was nothing missing. Of the rest, most were hiding one thing: the NodeChild, NodeFirstChild and NodeSecondChild accessors that implement IUnaryNode and IBinaryNode. The interface documents them and the members beside them in the same records already use , so 62 of them now do too. That clears nine files: Discrete, Piecewise, Rounding, Trigonometry, ArcTrigonometry, Operators, Exponential, Floors, AbsSignum and Factorial. Note what the pragma comments claimed while they did it -- "only while records' parameters cannot be documented" -- when what they were hiding was an ordinary property. Written rather than inherited, where there was something to say: Complex's operators and conversions, about thirty of them. These share a surprise worth stating: with MathS.Settings.DowncastingEnabled, which is the default, an exact whole value arrives as an Integer and an exact ratio as a Rational, so the runtime type is not the declared one. The float and double conversions read the binary value that was stored rather than the decimal that was written, and decimal does not, which is the kind of thing a caller finds out from a wrong answer. Set's Domain-to-Entity conversion, Piecewise.Cases, and the two members of Matrices.Direction. Eleven of the twenty regions are gone. The nine that remain are the operator and conversion blocks on Real, Rational, Integer, Number, Matrix and the implicit operators -- about 150 members of the same kind as Complex's, and the same treatment applies to them. The generated parser keeps its pragma: it is regenerated from the grammar, so anything written into it is written to be lost. Measured: suite 7143 passed / 0 failed, and the build itself is the check -- CS1591 is an error here, so a pragma cannot come out unless every member under it is documented. https://github.com/asc-community/AngouriMath/issues/585 https://github.com/asc-community/AngouriMath/issues/746 Co-authored-by: Claude Opus 5 --- Sources/AngouriMath/Convenience/MathS.cs | 5 +- .../Entity.Continuous.AbsSignum.Classes.cs | 4 +- ...tity.Continuous.ArcTrigonometry.Classes.cs | 8 +- .../Entity.Continuous.Calculus.Classes.cs | 2 - .../Entity.Continuous.Complex.Definition.cs | 89 ++++++++++++++++++- .../Entity.Continuous.Exponential.Classes.cs | 6 +- .../Entity.Continuous.Factorial.Classes.cs | 3 +- .../Entity.Continuous.Floors.Classes.cs | 4 +- .../Entity.Continuous.Operators.Classes.cs | 12 ++- .../Entity.Continuous.Rounding.Classes.cs | 9 +- .../Entity.Continuous.Trigonometry.Classes.cs | 8 +- .../Discrete/Entity.Discrete.Classes.cs | 24 ++++- .../Core/Entity/Omni/Entity.Omni.Classes.cs | 2 - .../Core/Entity/Omni/Entity.Piecewise.cs | 8 +- .../Core/Entity/Omni/Entity.Set.cs | 6 +- 15 files changed, 158 insertions(+), 32 deletions(-) diff --git a/Sources/AngouriMath/Convenience/MathS.cs b/Sources/AngouriMath/Convenience/MathS.cs index 25e3952e6..0b08e8deb 100644 --- a/Sources/AngouriMath/Convenience/MathS.cs +++ b/Sources/AngouriMath/Convenience/MathS.cs @@ -5166,10 +5166,11 @@ public static class Matrices /// public enum Direction { -#pragma warning disable CS1591 + /// Side by side, so the columns of the second follow the first. Horizontal, + + /// One above the other, so the rows of the second follow the first. Vertical -#pragma warning restore CS1591 } /// diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.AbsSignum.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.AbsSignum.Classes.cs index e75e8cd24..3fbe93d9c 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.AbsSignum.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.AbsSignum.Classes.cs @@ -11,12 +11,12 @@ namespace AngouriMath { partial record Entity { -#pragma warning disable CS1591 // only while records' parameters cannot be documented /// /// A node of signum /// public sealed partial record Signumf(Entity Argument) : Function, IUnaryNode { + /// public Entity NodeChild => Argument; private Signumf New(Entity arg) => @@ -32,6 +32,7 @@ private Signumf New(Entity arg) => /// public sealed partial record Absf(Entity Argument) : Function, IUnaryNode { + /// public Entity NodeChild => Argument; private Absf New(Entity arg) => @@ -41,6 +42,5 @@ private Absf New(Entity arg) => /// protected override Entity[] InitDirectChildren() => new[] { Argument }; } -#pragma warning restore CS1591 // only while records' parameters cannot be documented } } diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.ArcTrigonometry.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.ArcTrigonometry.Classes.cs index cbfdfdb5e..239903e16 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.ArcTrigonometry.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.ArcTrigonometry.Classes.cs @@ -11,12 +11,12 @@ namespace AngouriMath { partial record Entity { -#pragma warning disable CS1591 // only while records' parameters cannot be documented /// /// A node of arcsine /// public sealed partial record Arcsinf(Entity Argument) : TrigonometricFunction, IUnaryNode { + /// public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible @@ -32,6 +32,7 @@ public sealed partial record Arcsinf(Entity Argument) : TrigonometricFunction, I /// public sealed partial record Arccosf(Entity Argument) : TrigonometricFunction, IUnaryNode { + /// public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible @@ -47,6 +48,7 @@ public sealed partial record Arccosf(Entity Argument) : TrigonometricFunction, I /// public sealed partial record Arctanf(Entity Argument) : TrigonometricFunction, IUnaryNode { + /// public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible @@ -62,6 +64,7 @@ public sealed partial record Arctanf(Entity Argument) : TrigonometricFunction, I /// public sealed partial record Arccotanf(Entity Argument) : TrigonometricFunction, IUnaryNode { + /// public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible @@ -77,6 +80,7 @@ public sealed partial record Arccotanf(Entity Argument) : TrigonometricFunction, /// public sealed partial record Arcsecantf(Entity Argument) : TrigonometricFunction, IUnaryNode { + /// public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible @@ -92,6 +96,7 @@ public sealed partial record Arcsecantf(Entity Argument) : TrigonometricFunction /// public sealed partial record Arccosecantf(Entity Argument) : TrigonometricFunction, IUnaryNode { + /// public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible @@ -101,6 +106,5 @@ public sealed partial record Arccosecantf(Entity Argument) : TrigonometricFuncti /// protected override Entity[] InitDirectChildren() => new[] { Argument }; } -#pragma warning restore CS1591 // only while records' parameters cannot be documented } } diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Calculus.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Calculus.Classes.cs index ff1dffcf7..08896cf82 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Calculus.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Calculus.Classes.cs @@ -11,7 +11,6 @@ namespace AngouriMath { partial record Entity { -#pragma warning disable CS1591 // only while records' parameters cannot be documented /// /// A node of derivative /// @@ -64,6 +63,5 @@ public override Entity Replace(Func func) => /// protected override Entity[] InitDirectChildren() => new[] { Expression, Var, Destination }; } -#pragma warning restore CS1591 // only while records' parameters cannot be documented } } diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Complex.Definition.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Complex.Definition.cs index d0e75fd01..2137e6c09 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Complex.Definition.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Complex.Definition.cs @@ -195,55 +195,136 @@ public static bool TryParse(string source, public System.Numerics.Complex ToNumerics() => new System.Numerics.Complex(RealPart.EDecimal.ToDouble(), ImaginaryPart.EDecimal.ToDouble()); -#pragma warning disable CS1591 + /// + /// Narrows to , which is + /// and loses precision the same way. + /// public static explicit operator System.Numerics.Complex(Complex it) => it.ToNumerics(); + /// Their sum. public static Complex operator +(Complex a, Complex b) => OpSum(a, b); + + /// Their difference. public static Complex operator -(Complex a, Complex b) => OpSub(a, b); + + /// Their product. public static Complex operator *(Complex a, Complex b) => OpMul(a, b); + + /// Their quotient. public static Complex operator /(Complex a, Complex b) => OpDiv(a, b); + + /// The operand itself; unary plus changes nothing. public static Complex operator +(Complex a) => a; + + /// Its negation. public static Complex operator -(Complex a) => OpMul(Integer.MinusOne, a); + + // The conversions below share one surprise, stated once here and referred to + // rather than repeated: where MathS.Settings.DowncastingEnabled is on, which is + // the default, an exact whole value arrives as an Integer and an exact ratio as + // a Rational. The declared type is Complex either way, so the difference shows + // up in the runtime type and in what the number prints as, not in the signature. + + /// The number as a . public static implicit operator Complex(sbyte value) => (long)value; + + /// The number as a . public static implicit operator Complex(byte value) => (ulong)value; + + /// The number as a . public static implicit operator Complex(short value) => (long)value; + + /// The number as a . public static implicit operator Complex(ushort value) => (ulong)value; + + /// The number as a . public static implicit operator Complex(int value) => (long)value; + + /// The number as a . public static implicit operator Complex(uint value) => (ulong)value; + + /// + /// The number as a — an at runtime + /// while downcasting is enabled. + /// public static implicit operator Complex(long value) => MathS.Settings.DowncastingEnabled ? Integer.Create(value) : Create(value, 0); + /// + /// The number as a — an at runtime + /// while downcasting is enabled. + /// public static implicit operator Complex(ulong value) => MathS.Settings.DowncastingEnabled ? Integer.Create(value) : Create(value, 0); + + /// + /// The integer as a — an at runtime + /// while downcasting is enabled. + /// public static implicit operator Complex(EInteger value) => MathS.Settings.DowncastingEnabled ? Integer.Create(value) : Create(value, 0); + + /// + /// The ratio as a — a at runtime + /// while downcasting is enabled, and so still exact. + /// public static implicit operator Complex(ERational value) => MathS.Settings.DowncastingEnabled ? Rational.Create(value) : Create(value, 0); + + /// The decimal as a with no imaginary part. public static implicit operator Complex(EDecimal value) => Create(value, 0); + + /// + /// The value as a with no imaginary part. It is read as the + /// binary it is, so a literal such as 0.1f arrives + /// as the value that literal actually holds and not as one tenth. + /// public static implicit operator Complex(float value) => Create(EDecimal.FromSingle(value), 0); + + /// + /// The value as a with no imaginary part, read as the + /// binary it is rather than as the decimal it was + /// written as. + /// public static implicit operator Complex(double value) => Create(EDecimal.FromDouble(value), 0); + + /// + /// The value as a with no imaginary part. A + /// is decimal already, so this one keeps the digits + /// that were written. + /// public static implicit operator Complex(decimal value) => Create(EDecimal.FromDecimal(value), 0); + + /// + /// The .NET complex number as this one, through its two + /// parts and their precision. + /// public static implicit operator Complex(System.Numerics.Complex value) => Create(EDecimal.FromDouble(value.Real), EDecimal.FromDouble(value.Imaginary)); + + /// The pair read as real and imaginary parts. public static implicit operator Complex((int re, int im) v) => Create(v.re, v.im); + + /// The pair read as real and imaginary parts. public static implicit operator Complex((float re, float im) v) => Create(v.re, v.im); - public static implicit operator Complex((decimal re, decimal im) v) => Create(v.re, v.im); - public static implicit operator Complex((double re, double im) v) => Create(v.re, v.im); -#pragma warning restore CS1591 + /// The pair read as real and imaginary parts. + public static implicit operator Complex((decimal re, decimal im) v) => Create(v.re, v.im); + /// The pair read as real and imaginary parts. + public static implicit operator Complex((double re, double im) v) => Create(v.re, v.im); } } } diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Exponential.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Exponential.Classes.cs index 7537e1b85..08e5081ec 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Exponential.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Exponential.Classes.cs @@ -11,7 +11,6 @@ namespace AngouriMath { partial record Entity { -#pragma warning disable CS1591 // only while records' parameters cannot be documented /// /// A node of exponential (power) /// @@ -22,8 +21,10 @@ private Powf New(Entity @base, Entity exponent) => ReferenceEquals(Base, @base) && ReferenceEquals(Exponent, exponent) ? this : new(@base, exponent); internal override Priority Priority => Priority.Pow; + /// public Entity NodeFirstChild => Base; + /// public Entity NodeSecondChild => Exponent; /// @@ -41,8 +42,10 @@ public sealed partial record Logf(Entity Base, Entity Antilogarithm) : Function, private Logf New(Entity @base, Entity antilogarithm) => ReferenceEquals(Base, @base) && ReferenceEquals(Antilogarithm, antilogarithm) ? this : new(@base, antilogarithm); + /// public Entity NodeFirstChild => Base; + /// public Entity NodeSecondChild => Antilogarithm; /// @@ -51,6 +54,5 @@ private Logf New(Entity @base, Entity antilogarithm) => protected override Entity[] InitDirectChildren() => new[] { Base, Antilogarithm }; } -#pragma warning restore CS1591 // only while records' parameters cannot be documented } } diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Factorial.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Factorial.Classes.cs index 8bddb0629..4d4b9280f 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Factorial.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Factorial.Classes.cs @@ -11,7 +11,6 @@ namespace AngouriMath { partial record Entity { -#pragma warning disable CS1591 // only while records' parameters cannot be documented /// /// A node of factorial /// @@ -22,6 +21,7 @@ public sealed partial record Factorialf(Entity Argument) : Function, IUnaryNode // This is still a function for pattern replacement internal override Priority Priority => Priority.Factorial; + /// public Entity NodeChild => Argument; /// @@ -29,6 +29,5 @@ public sealed partial record Factorialf(Entity Argument) : Function, IUnaryNode /// protected override Entity[] InitDirectChildren() => new[] { Argument }; } -#pragma warning restore CS1591 // only while records' parameters cannot be documented } } diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Floors.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Floors.Classes.cs index 05f94f543..de146b873 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Floors.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Floors.Classes.cs @@ -11,7 +11,6 @@ namespace AngouriMath { partial record Entity { -#pragma warning disable CS1591 // only while records' parameters cannot be documented /// /// A node of floor: the greatest integer not above the argument. /// @@ -23,6 +22,7 @@ partial record Entity /// public sealed partial record Floorf(Entity Argument) : Function, IUnaryNode { + /// public Entity NodeChild => Argument; private Floorf New(Entity arg) => @@ -42,6 +42,7 @@ private Floorf New(Entity arg) => /// public sealed partial record Ceilf(Entity Argument) : Function, IUnaryNode { + /// public Entity NodeChild => Argument; private Ceilf New(Entity arg) => @@ -51,6 +52,5 @@ private Ceilf New(Entity arg) => /// protected override Entity[] InitDirectChildren() => new[] { Argument }; } -#pragma warning restore CS1591 // only while records' parameters cannot be documented } } diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Operators.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Operators.Classes.cs index fe46c5258..fa216a635 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Operators.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Operators.Classes.cs @@ -11,7 +11,6 @@ namespace AngouriMath { partial record Entity { -#pragma warning disable CS1591 // only while records' parameters cannot be documented /// /// A node of sum /// @@ -34,8 +33,10 @@ private Sumf New(Entity augend, Entity addend) => ReferenceEquals(Augend, augend) && ReferenceEquals(Addend, addend) ? this : new(augend, addend); internal override Priority Priority => Priority.Sum; + /// public Entity NodeFirstChild => Augend; + /// public Entity NodeSecondChild => Addend; /// @@ -67,8 +68,10 @@ private Minusf New(Entity minuend, Entity subtrahend) => ReferenceEquals(Minuend, minuend) && ReferenceEquals(Subtrahend, subtrahend) ? this : new(minuend, subtrahend); internal override Priority Priority => Priority.Minus; + /// public Entity NodeFirstChild => Minuend; + /// public Entity NodeSecondChild => Subtrahend; /// @@ -99,8 +102,10 @@ private Mulf New(Entity multiplier, Entity multiplicand) => ReferenceEquals(Multiplier, multiplier) && ReferenceEquals(Multiplicand, multiplicand) ? this : new(multiplier, multiplicand); internal override Priority Priority => Priority.Mul; + /// public Entity NodeFirstChild => Multiplier; + /// public Entity NodeSecondChild => Multiplicand; /// @@ -132,8 +137,10 @@ internal Divf New(Entity dividend, Entity divisor) => ReferenceEquals(Dividend, dividend) && ReferenceEquals(Divisor, divisor) ? this : new(dividend, divisor); internal override Priority Priority => Priority.Div; + /// public Entity NodeFirstChild => Dividend; + /// public Entity NodeSecondChild => Divisor; /// @@ -155,8 +162,10 @@ internal Modf New(Entity dividend, Entity divisor) => ReferenceEquals(Dividend, dividend) && ReferenceEquals(Divisor, divisor) ? this : new(dividend, divisor); internal override Priority Priority => Priority.Mul; + /// public Entity NodeFirstChild => Dividend; + /// public Entity NodeSecondChild => Divisor; /// @@ -164,6 +173,5 @@ internal Modf New(Entity dividend, Entity divisor) => /// protected override Entity[] InitDirectChildren() => new[] { Dividend, Divisor }; } -#pragma warning restore CS1591 // only while records' parameters cannot be documented } } diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rounding.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rounding.Classes.cs index 147dcb921..b6376a30d 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rounding.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rounding.Classes.cs @@ -11,7 +11,6 @@ namespace AngouriMath { partial record Entity { -#pragma warning disable CS1591 // only while records' parameters cannot be documented /// /// A node of round: the nearest integer, with a tie going to the even one. /// @@ -23,6 +22,7 @@ partial record Entity /// public sealed partial record Roundf(Entity Argument) : Function, IUnaryNode { + /// public Entity NodeChild => Argument; private Roundf New(Entity arg) => @@ -46,8 +46,10 @@ private Roundf New(Entity arg) => /// public sealed partial record Minf(Entity Left, Entity Right) : Function, IBinaryNode { + /// public Entity NodeFirstChild => Left; + /// public Entity NodeSecondChild => Right; private Minf New(Entity left, Entity right) => @@ -64,8 +66,10 @@ private Minf New(Entity left, Entity right) => /// See for why this is a node. public sealed partial record Maxf(Entity Left, Entity Right) : Function, IBinaryNode { + /// public Entity NodeFirstChild => Left; + /// public Entity NodeSecondChild => Right; private Maxf New(Entity left, Entity right) => @@ -88,8 +92,10 @@ private Maxf New(Entity left, Entity right) => /// public sealed partial record Gcdf(Entity Left, Entity Right) : Function, IBinaryNode { + /// public Entity NodeFirstChild => Left; + /// public Entity NodeSecondChild => Right; private Gcdf New(Entity left, Entity right) => @@ -99,6 +105,5 @@ private Gcdf New(Entity left, Entity right) => /// protected override Entity[] InitDirectChildren() => new[] { Left, Right }; } -#pragma warning restore CS1591 // only while records' parameters cannot be documented } } diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Trigonometry.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Trigonometry.Classes.cs index 16f5296d8..49b8b2ea7 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Trigonometry.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Trigonometry.Classes.cs @@ -11,12 +11,12 @@ namespace AngouriMath { partial record Entity { -#pragma warning disable CS1591 // only while records' parameters cannot be documented /// /// A node of sine /// public sealed partial record Sinf(Entity Argument) : TrigonometricFunction, IUnaryNode { + /// public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible @@ -32,6 +32,7 @@ public sealed partial record Sinf(Entity Argument) : TrigonometricFunction, IUna /// public sealed partial record Cosf(Entity Argument) : TrigonometricFunction, IUnaryNode { + /// public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible @@ -47,6 +48,7 @@ public sealed partial record Cosf(Entity Argument) : TrigonometricFunction, IUna /// public sealed partial record Tanf(Entity Argument) : TrigonometricFunction, IUnaryNode { + /// public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible @@ -62,6 +64,7 @@ public sealed partial record Tanf(Entity Argument) : TrigonometricFunction, IUna /// public sealed partial record Cotanf(Entity Argument) : TrigonometricFunction, IUnaryNode { + /// public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible @@ -77,6 +80,7 @@ public sealed partial record Cotanf(Entity Argument) : TrigonometricFunction, IU /// public sealed partial record Secantf(Entity Argument) : TrigonometricFunction, IUnaryNode { + /// public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible @@ -92,6 +96,7 @@ public sealed partial record Secantf(Entity Argument) : TrigonometricFunction, I /// public sealed partial record Cosecantf(Entity Argument) : TrigonometricFunction, IUnaryNode { + /// public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible @@ -101,6 +106,5 @@ public sealed partial record Cosecantf(Entity Argument) : TrigonometricFunction, /// protected override Entity[] InitDirectChildren() => new[] { Argument }; } -#pragma warning restore CS1591 // only while records' parameters cannot be documented } } diff --git a/Sources/AngouriMath/Core/Entity/Discrete/Entity.Discrete.Classes.cs b/Sources/AngouriMath/Core/Entity/Discrete/Entity.Discrete.Classes.cs index acbe1ce2b..e5651f2d8 100644 --- a/Sources/AngouriMath/Core/Entity/Discrete/Entity.Discrete.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Discrete/Entity.Discrete.Classes.cs @@ -12,7 +12,6 @@ namespace AngouriMath { partial record Entity { -#pragma warning disable CS1591 // TODO: it's only for records' parameters! Remove it once you can document records parameters /// /// This node represents all possible values a boolean node might be of @@ -95,6 +94,7 @@ public sealed partial record Notf(Entity Argument) : Statement, IUnaryNode { internal override Priority Priority => Priority.Negation; + /// public Entity NodeChild => Argument; private Notf New(Entity negated) => @@ -113,8 +113,10 @@ public sealed partial record Andf(Entity Left, Entity Right) : Statement, IBinar { internal override Priority Priority => Priority.Conjunction; + /// public Entity NodeFirstChild => Left; + /// public Entity NodeSecondChild => Right; private Andf New(Entity left, Entity right) => @@ -133,8 +135,10 @@ public sealed partial record Orf(Entity Left, Entity Right) : Statement, IBinary { internal override Priority Priority => Priority.Disjunction; + /// public Entity NodeFirstChild => Left; + /// public Entity NodeSecondChild => Right; private Orf New(Entity left, Entity right) => @@ -153,8 +157,10 @@ public sealed partial record Xorf(Entity Left, Entity Right) : Statement, IBinar { internal override Priority Priority => Priority.XDisjunction; + /// public Entity NodeFirstChild => Left; + /// public Entity NodeSecondChild => Right; private Xorf New(Entity left, Entity right) => @@ -173,8 +179,10 @@ public sealed partial record Impliesf(Entity Assumption, Entity Conclusion) : St { internal override Priority Priority => Priority.Implication; + /// public Entity NodeFirstChild => Assumption; + /// public Entity NodeSecondChild => Conclusion; private Impliesf New(Entity assumption, Entity conclusion) => @@ -197,8 +205,10 @@ public sealed partial record Equalsf(Entity Left, Entity Right) : ComparisonSign { internal override Priority Priority => Priority.Equal; + /// public Entity NodeFirstChild => Left; + /// public Entity NodeSecondChild => Right; internal Equalsf New(Entity left, Entity right) @@ -221,8 +231,10 @@ public sealed partial record Greaterf(Entity Left, Entity Right) : ComparisonSig { internal override Priority Priority => Priority.GreaterThan; + /// public Entity NodeFirstChild => Left; + /// public Entity NodeSecondChild => Right; internal Greaterf New(Entity left, Entity right) @@ -245,8 +257,10 @@ public sealed partial record GreaterOrEqualf(Entity Left, Entity Right) : Compar { internal override Priority Priority => Priority.GreaterThan; + /// public Entity NodeFirstChild => Left; + /// public Entity NodeSecondChild => Right; internal GreaterOrEqualf New(Entity left, Entity right) @@ -269,8 +283,10 @@ public sealed partial record Lessf(Entity Left, Entity Right) : ComparisonSign, { internal override Priority Priority => Priority.GreaterThan; + /// public Entity NodeFirstChild => Left; + /// public Entity NodeSecondChild => Right; internal Lessf New(Entity left, Entity right) @@ -293,8 +309,10 @@ public sealed partial record LessOrEqualf(Entity Left, Entity Right) : Compariso { internal override Priority Priority => Priority.GreaterThan; + /// public Entity NodeFirstChild => Left; + /// public Entity NodeSecondChild => Right; internal LessOrEqualf New(Entity left, Entity right) @@ -318,8 +336,10 @@ public sealed partial record Inf(Entity Element, Entity SupSet) : Statement, IBi { internal override Priority Priority => Priority.ContainsIn; + /// public Entity NodeFirstChild => Element; + /// public Entity NodeSecondChild => SupSet; internal Inf New(Entity element, Entity supSet) @@ -339,6 +359,7 @@ public override Entity Replace(Func func) /// public sealed partial record Phif(Entity Argument) : Function, IUnaryNode { + /// public Entity NodeChild => Argument; internal Phif New(Entity argument) @@ -352,6 +373,5 @@ internal Phif New(Entity argument) } #endregion -#pragma warning restore CS1591 // TODO: it's only for records' parameters! Remove it once you can document records parameters } } diff --git a/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Classes.cs b/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Classes.cs index aa8b53c21..e5ae575be 100644 --- a/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Classes.cs @@ -222,7 +222,6 @@ public bool TryIsSubsetOf(FiniteSet superSet, out bool isSub) } #endregion -#pragma warning disable CS1591 // TODO: it's only for records' parameters! Remove it once you can document records parameters #region Interval /// @@ -810,7 +809,6 @@ public override Set Filter(Entity predicate, Variable over) } #endregion } -#pragma warning restore CS1591 // TODO: it's only for records' parameters! Remove it once you can document records parameters /// /// Application of arguments to the given expression diff --git a/Sources/AngouriMath/Core/Entity/Omni/Entity.Piecewise.cs b/Sources/AngouriMath/Core/Entity/Omni/Entity.Piecewise.cs index aef56b10b..94cbd5c3e 100644 --- a/Sources/AngouriMath/Core/Entity/Omni/Entity.Piecewise.cs +++ b/Sources/AngouriMath/Core/Entity/Omni/Entity.Piecewise.cs @@ -14,13 +14,14 @@ namespace AngouriMath { partial record Entity { -#pragma warning disable CS1591 // TODO: add docs for records' arguments /// /// That is a node which equals Expression if Predicate is true, otherwise /// public sealed partial record Providedf(Entity Expression, Entity Predicate) : Entity, IBinaryNode { + /// public Entity NodeFirstChild => Expression; + /// public Entity NodeSecondChild => Predicate; internal Providedf New(Entity expression, Entity predicate) @@ -50,6 +51,10 @@ public override Entity Replace(Func func) /// public sealed partial record Piecewise : Entity, IEquatable { + /// + /// The cases, in the order they are considered: the value of the piecewise is that + /// of the first one whose condition holds. + /// public IEnumerable Cases => cases; private readonly IEnumerable cases = Enumerable.Empty(); @@ -113,7 +118,6 @@ public Piecewise ApplyToValues(Func transformation) => Cases.Select(c => c.New(transformation(c.Expression), c.Predicate)).ToPiecewise(); } -#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member } diff --git a/Sources/AngouriMath/Core/Entity/Omni/Entity.Set.cs b/Sources/AngouriMath/Core/Entity/Omni/Entity.Set.cs index 0b52c47cb..377a4c073 100644 --- a/Sources/AngouriMath/Core/Entity/Omni/Entity.Set.cs +++ b/Sources/AngouriMath/Core/Entity/Omni/Entity.Set.cs @@ -79,9 +79,11 @@ public bool Contains(Entity entity) /// A new node public Set SetSubtract(Entity anotherSet) => new SetMinusf(this, anotherSet); -#pragma warning disable CS1591 + /// + /// The domain as the set of all its values — becomes the set + /// of reals — so a domain can be written wherever an expression is expected. + /// public static implicit operator Entity(Domain domain) => Set.SpecialSet.Create(domain); -#pragma warning restore CS1591 } } From 287828457d45ef36095fee35892d791298114385 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 14 Aug 2026 21:43:46 +0000 Subject: [PATCH 2/3] Document three more of the blocks a pragma was covering (#585) Entity's implicit conversions from the numeric types, Number's arithmetic operators, and the tensor operations a matrix of expressions is built on. Three more suppression regions gone, leaving four. Two things were worth saying rather than restating the signature. The conversions to Entity produce the narrowest literal that holds the value exactly, and the float and double ones read the binary value that was stored rather than the decimal that was written, while decimal reads what was written. A caller who does not know that finds it out from an answer. EntityTensorWrapperOperations normalises after every operation with InnerSimplified, and the reason is worth recording: a determinant over a 4x4 of symbols is a sum of products of sums, and an entry left unnormalised accumulates the whole history of the operations that built it. Its equality is structural rather than mathematical, so x + x and 2 * x are not equal there, and IsZero recognises the literal zero rather than an expression that happens to vanish. Measured: build succeeds, and the build is the check -- CS1591 is an error here. https://github.com/asc-community/AngouriMath/issues/585 Co-authored-by: Claude Opus 5 --- .../Entity/Continuous/Number/Operators.cs | 17 +++++- .../Core/Entity/Entity.ImplicitOperators.cs | 55 ++++++++++++++++++- .../Core/Entity/Omni/Entity.Matrix.cs | 52 +++++++++++++++++- 3 files changed, 118 insertions(+), 6 deletions(-) diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs b/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs index 353474c3b..1244db27f 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs @@ -177,14 +177,27 @@ internal static bool AreEqual(T a, T b) where T : Number => (a, b) => AreEqual(a.RealPart, b.RealPart) && AreEqual(a.ImaginaryPart, b.ImaginaryPart) ); -#pragma warning disable CS1591 + // These compute rather than build: unlike the operators on Entity, which assemble a + // Sumf or a Mulf to be evaluated later, these are arithmetic on numbers and hand back + // the number that results. + + /// Their sum, computed. public static Number operator +(Number a, Number b) => OpSum(a, b); + + /// Their difference, computed. public static Number operator -(Number a, Number b) => OpSub(a, b); + + /// Their product, computed. public static Number operator *(Number a, Number b) => OpMul(a, b); + + /// Their quotient, computed. public static Number operator /(Number a, Number b) => OpDiv(a, b); + + /// The operand itself; unary plus changes nothing. public static Number operator +(Number a) => a; + + /// Its negation, computed. public static Number operator -(Number a) => OpMul(-1, a); -#pragma warning restore CS1591 /// /// Gets all n-th roots of a number, diff --git a/Sources/AngouriMath/Core/Entity/Entity.ImplicitOperators.cs b/Sources/AngouriMath/Core/Entity/Entity.ImplicitOperators.cs index 9f78dcc67..d0c44e4b6 100644 --- a/Sources/AngouriMath/Core/Entity/Entity.ImplicitOperators.cs +++ b/Sources/AngouriMath/Core/Entity/Entity.ImplicitOperators.cs @@ -11,25 +11,76 @@ namespace AngouriMath { partial record Entity { -#pragma warning disable CS1591 + // A number written where an expression is expected becomes a literal node of the + // narrowest kind that holds it exactly: the integral types become Integer, a ratio + // becomes Rational, and the rest become Real. This is what lets `x + 1` and + // `MathS.Sin(2)` be written at all. + + /// The number as an literal. public static implicit operator Entity(sbyte value) => Number.Integer.Create(value); + + /// The number as an literal. public static implicit operator Entity(byte value) => Number.Integer.Create(value); + + /// The number as an literal. public static implicit operator Entity(short value) => Number.Integer.Create(value); + + /// The number as an literal. public static implicit operator Entity(ushort value) => Number.Integer.Create(value); + + /// The number as an literal. public static implicit operator Entity(int value) => Number.Integer.Create(value); + + /// The number as an literal. public static implicit operator Entity(uint value) => Number.Integer.Create(value); + + /// The number as an literal. public static implicit operator Entity(long value) => Number.Integer.Create(value); + + /// The number as an literal. public static implicit operator Entity(ulong value) => Number.Integer.Create(value); + + /// + /// The integer as an literal, of any size — this is the + /// conversion to reach for where a value will not fit in a . + /// public static implicit operator Entity(EInteger value) => Number.Integer.Create(value); + + /// The ratio as a literal, exactly. public static implicit operator Entity(ERational value) => Number.Rational.Create(value); + + /// The decimal as a literal. public static implicit operator Entity(EDecimal value) => Number.Real.Create(value); + + /// + /// The value as a literal, read as the binary + /// it is — so 0.1f becomes the value that literal holds + /// and not one tenth. Write 0.1m, or a , for the exact + /// number. + /// public static implicit operator Entity(float value) => Number.Real.Create(EDecimal.FromSingle(value)); + + /// + /// The value as a literal, read as the binary + /// it is rather than as the decimal it was written as. + /// public static implicit operator Entity(double value) => Number.Real.Create(EDecimal.FromDouble(value)); + + /// + /// The value as a literal. A is + /// decimal already, so this one keeps the digits that were written. + /// public static implicit operator Entity(decimal value) => Number.Real.Create(EDecimal.FromDecimal(value)); + + /// + /// The .NET complex number as a literal, through its two + /// parts and their precision. + /// public static implicit operator Entity(System.Numerics.Complex value) => Number.Complex.Create(EDecimal.FromDouble(value.Real), EDecimal.FromDouble(value.Imaginary)); + + /// The big integer as an literal, exactly. public static implicit operator Entity(System.Numerics.BigInteger bigInt) => Number.Integer.Create(EInteger.FromBytes(bigInt.ToByteArray(), littleEndian: true)); -#pragma warning restore CS1591 } } diff --git a/Sources/AngouriMath/Core/Entity/Omni/Entity.Matrix.cs b/Sources/AngouriMath/Core/Entity/Omni/Entity.Matrix.cs index 5fd80908f..c8f2ec06a 100644 --- a/Sources/AngouriMath/Core/Entity/Omni/Entity.Matrix.cs +++ b/Sources/AngouriMath/Core/Entity/Omni/Entity.Matrix.cs @@ -71,35 +71,83 @@ internal Matrix Elementwise(Matrix other, Func operation /// protected override Entity[] InitDirectChildren() => InnerMatrix.Iterate().Select(tup => tup.Value).ToArray(); -#pragma warning disable CS1591 + /// + /// Arithmetic on as the tensor library wants it, so that a + /// matrix can hold expressions rather than numbers. + /// + /// + /// Every operation here normalises its result with . + /// Without it an entry would accumulate the whole history of the operations that + /// built it — a determinant over a 4×4 of symbols is a sum of products of sums, and + /// left unnormalised it grows past being readable or comparable. + /// public readonly struct EntityTensorWrapperOperations : IOperations { + /// Their sum, normalised. public Entity Add(Entity a, Entity b) => (a + b).InnerSimplified; + + /// Their difference, normalised. public Entity Subtract(Entity a, Entity b) => (a - b).InnerSimplified; + + /// Their product, normalised. public Entity Multiply(Entity a, Entity b) => (a * b).InnerSimplified; + + /// Its negation, normalised. public Entity Negate(Entity a) => (-a).InnerSimplified; + + /// Their quotient, normalised. public Entity Divide(Entity a, Entity b) => (a / b).InnerSimplified; + + /// The multiplicative identity, which is the integer one. public Entity CreateOne() => Number.Integer.One; + + /// The additive identity, which is the integer zero. public Entity CreateZero() => Number.Integer.Zero; + + /// + /// The entity itself. An is immutable, so there is nothing + /// to copy and sharing it is safe. + /// public Entity Copy(Entity a) => a; + #pragma warning disable CA1822 // Mark members as static + /// The entity itself, as the interface requires it be passed through. public Entity Forward(Entity a) => a; #pragma warning restore CA1822 // Mark members as static + + /// + /// Whether the two are the same expression. This is structural equality and not + /// mathematical: x + x and 2 * x are not equal here. + /// public bool AreEqual(Entity a, Entity b) => a == b; + + /// + /// Whether the entity is the literal zero — again structurally, so an expression + /// that happens to be zero everywhere is not recognised as one. + /// public bool IsZero(Entity a) => a == 0; + + /// The entity written out, as writes it. public string ToString(Entity a) => a.Stringize(); + /// + /// Not supported: an expression has no byte form here. + /// + /// Always. public byte[] Serialize(Entity a) { throw new NotSufficientlySupportedException("Serializing a matrix is not supported"); } + /// + /// Not supported: an expression has no byte form here. + /// + /// Always. public Entity Deserialize(byte[] data) { throw new NotSufficientlySupportedException("Deserializing a matrix is not supported"); } } -#pragma warning restore CS1591 /// /// The number of columns of a matrix. It is 1 for vectors. /// From efe2a36eff5190288fcb38127c44d057966026c7 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 14 Aug 2026 22:02:19 +0000 Subject: [PATCH 3/3] Document Real, Rational and Integer's operators, and pin what comparing NaN does (#585) Three more suppression regions, leaving one -- Number's, which is left alone deliberately because the BigInteger fix is editing inside it and two branches on one block conflict for the second one. Most of these are the arithmetic and the conversions, and they say the two things a caller cannot see from the signature: comparison and arithmetic here are on the value and answer a number or a bool, where the same operators on Entity build a node to be evaluated later; and downcasting, which is on by default, means an exactly whole value handed to Real or Rational arrives as an Integer, so the runtime type is narrower than the declared one. Three answers that surprise, now written where they are: Integer / Integer is not integer division. 1 / 2 is a half, which is why it returns Real: a Rational wherever the division is not exact, and NaN where the divisor is zero. IntegerDiv is the truncating one. Rational / Rational returns Real for the same reason -- every quotient of two ratios is a ratio except division by zero, and NaN is not a ratio. A ratio arrives in lowest terms with a positive denominator: 2/4 is 1/2 and 1/(-2) is (-1)/2. Measured rather than assumed. And one that surprised me while writing it down. Real's comparison operators order NaN instead of refusing to: NaN > 1 and NaN >= 1 are both true, and 1 < NaN is true as well. It is a total order with NaN at the top, and it is not what double does, where every comparison against NaN is false. The first draft of this documentation said the opposite, from habit, and measuring at +1, -1, 0 and NaN is what caught it. RealComparisonTest pins it, because documentation now asserts it. Whether that ordering is the right answer is a separate question from documenting it, and this changes no behaviour. Measured: suite 7123 passed / 0 failed, 6 of them new. The build is the other check -- CS1591 is an error here, so a pragma cannot come out unless everything under it is documented. https://github.com/asc-community/AngouriMath/issues/585 Co-authored-by: Claude Opus 5 --- .../Entity.Continuous.Integer.Definition.cs | 62 ++++++++++- .../Entity.Continuous.Rational.Definition.cs | 86 ++++++++++++++- .../Entity.Continuous.Real.Definition.cs | 104 +++++++++++++++++- .../UnitTests/Core/RealComparisonTest.cs | 66 +++++++++++ 4 files changed, 307 insertions(+), 11 deletions(-) create mode 100644 Sources/Tests/UnitTests/Core/RealComparisonTest.cs diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Integer.Definition.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Integer.Definition.cs index bbd1795cd..2a7bb9229 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Integer.Definition.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Integer.Definition.cs @@ -121,15 +121,47 @@ internal static bool TryParse(string s, /// public Integer IntegerDiv(Integer a) => EInteger.Divide(a.EInteger); -#pragma warning disable CS1591 + // Comparison here is on the value and answers a bool, where the same operators + // on Entity build an inequality node instead. + + /// Whether the first is strictly greater. public static bool operator >(Integer a, Integer b) => a.EInteger.CompareTo(b.EInteger) > 0; + + /// Whether the first is greater or they are equal. public static bool operator >=(Integer a, Integer b) => a.EInteger.CompareTo(b.EInteger) >= 0; + + /// Whether the first is strictly less. public static bool operator <(Integer a, Integer b) => a.EInteger.CompareTo(b.EInteger) < 0; + + /// Whether the first is less or they are equal. public static bool operator <=(Integer a, Integer b) => a.EInteger.CompareTo(b.EInteger) <= 0; + + /// + /// Negative, zero or positive as this is less than, equal to or greater than + /// , which is what sorting wants. + /// + /// + /// Thrown where is , rather than + /// sorting it first as usually would. + /// public int CompareTo(Integer? other) => other is null ? throw new System.ArgumentNullException() : EInteger.CompareTo(other.EInteger); + + /// Their sum, exactly and at any size. public static Integer operator +(Integer a, Integer b) => OpSum(a, b); + + /// Their difference, exactly and at any size. public static Integer operator -(Integer a, Integer b) => OpSub(a, b); + + /// Their product, exactly and at any size. public static Integer operator *(Integer a, Integer b) => OpMul(a, b); + + /// + /// Their quotient — not integer division. 1 / 2 is a half and not + /// zero, which is why the result is a : it is a + /// wherever the division is not exact, and + /// where the divisor is zero. For the truncating kind, + /// see . + /// public static Real operator /(Integer a, Integer b) => (Real)OpDiv(a, b); /// /// The floored remainder, which takes the sign of the divisor: -7 % 3 is 2 and @@ -145,18 +177,44 @@ internal static bool TryParse(string s, .IsZero || truncated.Sign == b.EInteger.Sign ? truncated : truncated.Add(b.EInteger); + /// The operand itself; unary plus changes nothing. public static Integer operator +(Integer a) => a; + + /// Its negation. public static Integer operator -(Integer a) => OpMul(MinusOne, a); + + // Nothing to downcast to here, so unlike the conversions on Real and Rational + // these give exactly what they say. + + /// The number as an . public static implicit operator Integer(sbyte value) => Create(value); + + /// The number as an . public static implicit operator Integer(byte value) => Create(value); + + /// The number as an . public static implicit operator Integer(short value) => Create(value); + + /// The number as an . public static implicit operator Integer(ushort value) => Create(value); + + /// The number as an . public static implicit operator Integer(int value) => Create(value); + + /// The number as an . public static implicit operator Integer(uint value) => Create(value); + + /// The number as an . public static implicit operator Integer(long value) => Create(value); + + /// The number as an . public static implicit operator Integer(ulong value) => Create(value); + + /// + /// The integer as an , of any size — this is the conversion + /// to reach for where a value will not fit in a . + /// public static implicit operator Integer(EInteger value) => Create(value); -#pragma warning restore CS1591 } } diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rational.Definition.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rational.Definition.cs index 92cd286db..b83d2aef3 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rational.Definition.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rational.Definition.cs @@ -48,18 +48,23 @@ private protected Rational(ERational value) public Integer Denominator => denominator.GetValue(static @this => @this.ERational.Denominator, this); private LazyPropertyA denominator; -#pragma warning disable CS1591 - + /// Takes the ratio out, so that var (r) = rational; works. + /// The value as an . public void Deconstruct(out ERational rational) => rational = ERational; + /// + /// Takes the two parts out, so that var (num, den) = rational; works. + /// They are in lowest terms and the denominator is positive, since that is the + /// only form a is ever built in. + /// + /// The numerator. + /// The denominator. public void Deconstruct(out Integer numerator, out Integer denominator) { numerator = Numerator; denominator = Denominator; } -#pragma warning restore CS1591 - /// public override bool IsExact => true; @@ -161,17 +166,52 @@ internal static bool TryParse(string s, } } -#pragma warning disable CS1591 + // Comparison here is on the value and answers a bool, where the same operators + // on Entity build an inequality node instead. + + /// Whether the first is strictly greater. public static bool operator >(Rational a, Rational b) => a.ERational.CompareTo(b.ERational) > 0; + + /// Whether the first is greater or they are equal. public static bool operator >=(Rational a, Rational b) => a.ERational.CompareTo(b.ERational) >= 0; + + /// Whether the first is strictly less. public static bool operator <(Rational a, Rational b) => a.ERational.CompareTo(b.ERational) < 0; + + /// Whether the first is less or they are equal. public static bool operator <=(Rational a, Rational b) => a.ERational.CompareTo(b.ERational) <= 0; + + /// + /// Negative, zero or positive as this is less than, equal to or greater than + /// , which is what sorting wants. + /// + /// + /// Thrown where is , rather than + /// sorting it first as usually would. + /// public int CompareTo(Rational? other) => other is null ? throw new System.ArgumentNullException() : ERational.CompareTo(other.ERational); + + /// Their sum, exactly. public static Rational operator +(Rational a, Rational b) => OpSum(a, b); + + /// Their difference, exactly. public static Rational operator -(Rational a, Rational b) => OpSub(a, b); + + /// Their product, exactly. public static Rational operator *(Rational a, Rational b) => OpMul(a, b); + + /// + /// Their quotient. rather than , + /// because dividing by zero has to go somewhere and the answer is + /// , which is not a ratio. Every other quotient of two + /// ratios is a ratio and arrives as one. + /// public static Real operator /(Rational a, Rational b) => (Real)OpDiv(a, b); + + /// The operand itself; unary plus changes nothing. public static Rational operator +(Rational a) => a; + + /// Its negation. public static Rational operator -(Rational a) => OpMul(Integer.MinusOne, a); /// @@ -191,26 +231,60 @@ internal static bool TryParse(string s, .IsZero || truncated.IsNegative == b.ERational.IsNegative ? truncated : truncated + b; + // As elsewhere, downcasting is on by default and a whole value arrives as an + // Integer, so the runtime type is narrower than the declared one. + + /// The number as a . public static implicit operator Rational(sbyte value) => (long)value; + + /// The number as a . public static implicit operator Rational(byte value) => (ulong)value; + + /// The number as a . public static implicit operator Rational(short value) => (long)value; + + /// The number as a . public static implicit operator Rational(ushort value) => (ulong)value; + + /// The number as a . public static implicit operator Rational(int value) => (long)value; + + /// The number as a . public static implicit operator Rational(uint value) => (ulong)value; + + /// + /// The number as a — an at runtime + /// while downcasting is enabled. + /// public static implicit operator Rational(long value) => MathS.Settings.DowncastingEnabled ? Integer.Create(value) : new Rational(value); + + /// + /// The number as a — an at runtime + /// while downcasting is enabled. + /// public static implicit operator Rational(ulong value) => MathS.Settings.DowncastingEnabled ? Integer.Create(value) : new Rational(value); + + /// + /// The integer as a over one — an + /// at runtime while downcasting is enabled. + /// public static implicit operator Rational(EInteger value) => MathS.Settings.DowncastingEnabled ? Integer.Create(value) : new Rational(ERational.FromEInteger(value)); + + /// + /// The ratio as a , in lowest terms and with a positive + /// denominator: 2/4 arrives as 1/2 and 1/(-2) as + /// (-1)/2. + /// public static implicit operator Rational(ERational value) => Create(value); -#pragma warning restore CS1591 } } } diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Real.Definition.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Real.Definition.cs index d2b4c33ab..58a99a734 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Real.Definition.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Real.Definition.cs @@ -110,17 +110,63 @@ internal static bool TryParse(string s, /// public double AsDouble() => EDecimal.ToDouble(); -#pragma warning disable CS1591 + // Comparison here is on the value and answers a bool, unlike the operators on + // Entity, which build an inequality node to be solved or evaluated later. + // + // These order NaN rather than refusing to: NaN sorts above every number, so + // NaN > 1 and NaN >= 1 are both true and 1 < NaN is true as well. That is a + // total order and it is not what double does, where every comparison against + // NaN is false. Measured at +1, -1 and NaN rather than assumed, because the + // IEEE habit is the one a reader arrives with. + + /// + /// Whether the first is strictly greater. counts as greater + /// than every number, so NaN > x holds for any real x. + /// public static bool operator >(Real a, Real b) => a.EDecimal.GreaterThan(b.EDecimal); + + /// Whether the first is greater or they are equal. public static bool operator >=(Real a, Real b) => a.EDecimal.GreaterThanOrEquals(b.EDecimal); + + /// + /// Whether the first is strictly less. is less than nothing, + /// and every number is less than it. + /// public static bool operator <(Real a, Real b) => a.EDecimal.LessThan(b.EDecimal); + + /// Whether the first is less or they are equal. public static bool operator <=(Real a, Real b) => a.EDecimal.LessThanOrEquals(b.EDecimal); + + /// + /// Negative, zero or positive as this is less than, equal to or greater than + /// , which is what sorting wants. + /// + /// + /// Thrown where is . Unlike + /// 's usual contract, null does not sort first + /// here; it is refused. + /// public int CompareTo(Real? other) => other is null ? throw new System.ArgumentNullException() : EDecimal.CompareTo(other.EDecimal); + + /// Their sum, computed. public static Real operator +(Real a, Real b) => OpSum(a, b); + + /// Their difference, computed. public static Real operator -(Real a, Real b) => OpSub(a, b); + + /// Their product, computed. public static Real operator *(Real a, Real b) => OpMul(a, b); + + /// + /// Their quotient, computed. Division by zero gives rather + /// than throwing, so a quotient always has an answer of some kind. + /// public static Real operator /(Real a, Real b) => OpDiv(a, b).Downcast(); + + /// The operand itself; unary plus changes nothing. public static Real operator +(Real a) => a; + + /// Its negation, computed. public static Real operator -(Real a) => OpMul(Integer.MinusOne, a); /// /// The floored remainder, which takes the sign of the divisor: -7 % 3 is 2 and @@ -138,22 +184,74 @@ internal static bool TryParse(string s, .IsZero || truncated.IsNegative == b.EDecimal.IsNegative ? truncated : truncated.Add(b.EDecimal, MathS.Settings.DecimalPrecisionContext); + // With MathS.Settings.DowncastingEnabled, which is the default, a value that is + // exactly whole arrives as an Integer and one that is exactly a ratio as a + // Rational. The declared type is Real either way, so the difference is in the + // runtime type and in how the number prints, not in the signature. + + /// The number as a . public static implicit operator Real(sbyte value) => (long)value; + + /// The number as a . public static implicit operator Real(byte value) => (ulong)value; + + /// The number as a . public static implicit operator Real(short value) => (long)value; + + /// The number as a . public static implicit operator Real(ushort value) => (ulong)value; + + /// The number as a . public static implicit operator Real(int value) => (long)value; + + /// The number as a . public static implicit operator Real(uint value) => (ulong)value; + + /// + /// The number as a — an at runtime + /// while downcasting is enabled. + /// public static implicit operator Real(long value) => MathS.Settings.DowncastingEnabled ? Integer.Create(value) : new Real(value); + + /// + /// The number as a — an at runtime + /// while downcasting is enabled. + /// public static implicit operator Real(ulong value) => MathS.Settings.DowncastingEnabled ? Integer.Create(value) : new Real(value); + + /// + /// The integer as a — an at runtime + /// while downcasting is enabled. + /// public static implicit operator Real(EInteger value) => MathS.Settings.DowncastingEnabled ? Integer.Create(value) : new Real(value); + + /// + /// The ratio as a . While downcasting is enabled it stays a + /// and so stays exact; with it off the ratio is evaluated + /// to the working precision and a third becomes a finite decimal. + /// public static implicit operator Real(ERational value) => MathS.Settings.DowncastingEnabled ? Rational.Create(value) : Create(value.ToEDecimal(MathS.Settings.DecimalPrecisionContext)); + + /// The decimal as a . public static implicit operator Real(EDecimal value) => Create(value); + + /// + /// The value as a , read as the binary + /// it is — so 0.1f becomes the value that literal holds, not one tenth. + /// public static implicit operator Real(float value) => Create(EDecimal.FromSingle(value)); + + /// + /// The value as a , read as the binary + /// it is rather than as the decimal it was written as. + /// public static implicit operator Real(double value) => Create(EDecimal.FromDouble(value)); - public static implicit operator Real(decimal value) => Create(EDecimal.FromDecimal(value)); -#pragma warning restore CS1591 + /// + /// The value as a . A is decimal + /// already, so this one keeps the digits that were written. + /// + public static implicit operator Real(decimal value) => Create(EDecimal.FromDecimal(value)); } } } diff --git a/Sources/Tests/UnitTests/Core/RealComparisonTest.cs b/Sources/Tests/UnitTests/Core/RealComparisonTest.cs new file mode 100644 index 000000000..afb1656b8 --- /dev/null +++ b/Sources/Tests/UnitTests/Core/RealComparisonTest.cs @@ -0,0 +1,66 @@ +// +// Copyright (c) 2019-2026 Angouri. +// AngouriMath is licensed under MIT. +// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. +// Website: https://am.angouri.org. +// + +using AngouriMath; +using Xunit; +using static AngouriMath.Entity.Number; + +namespace AngouriMath.Tests.Core +{ + /// + /// How 's comparison operators order , which is not + /// how does and is now written into their documentation. + /// + /// + /// Pinned because it is surprising and because the documentation asserts it. A reader + /// arrives with the IEEE habit, where every comparison against NaN is false; here the + /// comparison is a total order and NaN sits at the top of it. + /// + [Trait("Area", "Core")] + public sealed class RealComparisonTest + { + [Theory] + [InlineData(1)] + [InlineData(-1)] + [InlineData(0)] + [InlineData(100000)] + public void NaNIsAboveEveryNumber(int number) + { + Real value = number; + Assert.True(Real.NaN > value); + Assert.True(Real.NaN >= value); + Assert.False(Real.NaN < value); + Assert.False(Real.NaN <= value); + + Assert.True(value < Real.NaN); + Assert.False(value > Real.NaN); + } + + /// And the ordinary order is the ordinary one. + [Fact] + public void AndTheRestOrdersAsExpected() + { + Real one = 1, two = 2; + Assert.True(two > one); + Assert.True(one < two); + Assert.True(one <= one); + Assert.True(one >= one); + Assert.False(one > one); + } + + /// + /// Division by zero answers rather than throwing, so a quotient + /// always has an answer of some kind. + /// + [Fact] + public void DivisionByZeroIsNaNRatherThanAThrow() + { + Real one = 1, zero = 0; + Assert.True((one / zero).IsNaN); + } + } +}