diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index a53b2ebec..486497a5c 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -194,6 +194,18 @@ and `Simplify` passed that on as `not x = 0`, though at `x = 0` the input is `-y
| `"sgn(NaN)".ToEntity().Evaled` | `1` | `NaN` |
| `"sgn(-1 provided false)".ToEntity().Evaled` | `1` | `NaN` |
+### A negation of a signed term is printed in brackets
+
+**Silent: `-(-2/3)` prints as `-(-2/3)`, where it printed as `--2/3`.** A negation prints as a bare `-` in front of what
+it negates, and a negative integer was bracketed by its priority, but a negative rational, a negation, and `+oo` were
+not. `--5/4` reads back as `-(-5) / 4`, which has the same value as a different expression, and is easy to misread
+([#2264](https://github.com/asc-community/AngouriMath/issues/2264)).
+
+| Input | Was (2.6.0) | Now |
+|---|---|---|
+| `"sinh(-5/4)".ToEntity().ToString()` | `(e ^ (-5/4) - e ^ (--5/4)) / 2` | `(e ^ (-5/4) - e ^ (-(-5/4))) / 2` |
+| `new Entity.Mulf(-1, new Entity.Mulf(-1, x)).ToString()` | `--x` | `-(-x)` |
+
### A matrix is divided, raised to a power and compared as a matrix
**Silent: wrong answers about matrices become right, or stay written.** Where an operation had no answer of its own
diff --git a/Sources/.editorconfig b/Sources/.editorconfig
index 0033226ad..faa5f1930 100644
--- a/Sources/.editorconfig
+++ b/Sources/.editorconfig
@@ -246,6 +246,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
[Tests/UnitTests/Convenience/RealAndImaginaryPartTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
+[Tests/UnitTests/Convenience/NegationOfASignedTermPrintsTest.cs]
+file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
+
[Tests/UnitTests/Convenience/DoubleFactorialTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
diff --git a/Sources/AngouriMath/Functions/Output/ToString/ToString.Arithmetics.Classes.cs b/Sources/AngouriMath/Functions/Output/ToString/ToString.Arithmetics.Classes.cs
index de663adce..338fd142c 100644
--- a/Sources/AngouriMath/Functions/Output/ToString/ToString.Arithmetics.Classes.cs
+++ b/Sources/AngouriMath/Functions/Output/ToString/ToString.Arithmetics.Classes.cs
@@ -34,10 +34,16 @@ public partial record Mulf
// unbracketed -- but `mod` shares this precedence level and is not associative, so a
// `mod` on the right must be bracketed or it is re-read as the outer operator:
// `2 * (3 mod 2)` is 2 and `(2 * 3) mod 2` is 0.
- private protected override string StringizeNode() =>
- (Multiplier is Integer(-1) && !MathS.Diagnostic.OutputExplicit ? "-"
- : Multiplier.Stringize(Multiplier.Priority < Priority) + " * ")
- + Multiplicand.Stringize(Multiplicand.Priority < Priority || Multiplicand is Modf);
+ private protected override string StringizeNode()
+ {
+ var multiplicand = Multiplicand.Stringize(Multiplicand.Priority < Priority || Multiplicand is Modf);
+ if (Multiplier is not Integer(-1) || MathS.Diagnostic.OutputExplicit)
+ return Multiplier.Stringize(Multiplier.Priority < Priority) + " * " + multiplicand;
+ // A negation of something printed with a sign of its own is bracketed: -(-2/3), not --2/3, which is
+ // read back as -(-2) / 3; and -(+oo), not -+oo. A negative rational is not bracketed by its priority,
+ // which is a quotient's, as a negative integer is. https://github.com/asc-community/AngouriMath/issues/2264
+ return multiplicand.StartsWith("-") || multiplicand.StartsWith("+") ? "-(" + multiplicand + ")" : "-" + multiplicand;
+ }
///
public override string ToString() => Stringize();
}
diff --git a/Sources/Tests/UnitTests/Convenience/NegationOfASignedTermPrintsTest.cs b/Sources/Tests/UnitTests/Convenience/NegationOfASignedTermPrintsTest.cs
new file mode 100644
index 000000000..9b586bf23
--- /dev/null
+++ b/Sources/Tests/UnitTests/Convenience/NegationOfASignedTermPrintsTest.cs
@@ -0,0 +1,51 @@
+//
+// 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 AngouriMath.Extensions;
+using Xunit;
+using static AngouriMath.Entity;
+using static AngouriMath.Entity.Number;
+
+namespace AngouriMath.Tests.Convenience
+{
+ ///
+ /// A negation of something printed with a sign of its own is bracketed, so it reads back as itself: -(-2/3)
+ /// printed as --2/3, read back as -(-2) / 3, and -(+oo) as -+oo.
+ /// #2264
+ ///
+ [Trait("Area", "Convenience")]
+ public sealed class NegationOfASignedTermPrintsTest
+ {
+ private static Entity Negated(Entity e) => new Mulf(Integer.Create(-1), e);
+
+ public static TheoryData Negations => new()
+ {
+ { Negated(Rational.Create(-2, 3)), "-(-2/3)" },
+ { Negated(Real.PositiveInfinity), "-(+oo)" },
+ { Negated(Negated(MathS.Var("x"))), "-(-x)" },
+ { Negated(Integer.Create(-3)), "-(-3)" },
+ { Negated(MathS.Var("x")), "-x" },
+ { Negated(Rational.Create(2, 3)), "-2/3" },
+ };
+
+ [Theory]
+ [MemberData(nameof(Negations))]
+ public void ItPrintsWithOneSignAndReadsBackToItsValue(Entity negation, string printed)
+ {
+ Assert.Equal(printed, negation.Stringize());
+ // The parser folds a negated number into the number, so -(-2/3) reads back as 2/3: the same value, and for
+ // a symbol the same tree.
+ var x = "7/10".ToEntity();
+ Assert.Equal(negation.Substitute("x", x).Evaled, negation.Stringize().ToEntity().Substitute("x", x).Evaled);
+ }
+
+ [Fact]
+ public void AHyperbolicSineOfANegativeRationalHasNoDoubleSign()
+ => Assert.DoesNotContain("--", "sinh(-5/4)".ToEntity().Stringize());
+ }
+}