From 82d74f25900061bdf9c33246020c892ba3ce76c7 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 25 Feb 2024 02:20:06 +0800 Subject: [PATCH 1/7] Generic DiyFp --- .../src/System/Number.DiyFp.cs | 24 ++++++++++++++ .../src/System/Number.Formatting.cs | 33 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DiyFp.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DiyFp.cs index 268bd53695e7da..c910c0bcff874f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DiyFp.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DiyFp.cs @@ -68,6 +68,21 @@ public static DiyFp CreateAndGetBoundaries(Half value, out DiyFp mMinus, out Diy return result; } + // Computes the two boundaries of value. + // + // The bigger boundary (mPlus) is normalized. + // The lower boundary has the same exponent as mPlus. + // + // Precondition: + // The value encoded by value must be greater than 0. + public static DiyFp CreateAndGetBoundaries(TNumber value, out DiyFp mMinus, out DiyFp mPlus) + where TNumber : unmanaged, IBinaryFloatParseAndFormatInfo + { + var result = Create(value); + result.GetBoundaries(TNumber.DenormalMantissaBits, out mMinus, out mPlus); + return result; + } + public DiyFp(double value) { Debug.Assert(double.IsFinite(value)); @@ -89,6 +104,15 @@ public DiyFp(Half value) f = ExtractFractionAndBiasedExponent(value, out e); } + public static DiyFp Create(TNumber value) + where TNumber : unmanaged, IBinaryFloatParseAndFormatInfo + { + Debug.Assert(TNumber.IsFinite(value)); + Debug.Assert(value > TNumber.Zero); + ulong f = ExtractFractionAndBiasedExponent(value, out int e); + return new DiyFp(f, e); + } + public DiyFp(ulong f, int e) { this.f = f; diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs index 2a89bf96385294..5523116bb91987 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -2867,5 +2867,38 @@ private static uint ExtractFractionAndBiasedExponent(float value, out int expone return fraction; } + + private static ulong ExtractFractionAndBiasedExponent(TNumber value, out int exponent) + where TNumber : unmanaged, IBinaryFloatParseAndFormatInfo + { + ulong bits = TNumber.FloatToBits(value); + ulong fraction = (bits & TNumber.DenormalMantissaMask); + exponent = ((int)(bits >> TNumber.DenormalMantissaBits) & TNumber.InfinityExponent); + + if (exponent != 0) + { + // For normalized value, + // value = 1.fraction * 2^(exp - ExponentBias) + // = (1 + mantissa / 2^TrailingSignificandLength) * 2^(exp - ExponentBias) + // = (2^TrailingSignificandLength + mantissa) * 2^(exp - ExponentBias - TrailingSignificandLength) + // + // So f = (2^TrailingSignificandLength + mantissa), e = exp - ExponentBias - TrailingSignificandLength; + + fraction |= (1UL << TNumber.DenormalMantissaBits); + exponent -= TNumber.ExponentBias - TNumber.DenormalMantissaBits; + } + else + { + // For denormalized value, + // value = 0.fraction * 2^(MinBinaryExponent) + // = (mantissa / 2^TrailingSignificandLength) * 2^(MinBinaryExponent) + // = mantissa * 2^(MinBinaryExponent - TrailingSignificandLength) + // = mantissa * 2^(MinBinaryExponent - TrailingSignificandLength) + // So f = mantissa, e = MinBinaryExponent - TrailingSignificandLength + exponent = TNumber.MinBinaryExponent - TNumber.DenormalMantissaBits; + } + + return fraction; + } } } From 7139a31ab0e37d78cb2c7e30b5c51ea27e5b5ca6 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 25 Feb 2024 02:26:50 +0800 Subject: [PATCH 2/7] Generic Grisu3 --- .../src/System/Number.Grisu3.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Grisu3.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Grisu3.cs index 4a746b38cc2c31..b4e01cac10a976 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Grisu3.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Grisu3.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Numerics; namespace System { @@ -423,6 +424,41 @@ public static bool TryRunSingle(float value, int requestedDigits, ref NumberBuff return result; } + public static bool TryRun(TNumber value, int requestedDigits, ref NumberBuffer number) + where TNumber : unmanaged, IBinaryFloatParseAndFormatInfo + { + TNumber v = TNumber.IsNegative(value) ? -value : value; + + Debug.Assert(v > TNumber.Zero); + Debug.Assert(TNumber.IsFinite(v)); + + int length; + int decimalExponent; + bool result; + + if (requestedDigits == -1) + { + DiyFp w = DiyFp.CreateAndGetBoundaries(v, out DiyFp boundaryMinus, out DiyFp boundaryPlus).Normalize(); + result = TryRunShortest(in boundaryMinus, in w, in boundaryPlus, number.Digits, out length, out decimalExponent); + } + else + { + DiyFp w = DiyFp.Create(v).Normalize(); + result = TryRunCounted(in w, requestedDigits, number.Digits, out length, out decimalExponent); + } + + if (result) + { + Debug.Assert((requestedDigits == -1) || (length == requestedDigits)); + + number.Scale = length + decimalExponent; + number.Digits[length] = (byte)('\0'); + number.DigitsCount = length; + } + + return result; + } + // The counted version of Grisu3 only generates requestedDigits number of digits. // This version does not generate the shortest representation, and with enough requested digits 0.1 will at some point print as 0.9999999... // Grisu3 is too imprecise for real halfway cases (1.5 will not work) and therefore the rounding strategy for halfway cases is irrelevant. From c861be842cebc69bccfe409cde04f2f0661daad2 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 25 Feb 2024 02:39:39 +0800 Subject: [PATCH 3/7] Generic Dragon4 --- .../src/System/Number.Dragon4.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs index 10fbfcdbee5478..2a50766a9ff439 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs @@ -100,6 +100,37 @@ public static unsafe void Dragon4Single(float value, int cutoffNumber, bool isSi number.DigitsCount = length; } + public static unsafe void Dragon4(TNumber value, int cutoffNumber, bool isSignificantDigits, ref NumberBuffer number) + where TNumber : unmanaged, IBinaryFloatParseAndFormatInfo + { + TNumber v = TNumber.IsNegative(value) ? -value : value; + + Debug.Assert(v > TNumber.Zero); + Debug.Assert(TNumber.IsFinite(v)); + + ulong mantissa = ExtractFractionAndBiasedExponent(value, out int exponent); + + uint mantissaHighBitIdx; + bool hasUnequalMargins = false; + + if ((mantissa >> TNumber.DenormalMantissaBits) != 0) + { + mantissaHighBitIdx = TNumber.DenormalMantissaBits; + hasUnequalMargins = (mantissa == (1U << TNumber.DenormalMantissaBits)); + } + else + { + Debug.Assert(mantissa != 0); + mantissaHighBitIdx = (uint)BitOperations.Log2(mantissa); + } + + int length = (int)(Dragon4(mantissa, exponent, mantissaHighBitIdx, hasUnequalMargins, cutoffNumber, isSignificantDigits, number.Digits, out int decimalExponent)); + + number.Scale = decimalExponent + 1; + number.Digits[length] = (byte)('\0'); + number.DigitsCount = length; + } + // This is an implementation of the Dragon4 algorithm to convert a binary number in floating-point format to a decimal number in string format. // The function returns the number of digits written to the output buffer and the output is not NUL terminated. // From e00ff8a8d51e3c9dd6707e661ef582b18522c6da Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 25 Feb 2024 13:40:33 +0800 Subject: [PATCH 4/7] Add MaxRoundTripDigits to MaxPrecisionCustomFormat to FormatInfo --- .../System.Private.CoreLib/src/System/Double.cs | 4 ++++ .../System.Private.CoreLib/src/System/Half.cs | 4 ++++ .../src/System/Number.Parsing.cs | 12 ++++++++++++ .../System.Private.CoreLib/src/System/Single.cs | 4 ++++ 4 files changed, 24 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Double.cs b/src/libraries/System.Private.CoreLib/src/System/Double.cs index 26e65460ef24f3..45e312653185f3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -2335,6 +2335,10 @@ public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFo static ulong IBinaryFloatParseAndFormatInfo.FloatToBits(double value) => BitConverter.DoubleToUInt64Bits(value); + static int IBinaryFloatParseAndFormatInfo.MaxRoundTripDigits => 17; + + static int IBinaryFloatParseAndFormatInfo.MaxPrecisionCustomFormat => 15; + // // Helpers // diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index 533521970c7e23..dd0de7cdef4cc4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -2373,5 +2373,9 @@ public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFo static Half IBinaryFloatParseAndFormatInfo.BitsToFloat(ulong bits) => BitConverter.UInt16BitsToHalf((ushort)(bits)); static ulong IBinaryFloatParseAndFormatInfo.FloatToBits(Half value) => BitConverter.HalfToUInt16Bits(value); + + static int IBinaryFloatParseAndFormatInfo.MaxRoundTripDigits => 5; + + static int IBinaryFloatParseAndFormatInfo.MaxPrecisionCustomFormat => 5; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs index 852b979492c2d5..263f60ad0ba10f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -85,6 +85,18 @@ internal interface IBinaryFloatParseAndFormatInfo : IBinaryFloatingPointI static abstract TSelf BitsToFloat(ulong bits); static abstract ulong FloatToBits(TSelf value); + + // Maximum number of digits required to guarantee that any given floating point + // number can roundtrip. Some numbers may require less, but none will require more. + static abstract int MaxRoundTripDigits { get; } + + // SinglePrecisionCustomFormat and DoublePrecisionCustomFormat are used to ensure that + // custom format strings return the same string as in previous releases when the format + // would return x digits or less (where x is the value of the corresponding constant). + // In order to support more digits, we would need to update ParseFormatSpecifier to pre-parse + // the format and determine exactly how many digits are being requested and whether they + // represent "significant digits" or "digits after the decimal point". + static abstract int MaxPrecisionCustomFormat { get; } } internal static partial class Number diff --git a/src/libraries/System.Private.CoreLib/src/System/Single.cs b/src/libraries/System.Private.CoreLib/src/System/Single.cs index 88c923892309c1..85c6c162540270 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -2217,6 +2217,10 @@ public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFo static ulong IBinaryFloatParseAndFormatInfo.FloatToBits(float value) => BitConverter.SingleToUInt32Bits(value); + static int IBinaryFloatParseAndFormatInfo.MaxRoundTripDigits => 9; + + static int IBinaryFloatParseAndFormatInfo.MaxPrecisionCustomFormat => 7; + // // Helpers // From a6b06d04bb737a8b4aa323f17c18f4027e4f6ee9 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 25 Feb 2024 13:40:40 +0800 Subject: [PATCH 5/7] Generic FormatFloat --- .../src/System/Number.Formatting.cs | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs index 5523116bb91987..8e409b6930a2d0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -846,6 +846,122 @@ public static bool TryFormatHalf(Half value, ReadOnlySpan format, N return success; } + public static string FormatFloat(TNumber value, string? format, NumberFormatInfo info) + where TNumber : unmanaged, IBinaryFloatParseAndFormatInfo + { + var vlb = new ValueListBuilder(stackalloc char[CharStackBufferSize]); + string result = FormatFloat(ref vlb, value, format, info) ?? vlb.AsSpan().ToString(); + vlb.Dispose(); + return result; + } + + /// Formats the specified value according to the specified format and info. + /// + /// Non-null if an existing string can be returned, in which case the builder will be unmodified. + /// Null if no existing string was returned, in which case the formatted output is in the builder. + /// + private static unsafe string? FormatFloat(ref ValueListBuilder vlb, TNumber value, ReadOnlySpan format, NumberFormatInfo info) + where TNumber : unmanaged, IBinaryFloatParseAndFormatInfo + where TChar : unmanaged, IUtfChar + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + + if (!TNumber.IsFinite(value)) + { + if (TNumber.IsNaN(value)) + { + if (typeof(TChar) == typeof(char)) + { + return info.NaNSymbol; + } + else + { + vlb.Append(info.NaNSymbolTChar()); + return null; + } + } + + if (typeof(TChar) == typeof(char)) + { + return TNumber.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; + } + else + { + vlb.Append(TNumber.IsNegative(value) ? info.NegativeInfinitySymbolTChar() : info.PositiveInfinitySymbolTChar()); + return null; + } + } + + char fmt = ParseFormatSpecifier(format, out int precision); + byte* pDigits = stackalloc byte[TNumber.NumberBufferLength]; + + if (fmt == '\0') + { + precision = TNumber.MaxPrecisionCustomFormat; + } + + NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, TNumber.NumberBufferLength); + number.IsNegative = TNumber.IsNegative(value); + + // We need to track the original precision requested since some formats + // accept values like 0 and others may require additional fixups. + int nMaxDigits = GetFloatingPointMaxDigitsAndPrecision(fmt, ref precision, info, out bool isSignificantDigits); + + if ((value != default) && (!isSignificantDigits || !Grisu3.TryRun(value, precision, ref number))) + { + Dragon4(value, precision, isSignificantDigits, ref number); + } + + number.CheckConsistency(); + + // When the number is known to be roundtrippable (either because we requested it be, or + // because we know we have enough digits to satisfy roundtrippability), we should validate + // that the number actually roundtrips back to the original result. + + Debug.Assert(((precision != -1) && (precision < TNumber.MaxRoundTripDigits)) || (TNumber.FloatToBits(value) == TNumber.FloatToBits(NumberToFloat(ref number)))); + + if (fmt != 0) + { + if (precision == -1) + { + Debug.Assert((fmt == 'G') || (fmt == 'g') || (fmt == 'R') || (fmt == 'r')); + + // For the roundtrip and general format specifiers, when returning the shortest roundtrippable + // string, we need to update the maximum number of digits to be the greater of number.DigitsCount + // or SinglePrecision. This ensures that we continue returning "pretty" strings for values with + // less digits. One example this fixes is "-60", which would otherwise be formatted as "-6E+01" + // since DigitsCount would be 1 and the formatter would almost immediately switch to scientific notation. + + nMaxDigits = Math.Max(number.DigitsCount, TNumber.MaxRoundTripDigits); + } + NumberToString(ref vlb, ref number, fmt, nMaxDigits, info); + } + else + { + Debug.Assert(precision == TNumber.MaxPrecisionCustomFormat); + NumberToStringFormat(ref vlb, ref number, format, info); + } + return null; + } + + public static bool TryFormatFloat(TNumber value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) + where TNumber : unmanaged, IBinaryFloatParseAndFormatInfo + where TChar : unmanaged, IUtfChar + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + + var vlb = new ValueListBuilder(stackalloc TChar[CharStackBufferSize]); + string? s = FormatFloat(ref vlb, value, format, info); + + Debug.Assert(s is null || typeof(TChar) == typeof(char)); + bool success = s != null ? + TryCopyTo(s, destination, out charsWritten) : + vlb.TryCopyTo(destination, out charsWritten); + + vlb.Dispose(); + return success; + } + private static bool TryCopyTo(string source, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); From f3508bd9f80b7179498ebf945f58ba1fd2e0a766 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 25 Feb 2024 15:37:13 +0800 Subject: [PATCH 6/7] Adapt with existing FP types --- .../src/System/Double.cs | 12 +- .../System.Private.CoreLib/src/System/Half.cs | 12 +- .../src/System/Number.DiyFp.cs | 67 ---- .../src/System/Number.Dragon4.cs | 90 ----- .../src/System/Number.Formatting.cs | 362 +----------------- .../src/System/Number.Grisu3.cs | 103 ----- .../src/System/Single.cs | 12 +- 7 files changed, 25 insertions(+), 633 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Double.cs b/src/libraries/System.Private.CoreLib/src/System/Double.cs index 45e312653185f3..8d66af450f327a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -354,33 +354,33 @@ public override int GetHashCode() public override string ToString() { - return Number.FormatDouble(m_value, null, NumberFormatInfo.CurrentInfo); + return Number.FormatFloat(m_value, null, NumberFormatInfo.CurrentInfo); } public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format) { - return Number.FormatDouble(m_value, format, NumberFormatInfo.CurrentInfo); + return Number.FormatFloat(m_value, format, NumberFormatInfo.CurrentInfo); } public string ToString(IFormatProvider? provider) { - return Number.FormatDouble(m_value, null, NumberFormatInfo.GetInstance(provider)); + return Number.FormatFloat(m_value, null, NumberFormatInfo.GetInstance(provider)); } public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider) { - return Number.FormatDouble(m_value, format, NumberFormatInfo.GetInstance(provider)); + return Number.FormatFloat(m_value, format, NumberFormatInfo.GetInstance(provider)); } public bool TryFormat(Span destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan format = default, IFormatProvider? provider = null) { - return Number.TryFormatDouble(m_value, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten); + return Number.TryFormatFloat(m_value, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten); } /// public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan format = default, IFormatProvider? provider = null) { - return Number.TryFormatDouble(m_value, format, NumberFormatInfo.GetInstance(provider), utf8Destination, out bytesWritten); + return Number.TryFormatFloat(m_value, format, NumberFormatInfo.GetInstance(provider), utf8Destination, out bytesWritten); } public static double Parse(string s) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null); diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index dd0de7cdef4cc4..73cf14403d59e8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -505,7 +505,7 @@ public override int GetHashCode() /// public override string ToString() { - return Number.FormatHalf(this, null, NumberFormatInfo.CurrentInfo); + return Number.FormatFloat(this, null, NumberFormatInfo.CurrentInfo); } /// @@ -513,7 +513,7 @@ public override string ToString() /// public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format) { - return Number.FormatHalf(this, format, NumberFormatInfo.CurrentInfo); + return Number.FormatFloat(this, format, NumberFormatInfo.CurrentInfo); } /// @@ -521,7 +521,7 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin /// public string ToString(IFormatProvider? provider) { - return Number.FormatHalf(this, null, NumberFormatInfo.GetInstance(provider)); + return Number.FormatFloat(this, null, NumberFormatInfo.GetInstance(provider)); } /// @@ -529,7 +529,7 @@ public string ToString(IFormatProvider? provider) /// public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider) { - return Number.FormatHalf(this, format, NumberFormatInfo.GetInstance(provider)); + return Number.FormatFloat(this, format, NumberFormatInfo.GetInstance(provider)); } /// @@ -542,13 +542,13 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin /// public bool TryFormat(Span destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan format = default, IFormatProvider? provider = null) { - return Number.TryFormatHalf(this, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten); + return Number.TryFormatFloat(this, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten); } /// public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan format = default, IFormatProvider? provider = null) { - return Number.TryFormatHalf(this, format, NumberFormatInfo.GetInstance(provider), utf8Destination, out bytesWritten); + return Number.TryFormatFloat(this, format, NumberFormatInfo.GetInstance(provider), utf8Destination, out bytesWritten); } // diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DiyFp.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DiyFp.cs index c910c0bcff874f..49148306fff0f4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DiyFp.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DiyFp.cs @@ -17,57 +17,11 @@ internal static partial class Number // DiyFp are not designed to contain special doubles (NaN and Infinity). internal readonly ref struct DiyFp { - public const int DoubleImplicitBitIndex = 52; - public const int SingleImplicitBitIndex = 23; - public const int HalfImplicitBitIndex = 10; - public const int SignificandSize = 64; public readonly ulong f; public readonly int e; - // Computes the two boundaries of value. - // - // The bigger boundary (mPlus) is normalized. - // The lower boundary has the same exponent as mPlus. - // - // Precondition: - // The value encoded by value must be greater than 0. - public static DiyFp CreateAndGetBoundaries(double value, out DiyFp mMinus, out DiyFp mPlus) - { - var result = new DiyFp(value); - result.GetBoundaries(DoubleImplicitBitIndex, out mMinus, out mPlus); - return result; - } - - // Computes the two boundaries of value. - // - // The bigger boundary (mPlus) is normalized. - // The lower boundary has the same exponent as mPlus. - // - // Precondition: - // The value encoded by value must be greater than 0. - public static DiyFp CreateAndGetBoundaries(float value, out DiyFp mMinus, out DiyFp mPlus) - { - var result = new DiyFp(value); - result.GetBoundaries(SingleImplicitBitIndex, out mMinus, out mPlus); - return result; - } - - // Computes the two boundaries of value. - // - // The bigger boundary (mPlus) is normalized. - // The lower boundary has the same exponent as mPlus. - // - // Precondition: - // The value encoded by value must be greater than 0. - public static DiyFp CreateAndGetBoundaries(Half value, out DiyFp mMinus, out DiyFp mPlus) - { - var result = new DiyFp(value); - result.GetBoundaries(HalfImplicitBitIndex, out mMinus, out mPlus); - return result; - } - // Computes the two boundaries of value. // // The bigger boundary (mPlus) is normalized. @@ -83,27 +37,6 @@ public static DiyFp CreateAndGetBoundaries(TNumber value, out DiyFp mMi return result; } - public DiyFp(double value) - { - Debug.Assert(double.IsFinite(value)); - Debug.Assert(value > 0.0); - f = ExtractFractionAndBiasedExponent(value, out e); - } - - public DiyFp(float value) - { - Debug.Assert(float.IsFinite(value)); - Debug.Assert(value > 0.0f); - f = ExtractFractionAndBiasedExponent(value, out e); - } - - public DiyFp(Half value) - { - Debug.Assert(Half.IsFinite(value)); - Debug.Assert((float)value > 0.0f); - f = ExtractFractionAndBiasedExponent(value, out e); - } - public static DiyFp Create(TNumber value) where TNumber : unmanaged, IBinaryFloatParseAndFormatInfo { diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs index 2a50766a9ff439..038fdb23947ddc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs @@ -10,96 +10,6 @@ namespace System // The backing algorithm and the proofs behind it are described in more detail here: https://www.cs.indiana.edu/~dyb/pubs/FP-Printing-PLDI96.pdf internal static partial class Number { - public static void Dragon4Double(double value, int cutoffNumber, bool isSignificantDigits, ref NumberBuffer number) - { - double v = double.IsNegative(value) ? -value : value; - - Debug.Assert(v > 0); - Debug.Assert(double.IsFinite(v)); - - ulong mantissa = ExtractFractionAndBiasedExponent(value, out int exponent); - - uint mantissaHighBitIdx; - bool hasUnequalMargins = false; - - if ((mantissa >> DiyFp.DoubleImplicitBitIndex) != 0) - { - mantissaHighBitIdx = DiyFp.DoubleImplicitBitIndex; - hasUnequalMargins = (mantissa == (1UL << DiyFp.DoubleImplicitBitIndex)); - } - else - { - Debug.Assert(mantissa != 0); - mantissaHighBitIdx = (uint)BitOperations.Log2(mantissa); - } - - int length = (int)(Dragon4(mantissa, exponent, mantissaHighBitIdx, hasUnequalMargins, cutoffNumber, isSignificantDigits, number.Digits, out int decimalExponent)); - - number.Scale = decimalExponent + 1; - number.Digits[length] = (byte)('\0'); - number.DigitsCount = length; - } - - public static unsafe void Dragon4Half(Half value, int cutoffNumber, bool isSignificantDigits, ref NumberBuffer number) - { - Half v = Half.IsNegative(value) ? Half.Negate(value) : value; - - Debug.Assert((double)v > 0.0); - Debug.Assert(Half.IsFinite(v)); - - ushort mantissa = ExtractFractionAndBiasedExponent(value, out int exponent); - - uint mantissaHighBitIdx; - bool hasUnequalMargins = false; - - if ((mantissa >> DiyFp.HalfImplicitBitIndex) != 0) - { - mantissaHighBitIdx = DiyFp.HalfImplicitBitIndex; - hasUnequalMargins = (mantissa == (1U << DiyFp.HalfImplicitBitIndex)); - } - else - { - Debug.Assert(mantissa != 0); - mantissaHighBitIdx = (uint)BitOperations.Log2(mantissa); - } - - int length = (int)(Dragon4(mantissa, exponent, mantissaHighBitIdx, hasUnequalMargins, cutoffNumber, isSignificantDigits, number.Digits, out int decimalExponent)); - - number.Scale = decimalExponent + 1; - number.Digits[length] = (byte)('\0'); - number.DigitsCount = length; - } - - public static unsafe void Dragon4Single(float value, int cutoffNumber, bool isSignificantDigits, ref NumberBuffer number) - { - float v = float.IsNegative(value) ? -value : value; - - Debug.Assert(v > 0); - Debug.Assert(float.IsFinite(v)); - - uint mantissa = ExtractFractionAndBiasedExponent(value, out int exponent); - - uint mantissaHighBitIdx; - bool hasUnequalMargins = false; - - if ((mantissa >> DiyFp.SingleImplicitBitIndex) != 0) - { - mantissaHighBitIdx = DiyFp.SingleImplicitBitIndex; - hasUnequalMargins = (mantissa == (1U << DiyFp.SingleImplicitBitIndex)); - } - else - { - Debug.Assert(mantissa != 0); - mantissaHighBitIdx = (uint)BitOperations.Log2(mantissa); - } - - int length = (int)(Dragon4(mantissa, exponent, mantissaHighBitIdx, hasUnequalMargins, cutoffNumber, isSignificantDigits, number.Digits, out int decimalExponent)); - - number.Scale = decimalExponent + 1; - number.Digits[length] = (byte)('\0'); - number.DigitsCount = length; - } - public static unsafe void Dragon4(TNumber value, int cutoffNumber, bool isSignificantDigits, ref NumberBuffer number) where TNumber : unmanaged, IBinaryFloatParseAndFormatInfo { diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs index 8e409b6930a2d0..7c6e69b18f7b81 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -252,23 +252,6 @@ internal static partial class Number { internal const int DecimalPrecision = 29; // Decimal.DecCalc also uses this value - // SinglePrecision and DoublePrecision represent the maximum number of digits required - // to guarantee that any given Single or Double can roundtrip. Some numbers may require - // less, but none will require more. - private const int HalfPrecision = 5; - private const int SinglePrecision = 9; - private const int DoublePrecision = 17; - - // SinglePrecisionCustomFormat and DoublePrecisionCustomFormat are used to ensure that - // custom format strings return the same string as in previous releases when the format - // would return x digits or less (where x is the value of the corresponding constant). - // In order to support more digits, we would need to update ParseFormatSpecifier to pre-parse - // the format and determine exactly how many digits are being requested and whether they - // represent "significant digits" or "digits after the decimal point". - private const int HalfPrecisionCustomFormat = 5; - private const int SinglePrecisionCustomFormat = 7; - private const int DoublePrecisionCustomFormat = 15; - /// The non-inclusive upper bound of . /// /// This is a semi-arbitrary bound. For mono, which is often used for more size-constrained workloads, @@ -394,28 +377,6 @@ internal static unsafe void DecimalToNumber(scoped ref decimal d, ref NumberBuff number.CheckConsistency(); } - public static string FormatDouble(double value, string? format, NumberFormatInfo info) - { - var vlb = new ValueListBuilder(stackalloc char[CharStackBufferSize]); - string result = FormatDouble(ref vlb, value, format, info) ?? vlb.AsSpan().ToString(); - vlb.Dispose(); - return result; - } - - public static bool TryFormatDouble(double value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar - { - var vlb = new ValueListBuilder(stackalloc TChar[CharStackBufferSize]); - string? s = FormatDouble(ref vlb, value, format, info); - - Debug.Assert(s is null || typeof(TChar) == typeof(char)); - bool success = s != null ? - TryCopyTo(s, destination, out charsWritten) : - vlb.TryCopyTo(destination, out charsWritten); - - vlb.Dispose(); - return success; - } - private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int precision, NumberFormatInfo info, out bool isSignificantDigits) { if (fmt == 0) @@ -537,305 +498,23 @@ private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int preci return maxDigits; } - /// Formats the specified value according to the specified format and info. - /// - /// Non-null if an existing string can be returned, in which case the builder will be unmodified. - /// Null if no existing string was returned, in which case the formatted output is in the builder. - /// - private static unsafe string? FormatDouble(ref ValueListBuilder vlb, double value, ReadOnlySpan format, NumberFormatInfo info) where TChar : unmanaged, IUtfChar - { - if (!double.IsFinite(value)) - { - if (double.IsNaN(value)) - { - if (typeof(TChar) == typeof(char)) - { - return info.NaNSymbol; - } - else - { - vlb.Append(info.NaNSymbolTChar()); - return null; - } - } - - if (typeof(TChar) == typeof(char)) - { - return double.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; - } - else - { - vlb.Append(double.IsNegative(value) ? info.NegativeInfinitySymbolTChar() : info.PositiveInfinitySymbolTChar()); - return null; - } - } - - char fmt = ParseFormatSpecifier(format, out int precision); - byte* pDigits = stackalloc byte[DoubleNumberBufferLength]; - - if (fmt == '\0') - { - // For back-compat we currently specially treat the precision for custom - // format specifiers. The constant has more details as to why. - precision = DoublePrecisionCustomFormat; - } - - NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, DoubleNumberBufferLength); - number.IsNegative = double.IsNegative(value); - - // We need to track the original precision requested since some formats - // accept values like 0 and others may require additional fixups. - int nMaxDigits = GetFloatingPointMaxDigitsAndPrecision(fmt, ref precision, info, out bool isSignificantDigits); - - if ((value != 0.0) && (!isSignificantDigits || !Grisu3.TryRunDouble(value, precision, ref number))) - { - Dragon4Double(value, precision, isSignificantDigits, ref number); - } - - number.CheckConsistency(); - - // When the number is known to be roundtrippable (either because we requested it be, or - // because we know we have enough digits to satisfy roundtrippability), we should validate - // that the number actually roundtrips back to the original result. - - Debug.Assert(((precision != -1) && (precision < DoublePrecision)) || (BitConverter.DoubleToInt64Bits(value) == BitConverter.DoubleToInt64Bits(NumberToFloat(ref number)))); - - if (fmt != 0) - { - if (precision == -1) - { - Debug.Assert((fmt == 'G') || (fmt == 'g') || (fmt == 'R') || (fmt == 'r')); - - // For the roundtrip and general format specifiers, when returning the shortest roundtrippable - // string, we need to update the maximum number of digits to be the greater of number.DigitsCount - // or DoublePrecision. This ensures that we continue returning "pretty" strings for values with - // less digits. One example this fixes is "-60", which would otherwise be formatted as "-6E+01" - // since DigitsCount would be 1 and the formatter would almost immediately switch to scientific notation. - - nMaxDigits = Math.Max(number.DigitsCount, DoublePrecision); - } - NumberToString(ref vlb, ref number, fmt, nMaxDigits, info); - } - else - { - Debug.Assert(precision == DoublePrecisionCustomFormat); - NumberToStringFormat(ref vlb, ref number, format, info); - } - return null; - } - - public static string FormatSingle(float value, string? format, NumberFormatInfo info) - { - var vlb = new ValueListBuilder(stackalloc char[CharStackBufferSize]); - string result = FormatSingle(ref vlb, value, format, info) ?? vlb.AsSpan().ToString(); - vlb.Dispose(); - return result; - } - - public static bool TryFormatSingle(float value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar - { - var vlb = new ValueListBuilder(stackalloc TChar[CharStackBufferSize]); - string? s = FormatSingle(ref vlb, value, format, info); - - Debug.Assert(s is null || typeof(TChar) == typeof(char)); - bool success = s != null ? - TryCopyTo(s, destination, out charsWritten) : - vlb.TryCopyTo(destination, out charsWritten); - - vlb.Dispose(); - return success; - } - - /// Formats the specified value according to the specified format and info. - /// - /// Non-null if an existing string can be returned, in which case the builder will be unmodified. - /// Null if no existing string was returned, in which case the formatted output is in the builder. - /// - private static unsafe string? FormatSingle(ref ValueListBuilder vlb, float value, ReadOnlySpan format, NumberFormatInfo info) where TChar : unmanaged, IUtfChar - { - Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); - - if (!float.IsFinite(value)) - { - if (float.IsNaN(value)) - { - if (typeof(TChar) == typeof(char)) - { - return info.NaNSymbol; - } - else - { - vlb.Append(info.NaNSymbolTChar()); - return null; - } - } - - if (typeof(TChar) == typeof(char)) - { - return float.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; - } - else - { - vlb.Append(float.IsNegative(value) ? info.NegativeInfinitySymbolTChar() : info.PositiveInfinitySymbolTChar()); - return null; - } - } - - char fmt = ParseFormatSpecifier(format, out int precision); - byte* pDigits = stackalloc byte[SingleNumberBufferLength]; - - if (fmt == '\0') - { - // For back-compat we currently specially treat the precision for custom - // format specifiers. The constant has more details as to why. - precision = SinglePrecisionCustomFormat; - } - - NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, SingleNumberBufferLength); - number.IsNegative = float.IsNegative(value); - - // We need to track the original precision requested since some formats - // accept values like 0 and others may require additional fixups. - int nMaxDigits = GetFloatingPointMaxDigitsAndPrecision(fmt, ref precision, info, out bool isSignificantDigits); - - if ((value != default) && (!isSignificantDigits || !Grisu3.TryRunSingle(value, precision, ref number))) - { - Dragon4Single(value, precision, isSignificantDigits, ref number); - } - - number.CheckConsistency(); - - // When the number is known to be roundtrippable (either because we requested it be, or - // because we know we have enough digits to satisfy roundtrippability), we should validate - // that the number actually roundtrips back to the original result. - - Debug.Assert(((precision != -1) && (precision < SinglePrecision)) || (BitConverter.SingleToInt32Bits(value) == BitConverter.SingleToInt32Bits(NumberToFloat(ref number)))); - - if (fmt != 0) - { - if (precision == -1) - { - Debug.Assert((fmt == 'G') || (fmt == 'g') || (fmt == 'R') || (fmt == 'r')); - - // For the roundtrip and general format specifiers, when returning the shortest roundtrippable - // string, we need to update the maximum number of digits to be the greater of number.DigitsCount - // or SinglePrecision. This ensures that we continue returning "pretty" strings for values with - // less digits. One example this fixes is "-60", which would otherwise be formatted as "-6E+01" - // since DigitsCount would be 1 and the formatter would almost immediately switch to scientific notation. - - nMaxDigits = Math.Max(number.DigitsCount, SinglePrecision); - } - NumberToString(ref vlb, ref number, fmt, nMaxDigits, info); - } - else - { - Debug.Assert(precision == SinglePrecisionCustomFormat); - NumberToStringFormat(ref vlb, ref number, format, info); - } - return null; - } - - public static string FormatHalf(Half value, string? format, NumberFormatInfo info) + public static string FormatFloat(TNumber value, string? format, NumberFormatInfo info) + where TNumber : unmanaged, IBinaryFloatParseAndFormatInfo { var vlb = new ValueListBuilder(stackalloc char[CharStackBufferSize]); - string result = FormatHalf(ref vlb, value, format, info) ?? vlb.AsSpan().ToString(); + string result = FormatFloat(ref vlb, value, format, info) ?? vlb.AsSpan().ToString(); vlb.Dispose(); return result; } - /// Formats the specified value according to the specified format and info. - /// - /// Non-null if an existing string can be returned, in which case the builder will be unmodified. - /// Null if no existing string was returned, in which case the formatted output is in the builder. - /// - private static unsafe string? FormatHalf(ref ValueListBuilder vlb, Half value, ReadOnlySpan format, NumberFormatInfo info) where TChar : unmanaged, IUtfChar - { - Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); - - if (!Half.IsFinite(value)) - { - if (Half.IsNaN(value)) - { - if (typeof(TChar) == typeof(char)) - { - return info.NaNSymbol; - } - else - { - vlb.Append(info.NaNSymbolTChar()); - return null; - } - } - - if (typeof(TChar) == typeof(char)) - { - return Half.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; - } - else - { - vlb.Append(Half.IsNegative(value) ? info.NegativeInfinitySymbolTChar() : info.PositiveInfinitySymbolTChar()); - return null; - } - } - - char fmt = ParseFormatSpecifier(format, out int precision); - byte* pDigits = stackalloc byte[HalfNumberBufferLength]; - - if (fmt == '\0') - { - precision = HalfPrecisionCustomFormat; - } - - NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, HalfNumberBufferLength); - number.IsNegative = Half.IsNegative(value); - - // We need to track the original precision requested since some formats - // accept values like 0 and others may require additional fixups. - int nMaxDigits = GetFloatingPointMaxDigitsAndPrecision(fmt, ref precision, info, out bool isSignificantDigits); - - if ((value != default) && (!isSignificantDigits || !Grisu3.TryRunHalf(value, precision, ref number))) - { - Dragon4Half(value, precision, isSignificantDigits, ref number); - } - - number.CheckConsistency(); - - // When the number is known to be roundtrippable (either because we requested it be, or - // because we know we have enough digits to satisfy roundtrippability), we should validate - // that the number actually roundtrips back to the original result. - - Debug.Assert(((precision != -1) && (precision < HalfPrecision)) || (BitConverter.HalfToInt16Bits(value) == BitConverter.HalfToInt16Bits(NumberToFloat(ref number)))); - - if (fmt != 0) - { - if (precision == -1) - { - Debug.Assert((fmt == 'G') || (fmt == 'g') || (fmt == 'R') || (fmt == 'r')); - - // For the roundtrip and general format specifiers, when returning the shortest roundtrippable - // string, we need to update the maximum number of digits to be the greater of number.DigitsCount - // or SinglePrecision. This ensures that we continue returning "pretty" strings for values with - // less digits. One example this fixes is "-60", which would otherwise be formatted as "-6E+01" - // since DigitsCount would be 1 and the formatter would almost immediately switch to scientific notation. - - nMaxDigits = Math.Max(number.DigitsCount, HalfPrecision); - } - NumberToString(ref vlb, ref number, fmt, nMaxDigits, info); - } - else - { - Debug.Assert(precision == HalfPrecisionCustomFormat); - NumberToStringFormat(ref vlb, ref number, format, info); - } - return null; - } - - public static bool TryFormatHalf(Half value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar + public static bool TryFormatFloat(TNumber value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) + where TNumber : unmanaged, IBinaryFloatParseAndFormatInfo + where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); var vlb = new ValueListBuilder(stackalloc TChar[CharStackBufferSize]); - string? s = FormatHalf(ref vlb, value, format, info); + string? s = FormatFloat(ref vlb, value, format, info); Debug.Assert(s is null || typeof(TChar) == typeof(char)); bool success = s != null ? @@ -846,15 +525,6 @@ public static bool TryFormatHalf(Half value, ReadOnlySpan format, N return success; } - public static string FormatFloat(TNumber value, string? format, NumberFormatInfo info) - where TNumber : unmanaged, IBinaryFloatParseAndFormatInfo - { - var vlb = new ValueListBuilder(stackalloc char[CharStackBufferSize]); - string result = FormatFloat(ref vlb, value, format, info) ?? vlb.AsSpan().ToString(); - vlb.Dispose(); - return result; - } - /// Formats the specified value according to the specified format and info. /// /// Non-null if an existing string can be returned, in which case the builder will be unmodified. @@ -944,24 +614,6 @@ public static string FormatFloat(TNumber value, string? format, NumberF return null; } - public static bool TryFormatFloat(TNumber value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) - where TNumber : unmanaged, IBinaryFloatParseAndFormatInfo - where TChar : unmanaged, IUtfChar - { - Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); - - var vlb = new ValueListBuilder(stackalloc TChar[CharStackBufferSize]); - string? s = FormatFloat(ref vlb, value, format, info); - - Debug.Assert(s is null || typeof(TChar) == typeof(char)); - bool success = s != null ? - TryCopyTo(s, destination, out charsWritten) : - vlb.TryCopyTo(destination, out charsWritten); - - vlb.Dispose(); - return success; - } - private static bool TryCopyTo(string source, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Grisu3.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Grisu3.cs index b4e01cac10a976..e4a25b9b5939c6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Grisu3.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Grisu3.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Numerics; namespace System { @@ -322,108 +321,6 @@ internal static class Grisu3 1000000000, // 10^9 ]; - public static bool TryRunDouble(double value, int requestedDigits, ref NumberBuffer number) - { - double v = double.IsNegative(value) ? -value : value; - - Debug.Assert(v > 0); - Debug.Assert(double.IsFinite(v)); - - int length; - int decimalExponent; - bool result; - - if (requestedDigits == -1) - { - DiyFp w = DiyFp.CreateAndGetBoundaries(v, out DiyFp boundaryMinus, out DiyFp boundaryPlus).Normalize(); - result = TryRunShortest(in boundaryMinus, in w, in boundaryPlus, number.Digits, out length, out decimalExponent); - } - else - { - DiyFp w = new DiyFp(v).Normalize(); - result = TryRunCounted(in w, requestedDigits, number.Digits, out length, out decimalExponent); - } - - if (result) - { - Debug.Assert((requestedDigits == -1) || (length == requestedDigits)); - - number.Scale = length + decimalExponent; - number.Digits[length] = (byte)('\0'); - number.DigitsCount = length; - } - - return result; - } - - public static bool TryRunHalf(Half value, int requestedDigits, ref NumberBuffer number) - { - Half v = Half.IsNegative(value) ? Half.Negate(value) : value; - - Debug.Assert((double)v > 0); - Debug.Assert(Half.IsFinite(v)); - - int length; - int decimalExponent; - bool result; - - if (requestedDigits == -1) - { - DiyFp w = DiyFp.CreateAndGetBoundaries(v, out DiyFp boundaryMinus, out DiyFp boundaryPlus).Normalize(); - result = TryRunShortest(in boundaryMinus, in w, in boundaryPlus, number.Digits, out length, out decimalExponent); - } - else - { - DiyFp w = new DiyFp(v).Normalize(); - result = TryRunCounted(in w, requestedDigits, number.Digits, out length, out decimalExponent); - } - - if (result) - { - Debug.Assert((requestedDigits == -1) || (length == requestedDigits)); - - number.Scale = length + decimalExponent; - number.Digits[length] = (byte)('\0'); - number.DigitsCount = length; - } - - return result; - } - - public static bool TryRunSingle(float value, int requestedDigits, ref NumberBuffer number) - { - float v = float.IsNegative(value) ? -value : value; - - Debug.Assert(v > 0); - Debug.Assert(float.IsFinite(v)); - - int length; - int decimalExponent; - bool result; - - if (requestedDigits == -1) - { - DiyFp w = DiyFp.CreateAndGetBoundaries(v, out DiyFp boundaryMinus, out DiyFp boundaryPlus).Normalize(); - result = TryRunShortest(in boundaryMinus, in w, in boundaryPlus, number.Digits, out length, out decimalExponent); - } - else - { - DiyFp w = new DiyFp(v).Normalize(); - result = TryRunCounted(in w, requestedDigits, number.Digits, out length, out decimalExponent); - } - - if (result) - { - Debug.Assert((requestedDigits == -1) || (length == requestedDigits)); - - number.Scale = length + decimalExponent; - number.Digits[length] = (byte)('\0'); - number.DigitsCount = length; - } - - return result; - } - public static bool TryRun(TNumber value, int requestedDigits, ref NumberBuffer number) where TNumber : unmanaged, IBinaryFloatParseAndFormatInfo { diff --git a/src/libraries/System.Private.CoreLib/src/System/Single.cs b/src/libraries/System.Private.CoreLib/src/System/Single.cs index 85c6c162540270..7ee394a0d8f4c3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -349,33 +349,33 @@ public override int GetHashCode() public override string ToString() { - return Number.FormatSingle(m_value, null, NumberFormatInfo.CurrentInfo); + return Number.FormatFloat(m_value, null, NumberFormatInfo.CurrentInfo); } public string ToString(IFormatProvider? provider) { - return Number.FormatSingle(m_value, null, NumberFormatInfo.GetInstance(provider)); + return Number.FormatFloat(m_value, null, NumberFormatInfo.GetInstance(provider)); } public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format) { - return Number.FormatSingle(m_value, format, NumberFormatInfo.CurrentInfo); + return Number.FormatFloat(m_value, format, NumberFormatInfo.CurrentInfo); } public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider) { - return Number.FormatSingle(m_value, format, NumberFormatInfo.GetInstance(provider)); + return Number.FormatFloat(m_value, format, NumberFormatInfo.GetInstance(provider)); } public bool TryFormat(Span destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan format = default, IFormatProvider? provider = null) { - return Number.TryFormatSingle(m_value, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten); + return Number.TryFormatFloat(m_value, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten); } /// public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan format = default, IFormatProvider? provider = null) { - return Number.TryFormatSingle(m_value, format, NumberFormatInfo.GetInstance(provider), utf8Destination, out bytesWritten); + return Number.TryFormatFloat(m_value, format, NumberFormatInfo.GetInstance(provider), utf8Destination, out bytesWritten); } // Parses a float from a String in the given style. If From f973ead0675768c64cb9b946aa0fc8f90527864b Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 28 May 2024 00:55:45 +0800 Subject: [PATCH 7/7] Fix ExtractFractionAndBiasedExponent --- .../System.Private.CoreLib/src/System/Number.Formatting.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs index 7c6e69b18f7b81..928eaea0d9540a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -2653,7 +2653,7 @@ private static ulong ExtractFractionAndBiasedExponent(TNumber value, ou // So f = (2^TrailingSignificandLength + mantissa), e = exp - ExponentBias - TrailingSignificandLength; fraction |= (1UL << TNumber.DenormalMantissaBits); - exponent -= TNumber.ExponentBias - TNumber.DenormalMantissaBits; + exponent -= TNumber.ExponentBias + TNumber.DenormalMantissaBits; } else {