diff --git a/SignificantNumber.Test/SignificantNumberFormatTests.cs b/SignificantNumber.Test/SignificantNumberFormatTests.cs
new file mode 100644
index 0000000..b2896c4
--- /dev/null
+++ b/SignificantNumber.Test/SignificantNumberFormatTests.cs
@@ -0,0 +1,138 @@
+// Copyright (c) 2023-2026 ktsu-dev contributors
+
+namespace SignificantNumber.Test;
+
+using System.Globalization;
+using ktsu.SignificantNumber;
+
+///
+/// Covers and
+/// , which must write every format writes.
+///
+[TestClass]
+public class SignificantNumberFormatTests
+{
+ private static readonly CultureInfo CommaDecimalCulture = CreateCommaDecimalCulture();
+
+ ///
+ /// Builds a culture that writes 1.234,5, like de-DE, without depending on the host's culture data.
+ ///
+ private static CultureInfo CreateCommaDecimalCulture()
+ {
+ CultureInfo culture = (CultureInfo)CultureInfo.InvariantCulture.Clone();
+ NumberFormatInfo format = culture.NumberFormat;
+ format.NumberDecimalSeparator = ",";
+ format.NumberGroupSeparator = ".";
+ format.CurrencyDecimalSeparator = ",";
+ format.CurrencyGroupSeparator = ".";
+ format.PercentDecimalSeparator = ",";
+ format.PercentGroupSeparator = ".";
+ return culture;
+ }
+
+ private static CultureInfo Culture(bool commaDecimal) => commaDecimal ? CommaDecimalCulture : CultureInfo.InvariantCulture;
+
+ private static SignificantNumber Parse(string text) =>
+ SignificantNumber.Parse(text, NumberStyles.Float, CultureInfo.InvariantCulture);
+
+ [TestMethod]
+ [DataRow("1234.5", "F2", false)]
+ [DataRow("1234.5", "N2", false)]
+ [DataRow("1234.5", "E2", false)]
+ [DataRow("1234.5", "C", false)]
+ [DataRow("1234.5", "C0", false)]
+ [DataRow("1234.5", "P", false)]
+ [DataRow("1234.5", "P1", false)]
+ [DataRow("1234.5", "0.00", false)]
+ [DataRow("1234.5", "#,##0.0", false)]
+ [DataRow("1234.5", "F2", true)]
+ [DataRow("1234.5", "N2", true)]
+ [DataRow("1234.5", "E2", true)]
+ [DataRow("1234.5", "C", true)]
+ [DataRow("1234.5", "P1", true)]
+ [DataRow("1234.5", "0.00", true)]
+ [DataRow("1234.5", "#,##0.0", true)]
+ [DataRow("-0.00125", "C", false)]
+ [DataRow("-0.00125", "P", false)]
+ [DataRow("-0.00125", "0.0000", true)]
+ [DataRow("0", "C", false)]
+ [DataRow("0", "P", true)]
+ [DataRow("100000000000000000000", "C", false)]
+ [DataRow("100000000000000000000", "#,##0.0", true)]
+ public void ToString_Format_MatchesDecimal(string value, string format, bool commaDecimal)
+ {
+ CultureInfo culture = Culture(commaDecimal);
+ string expected = decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture).ToString(format, culture);
+
+ Assert.AreEqual(expected, Parse(value).ToString(format, culture));
+ }
+
+ [TestMethod]
+ [DataRow("1234.5", "C", false)]
+ [DataRow("1234.5", "P1", true)]
+ [DataRow("1234.5", "0.00", true)]
+ [DataRow("1234.5", "N2", false)]
+ public void TryFormat_Format_MatchesDecimal(string value, string format, bool commaDecimal)
+ {
+ CultureInfo culture = Culture(commaDecimal);
+ string expected = decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture).ToString(format, culture);
+ Span destination = stackalloc char[64];
+
+ Assert.IsTrue(Parse(value).TryFormat(destination, out int charsWritten, format, culture));
+ Assert.AreEqual(expected, destination[..charsWritten].ToString());
+ }
+
+ [TestMethod]
+ [DataRow("F2")]
+ [DataRow("N2")]
+ [DataRow("C")]
+ [DataRow("0.00")]
+ public void TryFormat_DestinationTooSmall_ReturnsFalse(string format)
+ {
+ Span destination = stackalloc char[2];
+
+ Assert.IsFalse(Parse("1234.5").TryFormat(destination, out _, format, CultureInfo.InvariantCulture));
+ }
+
+ [TestMethod]
+ public void Interpolation_WithFormatSpecifier_UsesTheFormat()
+ {
+ SignificantNumber value = Parse("1234.5");
+
+ Assert.AreEqual("1234.5", string.Create(CultureInfo.InvariantCulture, $"{value:F1}"));
+ Assert.AreEqual("1,234.50", string.Format(CultureInfo.InvariantCulture, "{0:N2}", value));
+ Assert.AreEqual("1234.50", string.Create(CultureInfo.InvariantCulture, $"{value:0.00}"));
+ Assert.AreEqual("ยค1,234.50", string.Format(CultureInfo.InvariantCulture, "{0:C}", value));
+ }
+
+ [TestMethod]
+ public void ToString_CustomFormat_OutsideDecimalRange_FormatsLikeDouble()
+ {
+ SignificantNumber value = Parse("1E+30");
+
+ Assert.AreEqual(1e30.ToString("0.###E+0", CultureInfo.InvariantCulture), value.ToString("0.###E+0", CultureInfo.InvariantCulture));
+ }
+
+ [TestMethod]
+ public void ToString_CustomFormat_BeyondDecimalPrecision_FormatsLikeDouble()
+ {
+ SignificantNumber value = Parse("1E-40");
+
+ Assert.AreEqual(1e-40.ToString("0.##E+0", CultureInfo.InvariantCulture), value.ToString("0.##E+0", CultureInfo.InvariantCulture));
+ }
+
+ [TestMethod]
+ public void ToString_FormatWithoutProvider_UsesCurrentCulture()
+ {
+ CultureInfo original = CultureInfo.CurrentCulture;
+ try
+ {
+ CultureInfo.CurrentCulture = CommaDecimalCulture;
+ Assert.AreEqual("1234,50", Parse("1234.5").ToString("0.00"));
+ }
+ finally
+ {
+ CultureInfo.CurrentCulture = original;
+ }
+ }
+}
diff --git a/SignificantNumber/SignificantNumber.cs b/SignificantNumber/SignificantNumber.cs
index 9179e20..7509b7d 100644
--- a/SignificantNumber/SignificantNumber.cs
+++ b/SignificantNumber/SignificantNumber.cs
@@ -1004,14 +1004,97 @@ public TOutput To()
///
/// The format to use.
/// The string representation of the number.
- public string ToString(string format) => PreciseNumber.ToString(Value, format, null);
+ ///
+ public string ToString(string format) => ToString(format, null);
///
- public string ToString(string? format, IFormatProvider? formatProvider) => PreciseNumber.ToString(Value, format, formatProvider);
+ ///
+ /// The standard formats G, R, F, N and E are written from the exact value. Any other format, such as C, P or a
+ /// custom pattern like 0.00, is written the way writes the same value, or the way
+ /// does when the value is outside the range or precision of .
+ ///
+ public string ToString(string? format, IFormatProvider? formatProvider)
+ {
+ if (IsExactFormat(format))
+ {
+ return PreciseNumber.ToString(Value, format, formatProvider);
+ }
+
+ if (TryToExactDecimal(out decimal exact))
+ {
+ return exact.ToString(format, formatProvider);
+ }
+
+ return Value.To().ToString(format, formatProvider);
+ }
///
- public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) =>
- Value.TryFormat(destination, out charsWritten, format, provider);
+ ///
+ public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider)
+ {
+ if (IsExactFormat(format))
+ {
+ return Value.TryFormat(destination, out charsWritten, format, provider);
+ }
+
+ if (TryToExactDecimal(out decimal exact))
+ {
+ return exact.TryFormat(destination, out charsWritten, format, provider);
+ }
+
+ return Value.To().TryFormat(destination, out charsWritten, format, provider);
+ }
+
+ ///
+ /// Determines whether a format is one that writes from the exact value: empty, or
+ /// G, R, F, N or E, in either case, optionally followed by a precision.
+ ///
+ /// The format to check.
+ /// if handles ; otherwise, .
+ private static bool IsExactFormat(ReadOnlySpan format)
+ {
+ if (format.IsEmpty)
+ {
+ return true;
+ }
+
+ if (char.ToUpperInvariant(format[0]) is not ('G' or 'R' or 'F' or 'N' or 'E'))
+ {
+ return false;
+ }
+
+ foreach (char digit in format[1..])
+ {
+ if (!char.IsAsciiDigit(digit))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Converts the current instance to a when that loses nothing.
+ ///
+ /// The value as a , when the method returns .
+ /// if holds the value exactly; otherwise, .
+ private bool TryToExactDecimal(out decimal exact)
+ {
+ if (PreciseNumber.Abs(Value) > DecimalMaxValue)
+ {
+ exact = default;
+ return false;
+ }
+
+ exact = Value.To();
+ return exact.ToPreciseNumber() == Value;
+ }
+
+ ///
+ /// The largest , as a .
+ ///
+ private static readonly PreciseNumber DecimalMaxValue = decimal.MaxValue.ToPreciseNumber();
///
/// Determines whether the specified is canonical.