From c81eca8f65662dc5169327df5db6e2470979940e Mon Sep 17 00:00:00 2001 From: Linus Hamlin <78953007+lilinus@users.noreply.github.com> Date: Mon, 6 May 2024 18:04:31 +0200 Subject: [PATCH] Refactor some DateTime and TimeSpan formatting/parsing methods (#101640) * Refactor some DateTime and TimeSpan formatting/parsing methods * Fix assertion in TimeSpanParse.Pow10 * Don't use Unsafe in TimeSpanParse.Pow10 * Revert changes to TimeSpanParse.Pow10 * Revert "Revert changes to TimeSpanParse.Pow10" This reverts commit 267d5e861c022c338de5004bb6c85e26d6179039. * Change method name to Pow10UpToMaxFractionDigits * Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction * Address feedback in TimeSpanParse * Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction * Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction * Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com> --------- Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com> --- .../System/Globalization/DateTimeFormat.cs | 8 ++-- .../src/System/Globalization/DateTimeParse.cs | 3 +- .../System/Globalization/TimeSpanFormat.cs | 10 ++--- .../src/System/Globalization/TimeSpanParse.cs | 45 ++++++++++++------- 4 files changed, 40 insertions(+), 26 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs index 895a9e83b6a89..db12f89bf2dd1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs @@ -484,11 +484,11 @@ private static void FormatCustomized( tokenLen = ParseRepeatPattern(format, i, ch); if (tokenLen <= MaxSecondsFractionDigits) { - long fraction = (dateTime.Ticks % Calendar.TicksPerSecond); - fraction /= (long)Math.Pow(10, 7 - tokenLen); + int fraction = (int)(dateTime.Ticks % Calendar.TicksPerSecond); + fraction /= TimeSpanParse.Pow10UpToMaxFractionDigits(MaxSecondsFractionDigits - tokenLen); if (ch == 'f') { - FormatFraction(ref result, (int)fraction, fixedNumberFormats[tokenLen - 1]); + FormatFraction(ref result, fraction, fixedNumberFormats[tokenLen - 1]); } else { @@ -507,7 +507,7 @@ private static void FormatCustomized( } if (effectiveDigits > 0) { - FormatFraction(ref result, (int)fraction, fixedNumberFormats[effectiveDigits - 1]); + FormatFraction(ref result, fraction, fixedNumberFormats[effectiveDigits - 1]); } else { diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs index 687cafbe9ca50..9613bddf43549 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs @@ -3179,6 +3179,7 @@ internal static bool ParseDigits(ref __DTString str, int minDigitLen, int maxDig private static bool ParseFractionExact(ref __DTString str, int maxDigitLen, scoped ref double result) { + Debug.Assert(maxDigitLen <= DateTimeFormat.MaxSecondsFractionDigits); if (!str.GetNextDigit()) { str.Index--; @@ -3197,7 +3198,7 @@ private static bool ParseFractionExact(ref __DTString str, int maxDigitLen, scop result = result * 10 + str.GetDigit(); } - result /= TimeSpanParse.Pow10(digitLen); + result /= TimeSpanParse.Pow10UpToMaxFractionDigits(digitLen); return digitLen == maxDigitLen; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanFormat.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanFormat.cs index 8c8bea12bbf6b..48faa5b1a9834 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanFormat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanFormat.cs @@ -311,7 +311,7 @@ private static void FormatCustomized(TimeSpan value, scoped ReadOnlySpan< int seconds = (int)(time / TimeSpan.TicksPerSecond % 60); int fraction = (int)(time % TimeSpan.TicksPerSecond); - long tmp; + int tmp; int i = 0; int tokenLen; @@ -356,8 +356,8 @@ private static void FormatCustomized(TimeSpan value, scoped ReadOnlySpan< } tmp = fraction; - tmp /= TimeSpanParse.Pow10(DateTimeFormat.MaxSecondsFractionDigits - tokenLen); - DateTimeFormat.FormatFraction(ref result, (int)tmp, DateTimeFormat.fixedNumberFormats[tokenLen - 1]); + tmp /= TimeSpanParse.Pow10UpToMaxFractionDigits(DateTimeFormat.MaxSecondsFractionDigits - tokenLen); + DateTimeFormat.FormatFraction(ref result, tmp, DateTimeFormat.fixedNumberFormats[tokenLen - 1]); break; case 'F': // @@ -370,7 +370,7 @@ private static void FormatCustomized(TimeSpan value, scoped ReadOnlySpan< } tmp = fraction; - tmp /= TimeSpanParse.Pow10(DateTimeFormat.MaxSecondsFractionDigits - tokenLen); + tmp /= TimeSpanParse.Pow10UpToMaxFractionDigits(DateTimeFormat.MaxSecondsFractionDigits - tokenLen); int effectiveDigits = tokenLen; while (effectiveDigits > 0) { @@ -386,7 +386,7 @@ private static void FormatCustomized(TimeSpan value, scoped ReadOnlySpan< } if (effectiveDigits > 0) { - DateTimeFormat.FormatFraction(ref result, (int)tmp, DateTimeFormat.fixedNumberFormats[effectiveDigits - 1]); + DateTimeFormat.FormatFraction(ref result, tmp, DateTimeFormat.fixedNumberFormats[effectiveDigits - 1]); } break; case 'd': diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs index c900e64dcb969..033d0abe4e904 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs @@ -47,6 +47,7 @@ // //////////////////////////////////////////////////////////////////////////// +using System.Buffers.Text; using System.Diagnostics; using System.Text; @@ -114,7 +115,7 @@ public bool NormalizeAndValidateFraction() if (_zeroes == 0 && _num > MaxFraction) return false; - int totalDigitsCount = ((int)Math.Floor(Math.Log10(_num))) + 1 + _zeroes; + int totalDigitsCount = FormattingHelpers.CountDigits((uint)_num) + _zeroes; if (totalDigitsCount == MaxFractionDigits) { @@ -132,7 +133,7 @@ public bool NormalizeAndValidateFraction() // .000001 normalize to 10 ticks // .1 normalize to 1,000,000 ticks - _num *= (int)Pow10(MaxFractionDigits - totalDigitsCount); + _num *= Pow10UpToMaxFractionDigits(MaxFractionDigits - totalDigitsCount); return true; } @@ -143,7 +144,18 @@ public bool NormalizeAndValidateFraction() // .099999999 normalize to 1,000,000 ticks Debug.Assert(_zeroes > 0); // Already validated that in the condition _zeroes == 0 && _num > MaxFraction - _num = (int)Math.Round((double)_num / Pow10(totalDigitsCount - MaxFractionDigits), MidpointRounding.AwayFromZero); + + if (_zeroes > MaxFractionDigits) + { + // If there are 8 leading zeroes, it rounds to zero + _num = 0; + return true; + } + + Debug.Assert(totalDigitsCount - MaxFractionDigits <= MaxFractionDigits); + uint power = (uint)Pow10UpToMaxFractionDigits(totalDigitsCount - MaxFractionDigits); + // Unsigned integer division, rounding away from zero + _num = (int)(((uint)_num + power / 2) / power); Debug.Assert(_num < MaxFraction); return true; @@ -563,20 +575,21 @@ internal bool SetBadFormatSpecifierFailure(char? formatSpecifierCharacter = null } } - internal static long Pow10(int pow) + internal static int Pow10UpToMaxFractionDigits(int pow) { - return pow switch - { - 0 => 1, - 1 => 10, - 2 => 100, - 3 => 1000, - 4 => 10000, - 5 => 100000, - 6 => 1000000, - 7 => 10000000, - _ => (long)Math.Pow(10, pow), - }; + ReadOnlySpan powersOfTen = + [ + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + ]; + Debug.Assert(powersOfTen.Length == MaxFractionDigits + 1); + return powersOfTen[pow]; } private static bool TryTimeToTicks(bool positive, TimeSpanToken days, TimeSpanToken hours, TimeSpanToken minutes, TimeSpanToken seconds, TimeSpanToken fraction, out long result)