implemented ISignedNumber
.NET Test / .NET tests (push) Successful in 4m27s

This commit is contained in:
2026-09-13 23:08:27 +04:00
parent 082fd84c87
commit e51874b0c3
13 changed files with 1286 additions and 43 deletions
@@ -0,0 +1,202 @@
namespace Just.PreciseMath;
/// <remarks>
/// Generic conversions support the built-in binary floating, decimal, and integer types,
/// including native integers, 128-bit integers, and BigInteger. Integer output truncates
/// the exact component sum toward zero before range handling: checked throws, saturating
/// clamps, and truncating keeps the low destination-width bits. Nonfinite integer output
/// follows the destination's binary64 conversion policy. Decimal saturating/truncating
/// output clamps infinities and out-of-range values and maps NaN to zero. Binary floating
/// output rounds directly to the destination precision and preserves IEEE special values.
/// </remarks>
public readonly partial struct DoubleDouble
{
/// <summary>
/// Converts a supported number, rounding the high component and its residual to binary64.
/// Floating overflow produces signed infinity, including for BigInteger inputs.
/// Unsupported types are offered the corresponding source conversion hook, then rejected.
/// </summary>
public static DoubleDouble CreateChecked<TOther>(TOther value) where TOther : INumberBase<TOther>
{
if (GenericTryConvertFrom(value, out DoubleDouble result) || TOther.TryConvertToChecked(value, out result))
{
return result;
}
throw new NotSupportedException($"Conversion from {typeof(TOther)} to DoubleDouble is not supported.");
}
/// <summary>Converts with floating-point range semantics: overflow produces signed infinity.</summary>
public static DoubleDouble CreateSaturating<TOther>(TOther value) where TOther : INumberBase<TOther>
{
if (GenericTryConvertFrom(value, out DoubleDouble result) || TOther.TryConvertToSaturating(value, out result))
{
return result;
}
throw new NotSupportedException($"Conversion from {typeof(TOther)} to DoubleDouble is not supported.");
}
/// <summary>Converts with floating-point rounding; finite precision is not integer truncation.</summary>
public static DoubleDouble CreateTruncating<TOther>(TOther value) where TOther : INumberBase<TOther>
{
if (GenericTryConvertFrom(value, out DoubleDouble result) || TOther.TryConvertToTruncating(value, out result))
{
return result;
}
throw new NotSupportedException($"Conversion from {typeof(TOther)} to DoubleDouble is not supported.");
}
static bool INumberBase<DoubleDouble>.TryConvertFromChecked<TOther>(TOther value, out DoubleDouble result)
{
return GenericTryConvertFrom(value, out result);
}
static bool INumberBase<DoubleDouble>.TryConvertFromSaturating<TOther>(TOther value, out DoubleDouble result)
{
return GenericTryConvertFrom(value, out result);
}
static bool INumberBase<DoubleDouble>.TryConvertFromTruncating<TOther>(TOther value, out DoubleDouble result)
{
return GenericTryConvertFrom(value, out result);
}
static bool INumberBase<DoubleDouble>.TryConvertToChecked<TOther>(DoubleDouble value, out TOther result)
{
return GenericTryConvertTo(value, GenericConversionMode.Checked, out result);
}
static bool INumberBase<DoubleDouble>.TryConvertToSaturating<TOther>(DoubleDouble value, out TOther result)
{
return GenericTryConvertTo(value, GenericConversionMode.Saturating, out result);
}
static bool INumberBase<DoubleDouble>.TryConvertToTruncating<TOther>(DoubleDouble value, out TOther result)
{
return GenericTryConvertTo(value, GenericConversionMode.Truncating, out result);
}
private enum GenericConversionMode
{
Checked,
Saturating,
Truncating,
}
private static bool GenericIsInteger<TOther>()
{
return typeof(TOther) == typeof(byte) || typeof(TOther) == typeof(sbyte)
|| typeof(TOther) == typeof(short) || typeof(TOther) == typeof(ushort)
|| typeof(TOther) == typeof(int) || typeof(TOther) == typeof(uint)
|| typeof(TOther) == typeof(long) || typeof(TOther) == typeof(ulong)
|| typeof(TOther) == typeof(nint) || typeof(TOther) == typeof(nuint)
|| typeof(TOther) == typeof(Int128) || typeof(TOther) == typeof(UInt128)
|| typeof(TOther) == typeof(char) || typeof(TOther) == typeof(BigInteger);
}
private static bool GenericTryConvertFrom<TOther>(TOther value, out DoubleDouble result) where TOther : INumberBase<TOther>
{
// Never forward an unknown type to Create: the default two-sided dispatch would recurse.
if (value is DoubleDouble same)
{
result = same;
}
else if (value is double binary64)
{
result = new DoubleDouble(binary64);
}
else if (value is float binary32)
{
result = new DoubleDouble((double)binary32);
}
else if (value is Half binary16)
{
result = new DoubleDouble((double)binary16);
}
else if (value is decimal decimalValue)
{
result = new DoubleDouble(decimalValue);
}
else if (GenericIsInteger<TOther>())
{
result = PreciseMathHelper.ArithmeticFromRatio(BigInteger.CreateChecked(value), BigInteger.One);
}
else
{
result = Zero;
return false;
}
return true;
}
private static bool GenericTryConvertTo<TOther>(DoubleDouble value, GenericConversionMode mode, out TOther result) where TOther : INumberBase<TOther>
{
object converted;
if (typeof(TOther) == typeof(DoubleDouble))
{
converted = value;
}
else if (typeof(TOther) == typeof(double))
{
converted = (double)value;
}
else if (typeof(TOther) == typeof(float))
{
converted = (float)value;
}
else if (typeof(TOther) == typeof(Half))
{
if (value._low == 0.0)
{
converted = (Half)value._high;
}
else
{
(BigInteger numerator, BigInteger denominator) = value.ConversionFraction();
converted = (Half)ConversionRoundBinary(numerator, denominator, 11, -24);
}
}
else if (typeof(TOther) == typeof(decimal))
{
if (mode == GenericConversionMode.Checked)
{
converted = (decimal)value;
}
else if (double.IsNaN(value._high))
{
converted = decimal.Zero;
}
else
{
DoubleDouble maximum = new(decimal.MaxValue);
converted = value > maximum ? decimal.MaxValue
: value < -maximum ? decimal.MinValue : (decimal)value;
}
}
else if (GenericIsInteger<TOther>())
{
if (!double.IsFinite(value._high))
{
// BigInteger has no finite endpoints. Match its BCL nonfinite policy (throw).
// Bounded integer targets use the BCL's NaN and infinity mapping.
result = mode == GenericConversionMode.Checked ? TOther.CreateChecked(value._high)
: mode == GenericConversionMode.Saturating ? TOther.CreateSaturating(value._high)
: TOther.CreateTruncating(value._high);
return true;
}
// Truncate the exact sum first. Saturating clamps; truncating retains the
// low destination-width bits, as in BigInteger's generic conversion contract.
BigInteger integer = value.ConversionInteger();
result = mode == GenericConversionMode.Checked ? TOther.CreateChecked(integer)
: mode == GenericConversionMode.Saturating ? TOther.CreateSaturating(integer)
: TOther.CreateTruncating(integer);
return true;
}
else
{
result = default!;
return false;
}
result = (TOther)converted;
return true;
}
}
@@ -0,0 +1,143 @@
namespace Just.PreciseMath;
public readonly partial struct DoubleDouble
{
/// <summary>Gets the binary radix of the components.</summary>
public static int Radix => 2;
/// <summary>Returns the absolute value, preserving both components and canonicalizing NaN.</summary>
public static DoubleDouble Abs(DoubleDouble value)
{
if (IsNaN(value))
{
return NaN;
}
return IsNegative(value) ? -value : value;
}
/// <summary>Returns the operand with greater magnitude; ties prefer positive values and NaN propagates.</summary>
public static DoubleDouble MaxMagnitude(DoubleDouble x, DoubleDouble y)
{
if (IsNaN(x) || IsNaN(y))
{
return NaN;
}
DoubleDouble ax = Abs(x);
DoubleDouble ay = Abs(y);
return ax > ay || (ax == ay && !IsNegative(x)) ? x : y;
}
/// <summary>Returns the operand with lesser magnitude; ties prefer negative values and NaN propagates.</summary>
public static DoubleDouble MinMagnitude(DoubleDouble x, DoubleDouble y)
{
if (IsNaN(x) || IsNaN(y))
{
return NaN;
}
DoubleDouble ax = Abs(x);
DoubleDouble ay = Abs(y);
return ax < ay || (ax == ay && IsNegative(x)) ? x : y;
}
/// <summary>Returns the greater-magnitude operand, preferring a number over NaN and positive values on ties.</summary>
public static DoubleDouble MaxMagnitudeNumber(DoubleDouble x, DoubleDouble y)
{
if (IsNaN(x))
{
return y;
}
return IsNaN(y) ? x : MaxMagnitude(x, y);
}
/// <summary>Returns the lesser-magnitude operand, preferring a number over NaN and negative values on ties.</summary>
public static DoubleDouble MinMagnitudeNumber(DoubleDouble x, DoubleDouble y)
{
if (IsNaN(x))
{
return y;
}
return IsNaN(y) ? x : MinMagnitude(x, y);
}
/// <summary>Adds one using double-double arithmetic, including its overflow and nonfinite behavior.</summary>
public static DoubleDouble operator ++(DoubleDouble value)
{
return value + One;
}
/// <summary>Subtracts one using double-double arithmetic, including its overflow and nonfinite behavior.</summary>
public static DoubleDouble operator --(DoubleDouble value)
{
return value - One;
}
/// <summary>Tests normalization and canonical NaN and zero-residual bit patterns.</summary>
public static bool IsCanonical(DoubleDouble value)
{
DoubleDouble normalized = FromComponents(value._high, value._low);
return BitConverter.DoubleToInt64Bits(value._high) == BitConverter.DoubleToInt64Bits(normalized._high)
&& BitConverter.DoubleToInt64Bits(value._low) == BitConverter.DoubleToInt64Bits(normalized._low);
}
/// <summary>Returns false: this type has no complex values.</summary>
public static bool IsComplexNumber(DoubleDouble value)
{
return false;
}
/// <summary>Returns false: this type has no imaginary values.</summary>
public static bool IsImaginaryNumber(DoubleDouble value)
{
return false;
}
/// <summary>Tests whether the value is real, including infinities but excluding NaN.</summary>
public static bool IsRealNumber(DoubleDouble value)
{
return !IsNaN(value);
}
/// <summary>Tests the high component's sign bit, including positive zero.</summary>
public static bool IsPositive(DoubleDouble value)
{
return double.IsPositive(value._high);
}
/// <summary>Tests whether the normalized high component is a normal binary64 value.</summary>
public static bool IsNormal(DoubleDouble value)
{
return double.IsNormal(value._high);
}
/// <summary>Tests whether the normalized high component is subnormal.</summary>
public static bool IsSubnormal(DoubleDouble value)
{
return double.IsSubnormal(value._high);
}
/// <summary>Tests for either sign of zero.</summary>
public static bool IsZero(DoubleDouble value)
{
return value._high == 0.0 && value._low == 0.0;
}
/// <summary>Tests whether the exact sum of the normalized components is an integer.</summary>
public static bool IsInteger(DoubleDouble value)
{
// In a normalized pair, a fractional high cannot be made integral by
// its nonoverlapping residual. An integral high requires an integral low.
return double.IsInteger(value._high) && double.IsInteger(value._low);
}
/// <summary>Tests integer parity without discarding the residual above 2^53.</summary>
public static bool IsEvenInteger(DoubleDouble value)
{
return IsInteger(value) && double.IsOddInteger(value._high) == double.IsOddInteger(value._low);
}
/// <summary>Tests integer parity without discarding the residual above 2^53.</summary>
public static bool IsOddInteger(DoubleDouble value)
{
return IsInteger(value) && double.IsOddInteger(value._high) != double.IsOddInteger(value._low);
}
}
@@ -0,0 +1,222 @@
using System.Globalization;
namespace Just.PreciseMath;
public readonly partial struct DoubleDouble
{
/// <summary>Parses decimal text using the specified styles and culture (current when null).</summary>
/// <remarks>Supports all combinations of the decimal flags in NumberStyles.Any. The 2048-character limit,
/// exact coefficient conversion, signed zero, and overflow behavior of the provider-only parser apply.
/// Special values (including "inf") accept surrounding whitespace and culture-specific signs
/// independently of decimal style flags.</remarks>
/// <exception cref="ArgumentException">The style contains hexadecimal, binary, or undefined flags.</exception>
/// <exception cref="ArgumentNullException">The input is null.</exception>
/// <exception cref="FormatException">The input is invalid or exceeds 2048 characters.</exception>
public static DoubleDouble Parse(string s, NumberStyles style, IFormatProvider? provider = null)
{
ValidateParsingStyle(style);
ArgumentNullException.ThrowIfNull(s);
return Parse(s.AsSpan(), style, provider);
}
/// <summary>Parses decimal text using the specified styles and culture (current when null).</summary>
/// <exception cref="ArgumentException">The style contains hexadecimal, binary, or undefined flags.</exception>
/// <exception cref="FormatException">The input is invalid or exceeds 2048 characters.</exception>
public static DoubleDouble Parse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider = null)
{
if (!TryParse(s, style, provider, out DoubleDouble result))
{
throw new FormatException($"Invalid or unsupported DoubleDouble text (maximum {MaximumParsingLength} characters).");
}
return result;
}
/// <summary>Parses decimal text; returns false and Zero for null, invalid, or oversized input.</summary>
/// <exception cref="ArgumentException">The style contains hexadecimal, binary, or undefined flags.</exception>
public static bool TryParse(string? s, NumberStyles style, IFormatProvider? provider, out DoubleDouble result)
{
return TryParse(s.AsSpan(), style, provider, out result);
}
/// <summary>Parses decimal text; returns false and Zero for invalid or oversized input.</summary>
/// <remarks>Group sizes are not validated. Currency styles recognize currency separators, and number
/// separators before a currency symbol. No conversion through double or decimal is performed.</remarks>
/// <exception cref="ArgumentException">The style contains hexadecimal, binary, or undefined flags.</exception>
public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out DoubleDouble result)
{
ValidateParsingStyle(style);
result = Zero;
if (s.Length > MaximumParsingLength)
{
return false;
}
if ((style & NumberStyles.AllowLeadingWhite) != 0)
{
s = s.TrimStart();
}
if ((style & NumberStyles.AllowTrailingWhite) != 0)
{
s = s.TrimEnd();
}
if (s.IsEmpty)
{
return false;
}
NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
if (ParsingTrySpecial(s, info, out result))
{
return true;
}
bool negative = false;
bool signSeen = false;
bool parentheses = false;
bool currencySeen = false;
bool allowCurrency = (style & NumberStyles.AllowCurrencySymbol) != 0;
while (!s.IsEmpty)
{
if ((style & NumberStyles.AllowLeadingWhite) != 0 && char.IsWhiteSpace(s[0])
&& (!signSeen || currencySeen || info.NumberNegativePattern == 2))
{
s = s[1..];
}
else if (!signSeen && (style & NumberStyles.AllowLeadingSign) != 0
&& StyledConsumeSign(ref s, info, out negative))
{
signSeen = true;
}
else if (!signSeen && (style & NumberStyles.AllowParentheses) != 0 && s[0] == '(')
{
s = s[1..];
signSeen = negative = parentheses = true;
}
else if (allowCurrency && !currencySeen && StyledConsumeToken(ref s, info.CurrencySymbol))
{
currencySeen = true;
}
else
{
break;
}
}
// Normalize only syntax; preserve every digit for the exact-rational parser.
// Reserving the first character lets a trailing sign become a leading sign.
char[] buffer = new char[s.Length + 2];
int length = 1;
bool digitSeen = false;
bool decimalSeen = false;
string decimalSeparator = allowCurrency ? info.CurrencyDecimalSeparator : info.NumberDecimalSeparator;
string groupSeparator = allowCurrency ? info.CurrencyGroupSeparator : info.NumberGroupSeparator;
while (!s.IsEmpty)
{
if (s[0] is >= '0' and <= '9')
{
buffer[length++] = s[0];
s = s[1..];
digitSeen = true;
}
else if (!decimalSeen && (style & NumberStyles.AllowDecimalPoint) != 0
&& (StyledConsumeToken(ref s, decimalSeparator)
|| (allowCurrency && !currencySeen && StyledConsumeToken(ref s, info.NumberDecimalSeparator))))
{
decimalSeen = true;
buffer[length++] = '.';
}
else if (digitSeen && !decimalSeen && (style & NumberStyles.AllowThousands) != 0
&& (StyledConsumeToken(ref s, groupSeparator)
|| (allowCurrency && !currencySeen && StyledConsumeToken(ref s, info.NumberGroupSeparator))))
{
// Group sizes are deliberately not enforced, like standard numeric parsing.
}
else
{
break;
}
}
if (!digitSeen)
{
return false;
}
if (!s.IsEmpty && s[0] is 'e' or 'E' && (style & NumberStyles.AllowExponent) != 0)
{
buffer[length++] = 'E';
s = s[1..];
if (StyledConsumeSign(ref s, info, out bool negativeExponent))
{
buffer[length++] = negativeExponent ? '-' : '+';
}
int exponentStart = length;
while (!s.IsEmpty && s[0] is >= '0' and <= '9')
{
buffer[length++] = s[0];
s = s[1..];
}
if (length == exponentStart)
{
return false;
}
}
while (!s.IsEmpty)
{
if ((style & NumberStyles.AllowTrailingWhite) != 0 && char.IsWhiteSpace(s[0]))
{
s = s[1..];
}
else if (!signSeen && (style & NumberStyles.AllowTrailingSign) != 0
&& StyledConsumeSign(ref s, info, out negative))
{
signSeen = true;
}
else if (parentheses && s[0] == ')')
{
parentheses = false;
s = s[1..];
}
else if (allowCurrency && !currencySeen && StyledConsumeToken(ref s, info.CurrencySymbol))
{
currencySeen = true;
}
else
{
return false;
}
}
if (parentheses)
{
return false;
}
buffer[0] = '-';
int start = negative ? 0 : 1;
return TryParse(buffer.AsSpan(start, length - start), CultureInfo.InvariantCulture, out result);
}
private static void ValidateParsingStyle(NumberStyles style)
{
if ((style & ~NumberStyles.Any) != 0)
{
throw new ArgumentException("Only decimal NumberStyles flags are supported.", nameof(style));
}
}
private static bool StyledConsumeToken(ref ReadOnlySpan<char> text, string token)
{
if (token.Length == 0 || !text.StartsWith(token, StringComparison.Ordinal))
{
return false;
}
text = text[token.Length..];
return true;
}
private static bool StyledConsumeSign(ref ReadOnlySpan<char> text, NumberFormatInfo info, out bool negative)
{
negative = false;
if (StyledConsumeToken(ref text, info.PositiveSign))
{
return true;
}
negative = StyledConsumeToken(ref text, info.NegativeSign);
return negative;
}
}
@@ -5,8 +5,11 @@ namespace Just.PreciseMath;
/// <remarks>
/// Parsing supports decimal/scientific notation with ASCII digits, surrounding whitespace,
/// culture-specific signs and decimal separator, and NaN/infinity symbols (case-insensitive).
/// Group separators, currency, hexadecimal notation, and NumberStyles options are not supported.
/// Inputs are limited to 4096 characters, including surrounding whitespace, to bound work.
/// The case-insensitive alias "inf" also denotes infinity, with optional culture-specific sign.
/// Exact custom special symbols take precedence over the alias.
/// Provider-only overloads do not accept group separators or currency; explicit NumberStyles
/// overloads enable those decimal options. Hexadecimal and binary notation are not supported.
/// Inputs are limited to 2048 characters, including surrounding whitespace, to bound work.
/// The exact decimal coefficient and exponent are converted to a normalized high/low pair,
/// not through double or decimal. Components are rounded nearest, ties to even, then normalized;
/// a second rounding at the overflow midpoint is kept finite when the exact input is below it.
@@ -15,11 +18,11 @@ namespace Just.PreciseMath;
/// </remarks>
public readonly partial struct DoubleDouble : ISpanParsable<DoubleDouble>
{
private const int MaximumParsingLength = 4096;
private const int MaximumParsingLength = 2048;
/// <summary>Parses decimal/scientific text, using the current culture when provider is null.</summary>
/// <exception cref="ArgumentNullException">The input is null.</exception>
/// <exception cref="FormatException">The input is malformed, unsupported, or longer than 4096 characters.</exception>
/// <exception cref="FormatException">The input is malformed, unsupported, or longer than 2048 characters.</exception>
public static DoubleDouble Parse(string s, IFormatProvider? provider = null)
{
ArgumentNullException.ThrowIfNull(s);
@@ -27,12 +30,12 @@ public readonly partial struct DoubleDouble : ISpanParsable<DoubleDouble>
}
/// <summary>Parses decimal/scientific text, using the current culture when provider is null.</summary>
/// <exception cref="FormatException">The input is malformed, unsupported, or longer than 4096 characters.</exception>
/// <exception cref="FormatException">The input is malformed, unsupported, or longer than 2048 characters.</exception>
public static DoubleDouble Parse(ReadOnlySpan<char> s, IFormatProvider? provider = null)
{
if (!TryParse(s, provider, out DoubleDouble result))
{
throw new FormatException("Invalid or unsupported DoubleDouble text (maximum 4096 characters).");
throw new FormatException($"Invalid or unsupported DoubleDouble text (maximum {MaximumParsingLength} characters).");
}
return result;
}
@@ -69,33 +72,11 @@ public readonly partial struct DoubleDouble : ISpanParsable<DoubleDouble>
return false;
}
NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
// Custom symbols can themselves start with a numeric sign.
if (s.Equals(info.NaNSymbol, StringComparison.OrdinalIgnoreCase))
if (ParsingTrySpecial(s, info, out result))
{
result = NaN;
return true;
}
if (s.Equals(info.PositiveInfinitySymbol, StringComparison.OrdinalIgnoreCase))
{
result = new DoubleDouble(double.PositiveInfinity);
return true;
}
if (s.Equals(info.NegativeInfinitySymbol, StringComparison.OrdinalIgnoreCase))
{
result = new DoubleDouble(double.NegativeInfinity);
return true;
}
bool negative = ParsingConsumeSign(ref s, info);
if (s.Equals(info.NaNSymbol, StringComparison.OrdinalIgnoreCase))
{
result = NaN;
return true;
}
if (s.Equals(info.PositiveInfinitySymbol, StringComparison.OrdinalIgnoreCase))
{
result = new DoubleDouble(negative ? double.NegativeInfinity : double.PositiveInfinity);
return true;
}
BigInteger coefficient = BigInteger.Zero;
int significantDigits = 0;
@@ -190,6 +171,42 @@ public readonly partial struct DoubleDouble : ISpanParsable<DoubleDouble>
return true;
}
// Special symbols accept outer whitespace and culture signs independently of numeric styles.
private static bool ParsingTrySpecial(ReadOnlySpan<char> s, NumberFormatInfo info, out DoubleDouble result)
{
s = s.Trim();
result = Zero;
// Custom symbols can themselves start with a numeric sign.
if (s.Equals(info.NaNSymbol, StringComparison.OrdinalIgnoreCase))
{
result = NaN;
return true;
}
if (s.Equals(info.PositiveInfinitySymbol, StringComparison.OrdinalIgnoreCase))
{
result = new DoubleDouble(double.PositiveInfinity);
return true;
}
if (s.Equals(info.NegativeInfinitySymbol, StringComparison.OrdinalIgnoreCase))
{
result = new DoubleDouble(double.NegativeInfinity);
return true;
}
bool negative = ParsingConsumeSign(ref s, info);
if (s.Equals(info.NaNSymbol, StringComparison.OrdinalIgnoreCase))
{
result = NaN;
return true;
}
if (s.Equals(info.PositiveInfinitySymbol, StringComparison.OrdinalIgnoreCase)
|| s.Equals("inf", StringComparison.OrdinalIgnoreCase))
{
result = new DoubleDouble(negative ? double.NegativeInfinity : double.PositiveInfinity);
return true;
}
return false;
}
// A sign is optional; do not consume whitespace between it and the number.
private static bool ParsingConsumeSign(ref ReadOnlySpan<char> text, NumberFormatInfo info)
{
+2 -1
View File
@@ -13,7 +13,8 @@ namespace Just.PreciseMath;
/// </remarks>
public readonly partial struct DoubleDouble :
IEquatable<DoubleDouble>,
IEqualityOperators<DoubleDouble, DoubleDouble, bool>
IEqualityOperators<DoubleDouble, DoubleDouble, bool>,
ISignedNumber<DoubleDouble>
{
internal readonly double _high;
internal readonly double _low;
@@ -0,0 +1,20 @@
namespace Just.PreciseMath;
public readonly partial struct DoubleDouble : ISpanFormattable
{
/// <summary>Formats the exact component sum using the same G, E, and F formats as ToString.</summary>
/// <remarks>Uses the existing string formatter to preserve its rounding and culture contracts.
/// This implementation allocates; a short destination is unchanged and charsWritten is zero.</remarks>
/// <exception cref="FormatException">The format is unsupported or its precision exceeds 999.</exception>
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null)
{
charsWritten = 0;
string text = ToString(format.IsEmpty ? null : format.ToString(), provider);
if (!text.AsSpan().TryCopyTo(destination))
{
return false;
}
charsWritten = text.Length;
return true;
}
}