Summary

Class:System.Buffers.Text.FormattingHelpers
Assembly:System.Memory
File(s):C:\GitHub\corefx\src\System.Memory\src\System\Buffers\Text\Utf8Formatter\FormattingHelpers.cs
Covered lines:193
Uncovered lines:8
Coverable lines:201
Total lines:397
Line coverage:96%
Branch coverage:92%

Metrics

MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage
GetSymbolOrDefault(...)32100100
FillWithAsciiZeros(...)22100100
WriteHexByte(...)10100100
WriteDigits(...)22100100
WriteDigitsWithGroupSeparator(...)34100100
WriteDigits(...)22100100
WriteFourDecimalDigits(...)20100100
WriteTwoDecimalDigits(...)20100100
DivMod(...)10100100
DivMod(...)10100100
CountDecimalTrailingZeros(...)3410080
CountDigits(...)9256100100
CountDigits(...)63271.4372.73
CountHexDigits(...)516100100

File(s)

C:\GitHub\corefx\src\System.Memory\src\System\Buffers\Text\Utf8Formatter\FormattingHelpers.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3// See the LICENSE file in the project root for more information.
 4
 5using System.Diagnostics;
 6using System.Runtime.CompilerServices;
 7
 8namespace System.Buffers.Text
 9{
 10    // All the helper methods in this class assume that the by-ref is valid and that there is
 11    // enough space to fit the items that will be written into the underlying memory. The calling
 12    // code must have already done all the necessary validation.
 13    internal static class FormattingHelpers
 14    {
 15        // A simple lookup table for converting numbers to hex.
 16        internal const string HexTableLower = "0123456789abcdef";
 17
 18        internal const string HexTableUpper = "0123456789ABCDEF";
 19
 20        /// <summary>
 21        /// Returns the symbol contained within the standard format. If the standard format
 22        /// has not been initialized, returns the provided fallback symbol.
 23        /// </summary>
 24        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 25        public static char GetSymbolOrDefault(in StandardFormat format, char defaultSymbol)
 126        {
 27            // This is equivalent to the line below, but it is written in such a way
 28            // that the JIT is able to perform more optimizations.
 29            //
 30            // return (format.IsDefault) ? defaultSymbol : format.Symbol;
 31
 132            var symbol = format.Symbol;
 133            if (symbol == default && format.Precision == default)
 134            {
 135                symbol = defaultSymbol;
 136            }
 137            return symbol;
 138        }
 39
 40        #region UTF-8 Helper methods
 41
 42        /// <summary>
 43        /// Fills a buffer with the ASCII character '0' (0x30).
 44        /// </summary>
 45        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 46        public static void FillWithAsciiZeros(Span<byte> buffer)
 147        {
 48            // This is a faster implementation of Span<T>.Fill().
 349            for (int i = 0; i < buffer.Length; i++)
 150            {
 151                buffer[i] = (byte)'0';
 152            }
 153        }
 54
 55        public enum HexCasing : uint
 56        {
 57            // Output [ '0' .. '9' ] and [ 'A' .. 'F' ].
 58            Uppercase = 0,
 59
 60            // Output [ '0' .. '9' ] and [ 'a' .. 'f' ].
 61            // This works because values in the range [ 0x30 .. 0x39 ] ([ '0' .. '9' ])
 62            // already have the 0x20 bit set, so ORing them with 0x20 is a no-op,
 63            // while outputs in the range [ 0x41 .. 0x46 ] ([ 'A' .. 'F' ])
 64            // don't have the 0x20 bit set, so ORing them maps to
 65            // [ 0x61 .. 0x66 ] ([ 'a' .. 'f' ]), which is what we want.
 66            Lowercase = 0x2020U,
 67        }
 68
 69        // The JIT can elide bounds checks if 'startingIndex' is constant and if the caller is
 70        // writing to a span of known length (or the caller has already checked the bounds of the
 71        // furthest access).
 72        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 73        public static void WriteHexByte(byte value, Span<byte> buffer, int startingIndex = 0, HexCasing casing = HexCasi
 174        {
 75            // We want to pack the incoming byte into a single integer [ 0000 HHHH 0000 LLLL ],
 76            // where HHHH and LLLL are the high and low nibbles of the incoming byte. Then
 77            // subtract this integer from a constant minuend as shown below.
 78            //
 79            //   [ 1000 1001 1000 1001 ]
 80            // - [ 0000 HHHH 0000 LLLL ]
 81            // =========================
 82            //   [ *YYY **** *ZZZ **** ]
 83            //
 84            // The end result of this is that YYY is 0b000 if HHHH <= 9, and YYY is 0b111 if HHHH >= 10.
 85            // Similarly, ZZZ is 0b000 if LLLL <= 9, and ZZZ is 0b111 if LLLL >= 10.
 86            // (We don't care about the value of asterisked bits.)
 87            //
 88            // To turn a nibble in the range [ 0 .. 9 ] into hex, we calculate hex := nibble + 48 (ascii '0').
 89            // To turn a nibble in the range [ 10 .. 15 ] into hex, we calculate hex := nibble - 10 + 65 (ascii 'A').
 90            //                                                                => hex := nibble + 55.
 91            // The difference in the starting ASCII offset is (55 - 48) = 7, depending on whether the nibble is <= 9 or 
 92            // Since 7 is 0b111, this conveniently matches the YYY or ZZZ value computed during the earlier subtraction.
 93
 94            // The commented out code below is code that directly implements the logic described above.
 95
 96            //uint packedOriginalValues = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU);
 97            //uint difference = 0x8989U - packedOriginalValues;
 98            //uint add7Mask = (difference & 0x7070U) >> 4; // line YYY and ZZZ back up with the packed values
 99            //uint packedResult = packedOriginalValues + add7Mask + 0x3030U /* ascii '0' */;
 100
 101            // The code below is equivalent to the commented out code above but has been tweaked
 102            // to allow codegen to make some extra optimizations.
 103
 1104            uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U;
 1105            uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing;
 106
 107            // The low byte of the packed result contains the hex representation of the incoming byte's low nibble.
 108            // The adjacent byte of the packed result contains the hex representation of the incoming byte's high nibble
 109
 110            // Finally, write to the output buffer starting with the *highest* index so that codegen can
 111            // elide all but the first bounds check. (This only works if 'startingIndex' is a compile-time constant.)
 112
 1113            buffer[startingIndex + 1] = (byte)(packedResult);
 1114            buffer[startingIndex] = (byte)(packedResult >> 8);
 1115        }
 116
 117        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 118        public static void WriteDigits(ulong value, Span<byte> buffer)
 1119        {
 120            // We can mutate the 'value' parameter since it's a copy-by-value local.
 121            // It'll be used to represent the value left over after each division by 10.
 122
 3123            for (int i = buffer.Length - 1; i >= 1; i--)
 1124            {
 1125                ulong temp = '0' + value;
 1126                value /= 10;
 1127                buffer[i] = (byte)(temp - (value * 10));
 1128            }
 129
 1130            Debug.Assert(value < 10);
 1131            buffer[0] = (byte)('0' + value);
 1132        }
 133
 134        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 135        public static void WriteDigitsWithGroupSeparator(ulong value, Span<byte> buffer)
 1136        {
 137            // We can mutate the 'value' parameter since it's a copy-by-value local.
 138            // It'll be used to represent the value left over after each division by 10.
 139
 1140            int digitsWritten = 0;
 3141            for (int i = buffer.Length - 1; i >= 1; i--)
 1142            {
 1143                ulong temp = '0' + value;
 1144                value /= 10;
 1145                buffer[i] = (byte)(temp - (value * 10));
 1146                if (digitsWritten == Utf8Constants.GroupSize - 1)
 1147                {
 1148                    buffer[--i] = Utf8Constants.Comma;
 1149                    digitsWritten = 0;
 1150                }
 151                else
 1152                {
 1153                    digitsWritten++;
 1154                }
 1155            }
 156
 1157            Debug.Assert(value < 10);
 1158            buffer[0] = (byte)('0' + value);
 1159        }
 160
 161        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 162        public static void WriteDigits(uint value, Span<byte> buffer)
 1163        {
 164            // We can mutate the 'value' parameter since it's a copy-by-value local.
 165            // It'll be used to represent the value left over after each division by 10.
 166
 3167            for (int i = buffer.Length - 1; i >= 1; i--)
 1168            {
 1169                uint temp = '0' + value;
 1170                value /= 10;
 1171                buffer[i] = (byte)(temp - (value * 10));
 1172            }
 173
 1174            Debug.Assert(value < 10);
 1175            buffer[0] = (byte)('0' + value);
 1176        }
 177
 178        /// <summary>
 179        /// Writes a value [ 0000 .. 9999 ] to the buffer starting at the specified offset.
 180        /// This method performs best when the starting index is a constant literal.
 181        /// </summary>
 182        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 183        public static void WriteFourDecimalDigits(uint value, Span<byte> buffer, int startingIndex = 0)
 1184        {
 1185            Debug.Assert(0 <= value && value <= 9999);
 186
 1187            uint temp = '0' + value;
 1188            value /= 10;
 1189            buffer[startingIndex + 3] = (byte)(temp - (value * 10));
 190
 1191            temp = '0' + value;
 1192            value /= 10;
 1193            buffer[startingIndex + 2] = (byte)(temp - (value * 10));
 194
 1195            temp = '0' + value;
 1196            value /= 10;
 1197            buffer[startingIndex + 1] = (byte)(temp - (value * 10));
 198
 1199            buffer[startingIndex] = (byte)('0' + value);
 1200        }
 201
 202        /// <summary>
 203        /// Writes a value [ 00 .. 99 ] to the buffer starting at the specified offset.
 204        /// This method performs best when the starting index is a constant literal.
 205        /// </summary>
 206        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 207        public static void WriteTwoDecimalDigits(uint value, Span<byte> buffer, int startingIndex = 0)
 1208        {
 1209            Debug.Assert(0 <= value && value <= 99);
 210
 1211            uint temp = '0' + value;
 1212            value /= 10;
 1213            buffer[startingIndex + 1] = (byte)(temp - (value * 10));
 214
 1215            buffer[startingIndex] = (byte)('0' + value);
 1216        }
 217
 218        #endregion UTF-8 Helper methods
 219
 220        #region Math Helper methods
 221
 222        /// <summary>
 223        /// We don't have access to Math.DivRem, so this is a copy of the implementation.
 224        /// </summary>
 225        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 226        public static ulong DivMod(ulong numerator, ulong denominator, out ulong modulo)
 1227        {
 1228            ulong div = numerator / denominator;
 1229            modulo = numerator - (div * denominator);
 1230            return div;
 1231        }
 232
 233        /// <summary>
 234        /// We don't have access to Math.DivRem, so this is a copy of the implementation.
 235        /// </summary>
 236        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 237        public static uint DivMod(uint numerator, uint denominator, out uint modulo)
 1238        {
 1239            uint div = numerator / denominator;
 1240            modulo = numerator - (div * denominator);
 1241            return div;
 1242        }
 243
 244        #endregion Math Helper methods
 245
 246        #region Character counting helper methods
 247
 248        // Counts the number of trailing '0' digits in a decimal numnber.
 249        // e.g., value =      0 => retVal = 0, valueWithoutTrailingZeros = 0
 250        //       value =   1234 => retVal = 0, valueWithoutTrailingZeros = 1234
 251        //       value = 320900 => retVal = 2, valueWithoutTrailingZeros = 3209
 252        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 253        public static int CountDecimalTrailingZeros(uint value, out uint valueWithoutTrailingZeros)
 1254        {
 1255            int zeroCount = 0;
 256
 1257            if (value != 0)
 1258            {
 1259                while (true)
 1260                {
 1261                    uint temp = DivMod(value, 10, out uint modulus);
 1262                    if (modulus != 0)
 1263                    {
 1264                        break;
 265                    }
 1266                    value = temp;
 1267                    zeroCount++;
 1268                }
 1269            }
 270
 1271            valueWithoutTrailingZeros = value;
 1272            return zeroCount;
 1273        }
 274
 275        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 276        public static int CountDigits(ulong value)
 1277        {
 1278            int digits = 1;
 279            uint part;
 1280            if (value >= 10000000)
 1281            {
 1282                if (value >= 100000000000000)
 1283                {
 1284                    part = (uint)(value / 100000000000000);
 1285                    digits += 14;
 1286                }
 287                else
 1288                {
 1289                    part = (uint)(value / 10000000);
 1290                    digits += 7;
 1291                }
 1292            }
 293            else
 1294            {
 1295                part = (uint)value;
 1296            }
 297
 1298            if (part < 10)
 1299            {
 300                // no-op
 1301            }
 1302            else if (part < 100)
 1303            {
 1304                digits += 1;
 1305            }
 1306            else if (part < 1000)
 1307            {
 1308                digits += 2;
 1309            }
 1310            else if (part < 10000)
 1311            {
 1312                digits += 3;
 1313            }
 1314            else if (part < 100000)
 1315            {
 1316                digits += 4;
 1317            }
 1318            else if (part < 1000000)
 1319            {
 1320                digits += 5;
 1321            }
 322            else
 1323            {
 1324                Debug.Assert(part < 10000000);
 1325                digits += 6;
 1326            }
 327
 1328            return digits;
 1329        }
 330
 331        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 332        public static int CountDigits(uint value)
 1333        {
 1334            int digits = 1;
 1335            if (value >= 100000)
 1336            {
 1337                value = value / 100000;
 1338                digits += 5;
 1339            }
 340
 1341            if (value < 10)
 1342            {
 343                // no-op
 1344            }
 1345            else if (value < 100)
 1346            {
 1347                digits += 1;
 1348            }
 1349            else if (value < 1000)
 1350            {
 1351                digits += 2;
 1352            }
 0353            else if (value < 10000)
 0354            {
 0355                digits += 3;
 0356            }
 357            else
 0358            {
 0359                Debug.Assert(value < 100000);
 0360                digits += 4;
 0361            }
 362
 1363            return digits;
 1364        }
 365
 366        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 367        public static int CountHexDigits(ulong value)
 1368        {
 369            // TODO: When x86 intrinsic support comes online, experiment with implementing this using lzcnt.
 370            // return 16 - (int)((uint)Lzcnt.LeadingZeroCount(value | 1) >> 3);
 371
 1372            int digits = 1;
 373
 1374            if (value > 0xFFFFFFFF)
 1375            {
 1376                digits += 8;
 1377                value >>= 0x20;
 1378            }
 1379            if (value > 0xFFFF)
 1380            {
 1381                digits += 4;
 1382                value >>= 0x10;
 1383            }
 1384            if (value > 0xFF)
 1385            {
 1386                digits += 2;
 1387                value >>= 0x8;
 1388            }
 1389            if (value > 0xF)
 1390                digits++;
 391
 1392            return digits;
 1393        }
 394
 395        #endregion Character counting helper methods
 396    }
 397}