From aa13d99c23b01c5e0f49c9bcc81ac1e2f05d795c Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 13 May 2023 15:58:32 +0800 Subject: [PATCH 01/11] Cleanup DecStr formatting --- .../System.Private.CoreLib.Shared.projitems | 1 + .../FormattingHelpers.CountDigits.Int128.cs | 59 +++++ .../Text/FormattingHelpers.CountDigits.cs | 49 ----- .../src/System.Runtime.Numerics.csproj | 2 + .../src/System/Number.BigInteger.cs | 201 +++++++++++------- 5 files changed, 187 insertions(+), 125 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.Int128.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 22a4ddea6ed606..b9f4be64bc9a74 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -133,6 +133,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.Int128.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.Int128.cs new file mode 100644 index 00000000000000..5264f100112eb7 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.Int128.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System.Buffers.Text +{ + internal static partial class FormattingHelpers + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int CountDigits(UInt128 value) + { + ulong upper = value.Upper; + + // 1e19 is 8AC7_2304_89E8_0000 + // 1e20 is 5_6BC7_5E2D_6310_0000 + // 1e21 is 36_35C9_ADC5_DEA0_0000 + + if (upper == 0) + { + // We have less than 64-bits, so just return the lower count + return CountDigits(value.Lower); + } + + // We have more than 1e19, so we have at least 20 digits + int digits = 20; + + if (upper > 5) + { + // ((2^128) - 1) / 1e20 < 34_02_823_669_209_384_635 which + // is 18.5318 digits, meaning the result definitely fits + // into 64-bits and we only need to add the lower digit count + + value /= new UInt128(0x5, 0x6BC7_5E2D_6310_0000); // value /= 1e20 + Debug.Assert(value.Upper == 0); + + digits += CountDigits(value.Lower); + } + else if ((upper == 5) && (value.Lower >= 0x6BC75E2D63100000)) + { + // We're greater than 1e20, but definitely less than 1e21 + // so we have exactly 21 digits + + digits++; + Debug.Assert(digits == 21); + } + + return digits; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int CountHexDigits(UInt128 value) + { + // The number of hex digits is log16(value) + 1, or log2(value) / 4 + 1 + return ((int)UInt128.Log2(value) >> 2) + 1; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs index 3da8684132f077..18eac2cf08eb7e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Buffers.Binary; using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; @@ -11,47 +10,6 @@ namespace System.Buffers.Text { internal static partial class FormattingHelpers { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CountDigits(UInt128 value) - { - ulong upper = value.Upper; - - // 1e19 is 8AC7_2304_89E8_0000 - // 1e20 is 5_6BC7_5E2D_6310_0000 - // 1e21 is 36_35C9_ADC5_DEA0_0000 - - if (upper == 0) - { - // We have less than 64-bits, so just return the lower count - return CountDigits(value.Lower); - } - - // We have more than 1e19, so we have at least 20 digits - int digits = 20; - - if (upper > 5) - { - // ((2^128) - 1) / 1e20 < 34_02_823_669_209_384_635 which - // is 18.5318 digits, meaning the result definitely fits - // into 64-bits and we only need to add the lower digit count - - value /= new UInt128(0x5, 0x6BC7_5E2D_6310_0000); // value /= 1e20 - Debug.Assert(value.Upper == 0); - - digits += CountDigits(value.Lower); - } - else if ((upper == 5) && (value.Lower >= 0x6BC75E2D63100000)) - { - // We're greater than 1e20, but definitely less than 1e21 - // so we have exactly 21 digits - - digits++; - Debug.Assert(digits == 21); - } - - return digits; - } - // Based on do_count_digits from https://github.com/fmtlib/fmt/blob/662adf4f33346ba9aba8b072194e319869ede54a/include/fmt/format.h#L1124 [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int CountDigits(ulong value) @@ -149,13 +107,6 @@ public static int CountDigits(uint value) return (int)((value + tableValue) >> 32); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CountHexDigits(UInt128 value) - { - // The number of hex digits is log16(value) + 1, or log2(value) / 4 + 1 - return ((int)UInt128.Log2(value) >> 2) + 1; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int CountHexDigits(ulong value) { diff --git a/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj index d2003757c60f3d..6526656921c343 100644 --- a/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj +++ b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj @@ -25,6 +25,8 @@ + fo } catch (OverflowException e) { throw new FormatException(SR.Format_TooLarge, e); } - bool decimalFmt = (fmt == 'g' || fmt == 'G' || fmt == 'd' || fmt == 'D' || fmt == 'r' || fmt == 'R'); - if (decimalFmt) - { - if (digits > 0 && digits > cchMax) - cchMax = digits; - if (value._sign < 0) - { - try - { - // Leave an extra slot for a minus sign. - cchMax = checked(cchMax + info.NegativeSign.Length); - } - catch (OverflowException e) { throw new FormatException(SR.Format_TooLarge, e); } - } - } + ReadOnlySpan base1E9Value = rguDst.AsSpan(0, cuDst); + int valueDigits = (base1E9Value.Length - 1) * 9 + FormattingHelpers.CountDigits(base1E9Value[^1]); - int rgchBufSize; - - try + if (fmt == 'g' || fmt == 'G' || fmt == 'd' || fmt == 'D' || fmt == 'r' || fmt == 'R') { - // We'll pass the rgch buffer to native code, which is going to treat it like a string of digits, so it needs - // to be null terminated. Let's ensure that we can allocate a buffer of that size. - rgchBufSize = checked(cchMax + 1); + return value.Sign < 0 + ? NegativeBigIntegerToDecStr(targetSpan, base1E9Value, Math.Max(digits, valueDigits), info.NegativeSign, destination, out charsWritten, out spanSuccess) + : BigIntegerToDecStr(targetSpan, base1E9Value, Math.Max(digits, valueDigits), destination, out charsWritten, out spanSuccess); } - catch (OverflowException e) { throw new FormatException(SR.Format_TooLarge, e); } + + int rgchBufSize = cchMax + 1; char[] rgch = new char[rgchBufSize]; @@ -975,11 +962,11 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo uDig /= 10; } - if (!decimalFmt) - { - // sign = true for negative and false for 0 and positive values - bool sign = (value._sign < 0); - int scale = cchMax - ichDst; + // sign = true for negative and false for 0 and positive values + bool sign = (value._sign < 0); + // The cut-off point to switch (G)eneral from (F)ixed-point to (E)xponential form + // int precision = 29; + int scale = cchMax - ichDst; byte[]? buffer = ArrayPool.Shared.Rent(rgchBufSize + 1); fixed (byte* ptr = buffer) // NumberBuffer expects pinned Digits @@ -993,74 +980,136 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo number.Scale = scale; number.IsNegative = sign; - scoped var vlb = new ValueListBuilder(stackalloc Utf16Char[128]); // arbitrary stack cut-off + scoped var vlb = new ValueListBuilder(stackalloc char[128]); // arbitrary stack cut-off - if (fmt != 0) - { - NumberToString(ref vlb, ref number, fmt, digits, info); - } - else - { - NumberToStringFormat(ref vlb, ref number, formatSpan, info); - } + if (fmt != 0) + { + NumberToString(ref vlb, ref number, fmt, digits, info); + } + else + { + NumberToStringFormat(ref vlb, ref number, formatSpan, info); + } - if (targetSpan) - { - spanSuccess = vlb.TryCopyTo(MemoryMarshal.Cast(destination), out charsWritten); - vlb.Dispose(); - return null; - } - else - { - charsWritten = 0; - spanSuccess = false; - string result = MemoryMarshal.Cast(vlb.AsSpan()).ToString(); - vlb.Dispose(); - return result; - } + if (targetSpan) + { + spanSuccess = vlb.TryCopyTo(MemoryMarshal.Cast(destination), out charsWritten); + vlb.Dispose(); + return null; + } + else + { + charsWritten = 0; + spanSuccess = false; + string result = MemoryMarshal.Cast(vlb.AsSpan()).ToString(); + vlb.Dispose(); + return result; } } + } - // Format Round-trip decimal - // This format is supported for integral types only. The number is converted to a string of - // decimal digits (0-9), prefixed by a minus sign if the number is negative. The precision - // specifier indicates the minimum number of digits desired in the resulting string. If required, - // the number is padded with zeros to its left to produce the number of digits given by the - // precision specifier. - int numDigitsPrinted = cchMax - ichDst; - while (digits > 0 && digits > numDigitsPrinted) - { - // pad leading zeros - rgch[--ichDst] = '0'; - digits--; - } - if (value._sign < 0) + private static unsafe string? BigIntegerToDecStr(bool targetSpan, ReadOnlySpan base1E9Value, int digits, + Span destination, out int charsWritten, out bool spanSuccess) + { + Debug.Assert(digits > (base1E9Value.Length - 1) * 9); + + if (targetSpan) { - string negativeSign = info.NegativeSign; - for (int i = negativeSign.Length - 1; i > -1; i--) - rgch[--ichDst] = negativeSign[i]; + if (destination.Length < digits) + { + charsWritten = 0; + spanSuccess = false; + return null; + } + else + { + fixed (char* ptr = &MemoryMarshal.GetReference(destination)) + { + BigIntegerToDecChars(ptr + digits, base1E9Value, digits); + charsWritten = digits; + spanSuccess = true; + return null; + } + } } - - int resultLength = cchMax - ichDst; - if (!targetSpan) + else { charsWritten = 0; spanSuccess = false; - return new string(rgch, ichDst, cchMax - ichDst); + fixed (uint* valuePtr = &MemoryMarshal.GetReference(base1E9Value)) + { + return string.Create(digits, (digits, ptr: (nint)valuePtr, base1E9Value.Length), static (span, state) => + { + fixed (char* ptr = &MemoryMarshal.GetReference(span)) + { + BigIntegerToDecChars(ptr + span.Length, new ReadOnlySpan((uint*)state.ptr, state.Length), state.digits); + } + }); + } } - else if (new ReadOnlySpan(rgch, ichDst, cchMax - ichDst).TryCopyTo(destination)) + } + + private static unsafe string? NegativeBigIntegerToDecStr(bool targetSpan, ReadOnlySpan base1E9Value, int digits, + string sNegative, Span destination, out int charsWritten, out bool spanSuccess) + { + Debug.Assert(digits > (base1E9Value.Length - 1) * 9); + + int bufferLength = digits + sNegative.Length; + + if (targetSpan) { - charsWritten = resultLength; - spanSuccess = true; - return null; + if (bufferLength > destination.Length) + { + charsWritten = 0; + spanSuccess = false; + return null; + } + else + { + sNegative.CopyTo(destination); + fixed (char* ptr = &MemoryMarshal.GetReference(destination)) + { + BigIntegerToDecChars(ptr + bufferLength, base1E9Value, digits); + charsWritten = bufferLength; + spanSuccess = true; + return null; + } + } } else { charsWritten = 0; spanSuccess = false; - return null; + fixed (uint* valuePtr = &MemoryMarshal.GetReference(base1E9Value)) + { + return string.Create(bufferLength, (digits, ptr: (nint)valuePtr, base1E9Value.Length, sNegative), static (span, state) => + { + state.sNegative.CopyTo(span); + fixed (char* ptr = &MemoryMarshal.GetReference(span)) + { + BigIntegerToDecChars(ptr + span.Length, new ReadOnlySpan((uint*)state.ptr, state.Length), state.digits); + } + }); + } } } + + private static unsafe TChar* BigIntegerToDecChars(TChar* bufferEnd, ReadOnlySpan base1E9Value, int digits) + where TChar : unmanaged, INumberBase // CoreLib uses IUtfChar + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + Debug.Assert(base1E9Value[^1] != 0, "Leading zeros should be trimmed by caller."); + + // The base 10^9 value is in reverse order + for (int i = 0; i < base1E9Value.Length - 1; i++) + { + bufferEnd = UInt32ToDecChars(bufferEnd, base1E9Value[i], 9); + digits -= 9; + } + + Debug.Assert(digits >= FormattingHelpers.CountDigits(base1E9Value[^1])); + return UInt32ToDecChars(bufferEnd, base1E9Value[^1], digits); + } } internal interface IBigIntegerHexOrBinaryParser From b490bc383cab60b83715277a0e1e5f0c1b940588 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 13 May 2023 18:27:17 +0800 Subject: [PATCH 02/11] Cleanup ToNumber portion --- .../src/System/Number.BigInteger.cs | 66 ++++--------------- 1 file changed, 14 insertions(+), 52 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 26dcc7cbe06209..f10e8e57055d63 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -893,13 +893,12 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo const uint kuBase = 1000000000; // 10^9 const int kcchBase = 9; + // Each uint contributes at most 9 digits to the decimal representation. + // The current max length is int32.MaxValue bits int cuSrc = value._bits.Length; - int cuMax; - try - { - cuMax = checked(cuSrc * 10 / 9 + 2); - } - catch (OverflowException e) { throw new FormatException(SR.Format_TooLarge, e); } + Debug.Assert(cuSrc < int.MaxValue / kcchBase / 2); + + int cuMax = cuSrc * 10 / 9 + 2; uint[] rguDst = new uint[cuMax]; int cuDst = 0; @@ -922,15 +921,8 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo } } - int cchMax; - try - { - // Each uint contributes at most 9 digits to the decimal representation. - cchMax = checked(cuDst * kcchBase); - } - catch (OverflowException e) { throw new FormatException(SR.Format_TooLarge, e); } - ReadOnlySpan base1E9Value = rguDst.AsSpan(0, cuDst); + int valueDigits = (base1E9Value.Length - 1) * 9 + FormattingHelpers.CountDigits(base1E9Value[^1]); if (fmt == 'g' || fmt == 'G' || fmt == 'd' || fmt == 'D' || fmt == 'r' || fmt == 'R') @@ -940,45 +932,15 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo : BigIntegerToDecStr(targetSpan, base1E9Value, Math.Max(digits, valueDigits), destination, out charsWritten, out spanSuccess); } - int rgchBufSize = cchMax + 1; - - char[] rgch = new char[rgchBufSize]; - - int ichDst = cchMax; - - for (int iuDst = 0; iuDst < cuDst - 1; iuDst++) - { - uint uDig = rguDst[iuDst]; - Debug.Assert(uDig < kuBase); - for (int cch = kcchBase; --cch >= 0;) - { - rgch[--ichDst] = (char)('0' + uDig % 10); - uDig /= 10; - } - } - for (uint uDig = rguDst[cuDst - 1]; uDig != 0;) + byte[]? buffer = ArrayPool.Shared.Rent(valueDigits + 1); + fixed (byte* ptr = buffer) // NumberBuffer expects pinned Digits { - rgch[--ichDst] = (char)('0' + uDig % 10); - uDig /= 10; - } - - // sign = true for negative and false for 0 and positive values - bool sign = (value._sign < 0); - // The cut-off point to switch (G)eneral from (F)ixed-point to (E)xponential form - // int precision = 29; - int scale = cchMax - ichDst; - - byte[]? buffer = ArrayPool.Shared.Rent(rgchBufSize + 1); - fixed (byte* ptr = buffer) // NumberBuffer expects pinned Digits - { - scoped NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, buffer); - - for (int i = 0; i < rgch.Length - ichDst; i++) - number.Digits[i] = (byte)rgch[ichDst + i]; - number.Digits[rgch.Length - ichDst] = 0; - number.DigitsCount = rgch.Length - ichDst - 1; // The cut-off point to switch (G)eneral from (F)ixed-point to (E)xponential form - number.Scale = scale; - number.IsNegative = sign; + scoped NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, ptr, valueDigits + 1); + BigIntegerToDecChars(ptr + valueDigits, base1E9Value, valueDigits); + number.Digits[^1] = 0; + number.DigitsCount = valueDigits; + number.Scale = valueDigits; + number.IsNegative = value.Sign < 0; scoped var vlb = new ValueListBuilder(stackalloc char[128]); // arbitrary stack cut-off From 6d134878ffd5788ebaafb446db72f0fb48aca354 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 13 May 2023 20:54:45 +0800 Subject: [PATCH 03/11] Clean and pool base1E9 buffer --- .../src/System/Number.BigInteger.cs | 122 ++++++++++-------- 1 file changed, 66 insertions(+), 56 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index f10e8e57055d63..142b43a1a5dde1 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -890,7 +890,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo } // First convert to base 10^9. - const uint kuBase = 1000000000; // 10^9 + const uint kuBase = 1_000_000_000; // 10^9 const int kcchBase = 9; // Each uint contributes at most 9 digits to the decimal representation. @@ -899,75 +899,85 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo Debug.Assert(cuSrc < int.MaxValue / kcchBase / 2); int cuMax = cuSrc * 10 / 9 + 2; - uint[] rguDst = new uint[cuMax]; - int cuDst = 0; + uint[] base1E9Buffer = ArrayPool.Shared.Rent(cuMax); - for (int iuSrc = cuSrc; --iuSrc >= 0;) + try { - uint uCarry = value._bits[iuSrc]; - for (int iuDst = 0; iuDst < cuDst; iuDst++) - { - Debug.Assert(rguDst[iuDst] < kuBase); - ulong uuRes = NumericsHelpers.MakeUInt64(rguDst[iuDst], uCarry); - rguDst[iuDst] = (uint)(uuRes % kuBase); - uCarry = (uint)(uuRes / kuBase); - } - if (uCarry != 0) + int cuDst = 0; + + for (int iuSrc = cuSrc; --iuSrc >= 0;) { - rguDst[cuDst++] = uCarry % kuBase; - uCarry /= kuBase; + uint uCarry = value._bits[iuSrc]; + for (int iuDst = 0; iuDst < cuDst; iuDst++) + { + Debug.Assert(base1E9Buffer[iuDst] < kuBase); + + // Use X86Base.DivRem when stable + ulong uuRes = NumericsHelpers.MakeUInt64(base1E9Buffer[iuDst], uCarry); + (ulong quo, ulong rem) = Math.DivRem(uuRes, kuBase); + uCarry = (uint)quo; + base1E9Buffer[iuDst] = (uint)rem; + } if (uCarry != 0) - rguDst[cuDst++] = uCarry; + { + (uCarry, base1E9Buffer[cuDst++]) = Math.DivRem(uCarry, kuBase); + if (uCarry != 0) + base1E9Buffer[cuDst++] = uCarry; + } } - } - - ReadOnlySpan base1E9Value = rguDst.AsSpan(0, cuDst); - int valueDigits = (base1E9Value.Length - 1) * 9 + FormattingHelpers.CountDigits(base1E9Value[^1]); + ReadOnlySpan base1E9Value = base1E9Buffer.AsSpan(0, cuDst); - if (fmt == 'g' || fmt == 'G' || fmt == 'd' || fmt == 'D' || fmt == 'r' || fmt == 'R') - { - return value.Sign < 0 - ? NegativeBigIntegerToDecStr(targetSpan, base1E9Value, Math.Max(digits, valueDigits), info.NegativeSign, destination, out charsWritten, out spanSuccess) - : BigIntegerToDecStr(targetSpan, base1E9Value, Math.Max(digits, valueDigits), destination, out charsWritten, out spanSuccess); - } - - byte[]? buffer = ArrayPool.Shared.Rent(valueDigits + 1); - fixed (byte* ptr = buffer) // NumberBuffer expects pinned Digits - { - scoped NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, ptr, valueDigits + 1); - BigIntegerToDecChars(ptr + valueDigits, base1E9Value, valueDigits); - number.Digits[^1] = 0; - number.DigitsCount = valueDigits; - number.Scale = valueDigits; - number.IsNegative = value.Sign < 0; - - scoped var vlb = new ValueListBuilder(stackalloc char[128]); // arbitrary stack cut-off + int valueDigits = (base1E9Value.Length - 1) * 9 + FormattingHelpers.CountDigits(base1E9Value[^1]); - if (fmt != 0) + if (fmt == 'g' || fmt == 'G' || fmt == 'd' || fmt == 'D' || fmt == 'r' || fmt == 'R') { - NumberToString(ref vlb, ref number, fmt, digits, info); - } - else - { - NumberToStringFormat(ref vlb, ref number, formatSpan, info); + return value.Sign < 0 + ? NegativeBigIntegerToDecStr(targetSpan, base1E9Value, Math.Max(digits, valueDigits), info.NegativeSign, destination, out charsWritten, out spanSuccess) + : BigIntegerToDecStr(targetSpan, base1E9Value, Math.Max(digits, valueDigits), destination, out charsWritten, out spanSuccess); } - if (targetSpan) + byte[]? buffer = ArrayPool.Shared.Rent(valueDigits + 1); + fixed (byte* ptr = buffer) // NumberBuffer expects pinned Digits { - spanSuccess = vlb.TryCopyTo(MemoryMarshal.Cast(destination), out charsWritten); - vlb.Dispose(); - return null; - } - else - { - charsWritten = 0; - spanSuccess = false; - string result = MemoryMarshal.Cast(vlb.AsSpan()).ToString(); - vlb.Dispose(); - return result; + scoped NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, ptr, valueDigits + 1); + BigIntegerToDecChars(ptr + valueDigits, base1E9Value, valueDigits); + number.Digits[^1] = 0; + number.DigitsCount = valueDigits; + number.Scale = valueDigits; + number.IsNegative = value.Sign < 0; + + scoped var vlb = new ValueListBuilder(stackalloc char[128]); // arbitrary stack cut-off + + if (fmt != 0) + { + NumberToString(ref vlb, ref number, fmt, digits, info); + } + else + { + NumberToStringFormat(ref vlb, ref number, formatSpan, info); + } + + if (targetSpan) + { + spanSuccess = vlb.TryCopyTo(MemoryMarshal.Cast(destination), out charsWritten); + vlb.Dispose(); + return null; + } + else + { + charsWritten = 0; + spanSuccess = false; + string result = MemoryMarshal.Cast(vlb.AsSpan()).ToString(); + vlb.Dispose(); + return result; + } } } + finally + { + ArrayPool.Shared.Return(base1E9Buffer); + } } private static unsafe string? BigIntegerToDecStr(bool targetSpan, ReadOnlySpan base1E9Value, int digits, From 5dd1f2b4a11b6b7518f973a0df15e320b2bb70df Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 13 May 2023 21:02:04 +0800 Subject: [PATCH 04/11] Split leaf span and array path --- .../src/System/Number.BigInteger.cs | 126 +++++++++--------- 1 file changed, 66 insertions(+), 60 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 142b43a1a5dde1..1995f74f5300ed 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -932,9 +932,22 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo if (fmt == 'g' || fmt == 'G' || fmt == 'd' || fmt == 'D' || fmt == 'r' || fmt == 'R') { - return value.Sign < 0 - ? NegativeBigIntegerToDecStr(targetSpan, base1E9Value, Math.Max(digits, valueDigits), info.NegativeSign, destination, out charsWritten, out spanSuccess) - : BigIntegerToDecStr(targetSpan, base1E9Value, Math.Max(digits, valueDigits), destination, out charsWritten, out spanSuccess); + int strDigits = Math.Max(digits, valueDigits); + if (targetSpan) + { + spanSuccess = value.Sign < 0 + ? TryNegativeBigIntegerToDecStr(base1E9Value, strDigits, info.NegativeSign, destination, out charsWritten) + : TryBigIntegerToDecStr(base1E9Value, strDigits, destination, out charsWritten); + return null; + } + else + { + spanSuccess = false; + charsWritten = 0; + return value.Sign < 0 + ? NegativeBigIntegerToDecStr(base1E9Value, strDigits, info.NegativeSign) + : BigIntegerToDecStr(base1E9Value, strDigits); + } } byte[]? buffer = ArrayPool.Shared.Rent(valueDigits + 1); @@ -980,89 +993,82 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo } } - private static unsafe string? BigIntegerToDecStr(bool targetSpan, ReadOnlySpan base1E9Value, int digits, - Span destination, out int charsWritten, out bool spanSuccess) + private static unsafe bool TryBigIntegerToDecStr(ReadOnlySpan base1E9Value, int digits, Span destination, out int charsWritten) { Debug.Assert(digits > (base1E9Value.Length - 1) * 9); - if (targetSpan) + if (destination.Length < digits) + { + charsWritten = 0; + return false; + } + else { - if (destination.Length < digits) + fixed (char* ptr = &MemoryMarshal.GetReference(destination)) { - charsWritten = 0; - spanSuccess = false; - return null; + BigIntegerToDecChars(ptr + digits, base1E9Value, digits); + charsWritten = digits; + return true; } - else + } + } + + private static unsafe string BigIntegerToDecStr(ReadOnlySpan base1E9Value, int digits) + { + Debug.Assert(digits > (base1E9Value.Length - 1) * 9); + + fixed (uint* valuePtr = &MemoryMarshal.GetReference(base1E9Value)) + { + return string.Create(digits, (digits, ptr: (nint)valuePtr, base1E9Value.Length), static (span, state) => { - fixed (char* ptr = &MemoryMarshal.GetReference(destination)) + fixed (char* ptr = &MemoryMarshal.GetReference(span)) { - BigIntegerToDecChars(ptr + digits, base1E9Value, digits); - charsWritten = digits; - spanSuccess = true; - return null; + BigIntegerToDecChars(ptr + span.Length, new ReadOnlySpan((uint*)state.ptr, state.Length), state.digits); } - } + }); } - else + } + + private static unsafe bool TryNegativeBigIntegerToDecStr(ReadOnlySpan base1E9Value, int digits, + string sNegative, Span destination, out int charsWritten) + { + Debug.Assert(digits > (base1E9Value.Length - 1) * 9); + + int bufferLength = digits + sNegative.Length; + + if (bufferLength > destination.Length) { charsWritten = 0; - spanSuccess = false; - fixed (uint* valuePtr = &MemoryMarshal.GetReference(base1E9Value)) + return false; + } + else + { + sNegative.CopyTo(destination); + fixed (char* ptr = &MemoryMarshal.GetReference(destination)) { - return string.Create(digits, (digits, ptr: (nint)valuePtr, base1E9Value.Length), static (span, state) => - { - fixed (char* ptr = &MemoryMarshal.GetReference(span)) - { - BigIntegerToDecChars(ptr + span.Length, new ReadOnlySpan((uint*)state.ptr, state.Length), state.digits); - } - }); + BigIntegerToDecChars(ptr + bufferLength, base1E9Value, digits); + charsWritten = bufferLength; + return true; } } } - private static unsafe string? NegativeBigIntegerToDecStr(bool targetSpan, ReadOnlySpan base1E9Value, int digits, - string sNegative, Span destination, out int charsWritten, out bool spanSuccess) + private static unsafe string NegativeBigIntegerToDecStr(ReadOnlySpan base1E9Value, int digits, string sNegative) { Debug.Assert(digits > (base1E9Value.Length - 1) * 9); int bufferLength = digits + sNegative.Length; - if (targetSpan) + fixed (uint* valuePtr = &MemoryMarshal.GetReference(base1E9Value)) { - if (bufferLength > destination.Length) - { - charsWritten = 0; - spanSuccess = false; - return null; - } - else + return string.Create(bufferLength, (digits, ptr: (nint)valuePtr, base1E9Value.Length, sNegative), static (span, state) => { - sNegative.CopyTo(destination); - fixed (char* ptr = &MemoryMarshal.GetReference(destination)) + state.sNegative.CopyTo(span); + fixed (char* ptr = &MemoryMarshal.GetReference(span)) { - BigIntegerToDecChars(ptr + bufferLength, base1E9Value, digits); - charsWritten = bufferLength; - spanSuccess = true; - return null; + BigIntegerToDecChars(ptr + span.Length, new ReadOnlySpan((uint*)state.ptr, state.Length), state.digits); } - } - } - else - { - charsWritten = 0; - spanSuccess = false; - fixed (uint* valuePtr = &MemoryMarshal.GetReference(base1E9Value)) - { - return string.Create(bufferLength, (digits, ptr: (nint)valuePtr, base1E9Value.Length, sNegative), static (span, state) => - { - state.sNegative.CopyTo(span); - fixed (char* ptr = &MemoryMarshal.GetReference(span)) - { - BigIntegerToDecChars(ptr + span.Length, new ReadOnlySpan((uint*)state.ptr, state.Length), state.digits); - } - }); - } + }); } } From b5e883307775bf9588400e793cf74c465f64b3f4 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 5 Dec 2023 23:27:36 +0800 Subject: [PATCH 05/11] Post cherry-pick update --- .../src/System/Number.BigInteger.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 1995f74f5300ed..f57748ea764621 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -960,7 +960,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo number.Scale = valueDigits; number.IsNegative = value.Sign < 0; - scoped var vlb = new ValueListBuilder(stackalloc char[128]); // arbitrary stack cut-off + scoped var vlb = new ValueListBuilder(stackalloc Utf16Char[128]); // arbitrary stack cut-off if (fmt != 0) { @@ -1073,7 +1073,7 @@ private static unsafe string NegativeBigIntegerToDecStr(ReadOnlySpan base1 } private static unsafe TChar* BigIntegerToDecChars(TChar* bufferEnd, ReadOnlySpan base1E9Value, int digits) - where TChar : unmanaged, INumberBase // CoreLib uses IUtfChar + where TChar : unmanaged, IBinaryInteger // CoreLib uses IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); Debug.Assert(base1E9Value[^1] != 0, "Leading zeros should be trimmed by caller."); @@ -1081,12 +1081,25 @@ private static unsafe string NegativeBigIntegerToDecStr(ReadOnlySpan base1 // The base 10^9 value is in reverse order for (int i = 0; i < base1E9Value.Length - 1; i++) { - bufferEnd = UInt32ToDecChars(bufferEnd, base1E9Value[i], 9); + // TODO: is it worthy to introduce optimized UInt32ToDecChars from CoreLib? digits -= 9; + uint value = base1E9Value[i]; + for (int j = 0; j < 9; j++) + { + (value, uint digit) = Math.DivRem(value, 10); + *(--bufferEnd) = TChar.CreateTruncating('0' + digit); + } } Debug.Assert(digits >= FormattingHelpers.CountDigits(base1E9Value[^1])); - return UInt32ToDecChars(bufferEnd, base1E9Value[^1], digits); + for (uint value = base1E9Value[^1]; value > 0 || digits > 0;) + { + digits--; + (value, uint digit) = Math.DivRem(value, 10); + *(--bufferEnd) = TChar.CreateTruncating('0' + digit); + } + + return bufferEnd; } } From e1255a8dd88f00e9800b844487e3439464401fa9 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 5 Dec 2023 23:54:23 +0800 Subject: [PATCH 06/11] Remove unworthy positive/negative span/string split --- .../src/System/Number.BigInteger.cs | 111 ++++-------------- 1 file changed, 26 insertions(+), 85 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index f57748ea764621..fe64cf4dd0e3ac 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -933,20 +933,40 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo if (fmt == 'g' || fmt == 'G' || fmt == 'd' || fmt == 'D' || fmt == 'r' || fmt == 'R') { int strDigits = Math.Max(digits, valueDigits); + string? sNegative = value.Sign < 0 ? info.NegativeSign : null; + int strLength = digits + (sNegative?.Length ?? 0); + if (targetSpan) { - spanSuccess = value.Sign < 0 - ? TryNegativeBigIntegerToDecStr(base1E9Value, strDigits, info.NegativeSign, destination, out charsWritten) - : TryBigIntegerToDecStr(base1E9Value, strDigits, destination, out charsWritten); + if (destination.Length < strLength) + { + spanSuccess = false; + charsWritten = 0; + } + else + { + sNegative?.CopyTo(destination); + fixed (char* ptr = &MemoryMarshal.GetReference(destination)) + { + BigIntegerToDecChars(ptr + strLength, base1E9Value, digits); + } + charsWritten = strLength; + spanSuccess = true; + } return null; } else { spanSuccess = false; charsWritten = 0; - return value.Sign < 0 - ? NegativeBigIntegerToDecStr(base1E9Value, strDigits, info.NegativeSign) - : BigIntegerToDecStr(base1E9Value, strDigits); + return string.Create(strLength, (digits, base1E9Buffer, base1E9Value.Length, sNegative), static (span, state) => + { + state.sNegative?.CopyTo(span); + fixed (char* ptr = &MemoryMarshal.GetReference(span)) + { + BigIntegerToDecChars(ptr + span.Length, state.base1E9Buffer.AsSpan(0, state.Length), state.digits); + } + }); } } @@ -993,85 +1013,6 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo } } - private static unsafe bool TryBigIntegerToDecStr(ReadOnlySpan base1E9Value, int digits, Span destination, out int charsWritten) - { - Debug.Assert(digits > (base1E9Value.Length - 1) * 9); - - if (destination.Length < digits) - { - charsWritten = 0; - return false; - } - else - { - fixed (char* ptr = &MemoryMarshal.GetReference(destination)) - { - BigIntegerToDecChars(ptr + digits, base1E9Value, digits); - charsWritten = digits; - return true; - } - } - } - - private static unsafe string BigIntegerToDecStr(ReadOnlySpan base1E9Value, int digits) - { - Debug.Assert(digits > (base1E9Value.Length - 1) * 9); - - fixed (uint* valuePtr = &MemoryMarshal.GetReference(base1E9Value)) - { - return string.Create(digits, (digits, ptr: (nint)valuePtr, base1E9Value.Length), static (span, state) => - { - fixed (char* ptr = &MemoryMarshal.GetReference(span)) - { - BigIntegerToDecChars(ptr + span.Length, new ReadOnlySpan((uint*)state.ptr, state.Length), state.digits); - } - }); - } - } - - private static unsafe bool TryNegativeBigIntegerToDecStr(ReadOnlySpan base1E9Value, int digits, - string sNegative, Span destination, out int charsWritten) - { - Debug.Assert(digits > (base1E9Value.Length - 1) * 9); - - int bufferLength = digits + sNegative.Length; - - if (bufferLength > destination.Length) - { - charsWritten = 0; - return false; - } - else - { - sNegative.CopyTo(destination); - fixed (char* ptr = &MemoryMarshal.GetReference(destination)) - { - BigIntegerToDecChars(ptr + bufferLength, base1E9Value, digits); - charsWritten = bufferLength; - return true; - } - } - } - - private static unsafe string NegativeBigIntegerToDecStr(ReadOnlySpan base1E9Value, int digits, string sNegative) - { - Debug.Assert(digits > (base1E9Value.Length - 1) * 9); - - int bufferLength = digits + sNegative.Length; - - fixed (uint* valuePtr = &MemoryMarshal.GetReference(base1E9Value)) - { - return string.Create(bufferLength, (digits, ptr: (nint)valuePtr, base1E9Value.Length, sNegative), static (span, state) => - { - state.sNegative.CopyTo(span); - fixed (char* ptr = &MemoryMarshal.GetReference(span)) - { - BigIntegerToDecChars(ptr + span.Length, new ReadOnlySpan((uint*)state.ptr, state.Length), state.digits); - } - }); - } - } - private static unsafe TChar* BigIntegerToDecChars(TChar* bufferEnd, ReadOnlySpan base1E9Value, int digits) where TChar : unmanaged, IBinaryInteger // CoreLib uses IUtfChar { From ec1a71471289f7567879031ecb57bc0d60738f32 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 6 Dec 2023 00:23:19 +0800 Subject: [PATCH 07/11] Use stackalloc for buffers --- .../src/System/Number.BigInteger.cs | 133 ++++++++++-------- 1 file changed, 75 insertions(+), 58 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index fe64cf4dd0e3ac..98fef1e1b5a9b3 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -883,6 +883,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo } else { + Debug.Assert(formatString != null); charsWritten = 0; spanSuccess = false; return value._sign.ToString(formatString, info); @@ -899,84 +900,94 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo Debug.Assert(cuSrc < int.MaxValue / kcchBase / 2); int cuMax = cuSrc * 10 / 9 + 2; - uint[] base1E9Buffer = ArrayPool.Shared.Rent(cuMax); + uint[]? bufferToReturn = null; + Span base1E9Buffer = cuMax < BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[cuMax] : + (bufferToReturn = ArrayPool.Shared.Rent(cuMax)); - try - { - int cuDst = 0; + int cuDst = 0; - for (int iuSrc = cuSrc; --iuSrc >= 0;) + for (int iuSrc = cuSrc; --iuSrc >= 0;) + { + uint uCarry = value._bits[iuSrc]; + for (int iuDst = 0; iuDst < cuDst; iuDst++) { - uint uCarry = value._bits[iuSrc]; - for (int iuDst = 0; iuDst < cuDst; iuDst++) - { - Debug.Assert(base1E9Buffer[iuDst] < kuBase); + Debug.Assert(base1E9Buffer[iuDst] < kuBase); - // Use X86Base.DivRem when stable - ulong uuRes = NumericsHelpers.MakeUInt64(base1E9Buffer[iuDst], uCarry); - (ulong quo, ulong rem) = Math.DivRem(uuRes, kuBase); - uCarry = (uint)quo; - base1E9Buffer[iuDst] = (uint)rem; - } + // Use X86Base.DivRem when stable + ulong uuRes = NumericsHelpers.MakeUInt64(base1E9Buffer[iuDst], uCarry); + (ulong quo, ulong rem) = Math.DivRem(uuRes, kuBase); + uCarry = (uint)quo; + base1E9Buffer[iuDst] = (uint)rem; + } + if (uCarry != 0) + { + (uCarry, base1E9Buffer[cuDst++]) = Math.DivRem(uCarry, kuBase); if (uCarry != 0) - { - (uCarry, base1E9Buffer[cuDst++]) = Math.DivRem(uCarry, kuBase); - if (uCarry != 0) - base1E9Buffer[cuDst++] = uCarry; - } + base1E9Buffer[cuDst++] = uCarry; } + } - ReadOnlySpan base1E9Value = base1E9Buffer.AsSpan(0, cuDst); + ReadOnlySpan base1E9Value = base1E9Buffer[..cuDst]; - int valueDigits = (base1E9Value.Length - 1) * 9 + FormattingHelpers.CountDigits(base1E9Value[^1]); + int valueDigits = (base1E9Value.Length - 1) * 9 + FormattingHelpers.CountDigits(base1E9Value[^1]); - if (fmt == 'g' || fmt == 'G' || fmt == 'd' || fmt == 'D' || fmt == 'r' || fmt == 'R') - { - int strDigits = Math.Max(digits, valueDigits); - string? sNegative = value.Sign < 0 ? info.NegativeSign : null; - int strLength = digits + (sNegative?.Length ?? 0); + string? strResult; - if (targetSpan) + if (fmt == 'g' || fmt == 'G' || fmt == 'd' || fmt == 'D' || fmt == 'r' || fmt == 'R') + { + int strDigits = Math.Max(digits, valueDigits); + string? sNegative = value.Sign < 0 ? info.NegativeSign : null; + int strLength = digits + (sNegative?.Length ?? 0); + + if (targetSpan) + { + if (destination.Length < strLength) { - if (destination.Length < strLength) + spanSuccess = false; + charsWritten = 0; + } + else + { + sNegative?.CopyTo(destination); + fixed (char* ptr = &MemoryMarshal.GetReference(destination)) { - spanSuccess = false; - charsWritten = 0; + BigIntegerToDecChars(ptr + strLength, base1E9Value, digits); } - else - { - sNegative?.CopyTo(destination); - fixed (char* ptr = &MemoryMarshal.GetReference(destination)) - { - BigIntegerToDecChars(ptr + strLength, base1E9Value, digits); - } - charsWritten = strLength; - spanSuccess = true; - } - return null; + charsWritten = strLength; + spanSuccess = true; } - else + strResult = null; + } + else + { + spanSuccess = false; + charsWritten = 0; + fixed (uint* ptr = base1E9Value) { - spanSuccess = false; - charsWritten = 0; - return string.Create(strLength, (digits, base1E9Buffer, base1E9Value.Length, sNegative), static (span, state) => + strResult = string.Create(strLength, (digits, ptr: (IntPtr)ptr, base1E9Value.Length, sNegative), static (span, state) => { state.sNegative?.CopyTo(span); fixed (char* ptr = &MemoryMarshal.GetReference(span)) { - BigIntegerToDecChars(ptr + span.Length, state.base1E9Buffer.AsSpan(0, state.Length), state.digits); + BigIntegerToDecChars(ptr + span.Length, new ReadOnlySpan((void*)state.ptr, state.Length), state.digits); } }); } } - - byte[]? buffer = ArrayPool.Shared.Rent(valueDigits + 1); - fixed (byte* ptr = buffer) // NumberBuffer expects pinned Digits + } + else + { + byte[]? numberBufferToReturn = null; + Span numberBuffer = valueDigits + 1 <= 256 ? + stackalloc byte[valueDigits + 1] : + (numberBufferToReturn = ArrayPool.Shared.Rent(valueDigits + 1)); + fixed (byte* ptr = numberBuffer) // NumberBuffer expects pinned Digits { scoped NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, ptr, valueDigits + 1); BigIntegerToDecChars(ptr + valueDigits, base1E9Value, valueDigits); number.Digits[^1] = 0; - number.DigitsCount = valueDigits; + number.DigitsCount = DecimalPrecision; number.Scale = valueDigits; number.IsNegative = value.Sign < 0; @@ -994,23 +1005,29 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo if (targetSpan) { spanSuccess = vlb.TryCopyTo(MemoryMarshal.Cast(destination), out charsWritten); - vlb.Dispose(); - return null; + strResult = null; } else { charsWritten = 0; spanSuccess = false; - string result = MemoryMarshal.Cast(vlb.AsSpan()).ToString(); - vlb.Dispose(); - return result; + strResult = MemoryMarshal.Cast(vlb.AsSpan()).ToString(); + } + + vlb.Dispose(); + if (numberBufferToReturn != null) + { + ArrayPool.Shared.Return(numberBufferToReturn); } } } - finally + + if (bufferToReturn != null) { - ArrayPool.Shared.Return(base1E9Buffer); + ArrayPool.Shared.Return(bufferToReturn); } + + return strResult; } private static unsafe TChar* BigIntegerToDecChars(TChar* bufferEnd, ReadOnlySpan base1E9Value, int digits) From adf6a6f1d34feccd6078706cbd918084b48d86c9 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 6 Dec 2023 00:50:48 +0800 Subject: [PATCH 08/11] Update constant usage --- .../src/System/Number.Formatting.Common.cs | 2 ++ .../src/System/Number.Formatting.cs | 2 -- .../src/System/Number.BigInteger.cs | 26 +++++++++---------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/libraries/Common/src/System/Number.Formatting.Common.cs b/src/libraries/Common/src/System/Number.Formatting.Common.cs index 4caa59472eaea4..672ff2402682b9 100644 --- a/src/libraries/Common/src/System/Number.Formatting.Common.cs +++ b/src/libraries/Common/src/System/Number.Formatting.Common.cs @@ -12,6 +12,8 @@ namespace System { internal static partial class Number { + private const int CharStackBufferSize = 32; + private const int DefaultPrecisionExponentialFormat = 6; private const int MaxUInt32DecDigits = 10; 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 ad75d88cbda7c1..2a89bf96385294 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -269,8 +269,6 @@ internal static partial class Number private const int SinglePrecisionCustomFormat = 7; private const int DoublePrecisionCustomFormat = 15; - private const int CharStackBufferSize = 32; - /// The non-inclusive upper bound of . /// /// This is a semi-arbitrary bound. For mono, which is often used for more size-constrained workloads, diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 98fef1e1b5a9b3..25b7a4bba7c576 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -851,6 +851,9 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo return spanSuccess; } + private const uint kuBase = 1_000_000_000; // 10^9 + private const int kcchBase = 9; + private static unsafe string? FormatBigInteger( bool targetSpan, BigInteger value, string? formatString, ReadOnlySpan formatSpan, @@ -891,15 +894,12 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo } // First convert to base 10^9. - const uint kuBase = 1_000_000_000; // 10^9 - const int kcchBase = 9; - - // Each uint contributes at most 9 digits to the decimal representation. - // The current max length is int32.MaxValue bits int cuSrc = value._bits.Length; - Debug.Assert(cuSrc < int.MaxValue / kcchBase / 2); + // A quick conservative max length of base 10^9 representation + // A uint contributes to no more than 10/9 of 10^9 block, +1 for ceiling of division + int cuMax = cuSrc * (kcchBase + 1) / kcchBase + 1; + Debug.Assert((long)BigInteger.MaxLength * (kcchBase + 1) / kcchBase + 1 < (long)int.MaxValue); // won't overflow - int cuMax = cuSrc * 10 / 9 + 2; uint[]? bufferToReturn = null; Span base1E9Buffer = cuMax < BigIntegerCalculator.StackAllocThreshold ? stackalloc uint[cuMax] : @@ -930,7 +930,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo ReadOnlySpan base1E9Value = base1E9Buffer[..cuDst]; - int valueDigits = (base1E9Value.Length - 1) * 9 + FormattingHelpers.CountDigits(base1E9Value[^1]); + int valueDigits = (base1E9Value.Length - 1) * kcchBase + FormattingHelpers.CountDigits(base1E9Value[^1]); string? strResult; @@ -979,7 +979,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo else { byte[]? numberBufferToReturn = null; - Span numberBuffer = valueDigits + 1 <= 256 ? + Span numberBuffer = valueDigits + 1 <= CharStackBufferSize ? stackalloc byte[valueDigits + 1] : (numberBufferToReturn = ArrayPool.Shared.Rent(valueDigits + 1)); fixed (byte* ptr = numberBuffer) // NumberBuffer expects pinned Digits @@ -987,11 +987,11 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo scoped NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, ptr, valueDigits + 1); BigIntegerToDecChars(ptr + valueDigits, base1E9Value, valueDigits); number.Digits[^1] = 0; - number.DigitsCount = DecimalPrecision; + number.DigitsCount = valueDigits; number.Scale = valueDigits; number.IsNegative = value.Sign < 0; - scoped var vlb = new ValueListBuilder(stackalloc Utf16Char[128]); // arbitrary stack cut-off + scoped var vlb = new ValueListBuilder(stackalloc Utf16Char[CharStackBufferSize]); // arbitrary stack cut-off if (fmt != 0) { @@ -1040,9 +1040,9 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo for (int i = 0; i < base1E9Value.Length - 1; i++) { // TODO: is it worthy to introduce optimized UInt32ToDecChars from CoreLib? - digits -= 9; + digits -= kcchBase; uint value = base1E9Value[i]; - for (int j = 0; j < 9; j++) + for (int j = 0; j < kcchBase; j++) { (value, uint digit) = Math.DivRem(value, 10); *(--bufferEnd) = TChar.CreateTruncating('0' + digit); From cdbe4159d3afe2d168a204781f3933ac33bb4e5d Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 6 Dec 2023 13:01:05 +0800 Subject: [PATCH 09/11] Use UInt32ToDecChars --- .../src/System/Number.BigInteger.cs | 26 +++++-------------- .../src/System/Number.Polyfill.cs | 15 +++++++++++ 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 25b7a4bba7c576..8e7f05947ac000 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -952,7 +952,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo sNegative?.CopyTo(destination); fixed (char* ptr = &MemoryMarshal.GetReference(destination)) { - BigIntegerToDecChars(ptr + strLength, base1E9Value, digits); + BigIntegerToDecChars((Utf16Char*)ptr + strLength, base1E9Value, digits); } charsWritten = strLength; spanSuccess = true; @@ -970,7 +970,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo state.sNegative?.CopyTo(span); fixed (char* ptr = &MemoryMarshal.GetReference(span)) { - BigIntegerToDecChars(ptr + span.Length, new ReadOnlySpan((void*)state.ptr, state.Length), state.digits); + BigIntegerToDecChars((Utf16Char*)ptr + span.Length, new ReadOnlySpan((void*)state.ptr, state.Length), state.digits); } }); } @@ -985,7 +985,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo fixed (byte* ptr = numberBuffer) // NumberBuffer expects pinned Digits { scoped NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, ptr, valueDigits + 1); - BigIntegerToDecChars(ptr + valueDigits, base1E9Value, valueDigits); + BigIntegerToDecChars((Utf8Char*)ptr + valueDigits, base1E9Value, valueDigits); number.Digits[^1] = 0; number.DigitsCount = valueDigits; number.Scale = valueDigits; @@ -1031,7 +1031,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo } private static unsafe TChar* BigIntegerToDecChars(TChar* bufferEnd, ReadOnlySpan base1E9Value, int digits) - where TChar : unmanaged, IBinaryInteger // CoreLib uses IUtfChar + where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); Debug.Assert(base1E9Value[^1] != 0, "Leading zeros should be trimmed by caller."); @@ -1039,25 +1039,11 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo // The base 10^9 value is in reverse order for (int i = 0; i < base1E9Value.Length - 1; i++) { - // TODO: is it worthy to introduce optimized UInt32ToDecChars from CoreLib? + bufferEnd = UInt32ToDecChars(bufferEnd, base1E9Value[i], kcchBase); digits -= kcchBase; - uint value = base1E9Value[i]; - for (int j = 0; j < kcchBase; j++) - { - (value, uint digit) = Math.DivRem(value, 10); - *(--bufferEnd) = TChar.CreateTruncating('0' + digit); - } - } - - Debug.Assert(digits >= FormattingHelpers.CountDigits(base1E9Value[^1])); - for (uint value = base1E9Value[^1]; value > 0 || digits > 0;) - { - digits--; - (value, uint digit) = Math.DivRem(value, 10); - *(--bufferEnd) = TChar.CreateTruncating('0' + digit); } - return bufferEnd; + return UInt32ToDecChars(bufferEnd, base1E9Value[^1], digits); } } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs index d5de46b5842eb5..9c1c2a89daf5ef 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs @@ -43,6 +43,21 @@ internal readonly struct Utf16Char(char ch) : IUtfChar public bool Equals(Utf16Char other) => value == other.value; } +#pragma warning disable CA1067 // Polyfill only type + internal readonly struct Utf8Char(byte ch) : IUtfChar +#pragma warning restore CA1067 + { + private readonly byte value = ch; + + public static Utf8Char CastFrom(byte value) => new(value); + public static Utf8Char CastFrom(char value) => new((byte)value); + public static Utf8Char CastFrom(int value) => new((byte)value); + public static Utf8Char CastFrom(uint value) => new((byte)value); + public static Utf8Char CastFrom(ulong value) => new((byte)value); + public static uint CastToUInt32(Utf8Char value) => value.value; + public bool Equals(Utf8Char other) => value == other.value; + } + internal static partial class Number { internal static bool AllowHyphenDuringParsing(this NumberFormatInfo info) From 2bd21dc7898e89054205a63917546d0091cdc498 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 23 Mar 2024 15:06:18 +0800 Subject: [PATCH 10/11] Fix type assert --- .../System.Runtime.Numerics/src/System/Number.BigInteger.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 8e7f05947ac000..1c009848d19be6 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -1033,7 +1033,6 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo private static unsafe TChar* BigIntegerToDecChars(TChar* bufferEnd, ReadOnlySpan base1E9Value, int digits) where TChar : unmanaged, IUtfChar { - Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); Debug.Assert(base1E9Value[^1] != 0, "Leading zeros should be trimmed by caller."); // The base 10^9 value is in reverse order From 45973b4f79d257f481c32a622f3d010e1388d50d Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 23 Mar 2024 19:22:15 +0800 Subject: [PATCH 11/11] Fix strLength --- .../System.Runtime.Numerics/src/System/Number.BigInteger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 1c009848d19be6..452cbd3759fea6 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -938,7 +938,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo { int strDigits = Math.Max(digits, valueDigits); string? sNegative = value.Sign < 0 ? info.NegativeSign : null; - int strLength = digits + (sNegative?.Length ?? 0); + int strLength = strDigits + (sNegative?.Length ?? 0); if (targetSpan) {