-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Unify int to hexadecimal char conversions (#1273)
* Unify int to hexadecimal char conversions This replaces about 7 different implementations with a single version
- Loading branch information
1 parent
6736356
commit 91c1d7c
Showing
70 changed files
with
413 additions
and
457 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Runtime.CompilerServices; | ||
|
||
namespace System | ||
{ | ||
internal static class HexConverter | ||
{ | ||
public enum Casing : uint | ||
{ | ||
// Output [ '0' .. '9' ] and [ 'A' .. 'F' ]. | ||
Upper = 0, | ||
|
||
// Output [ '0' .. '9' ] and [ 'a' .. 'f' ]. | ||
// This works because values in the range [ 0x30 .. 0x39 ] ([ '0' .. '9' ]) | ||
// already have the 0x20 bit set, so ORing them with 0x20 is a no-op, | ||
// while outputs in the range [ 0x41 .. 0x46 ] ([ 'A' .. 'F' ]) | ||
// don't have the 0x20 bit set, so ORing them maps to | ||
// [ 0x61 .. 0x66 ] ([ 'a' .. 'f' ]), which is what we want. | ||
Lower = 0x2020U, | ||
} | ||
|
||
// We want to pack the incoming byte into a single integer [ 0000 HHHH 0000 LLLL ], | ||
// where HHHH and LLLL are the high and low nibbles of the incoming byte. Then | ||
// subtract this integer from a constant minuend as shown below. | ||
// | ||
// [ 1000 1001 1000 1001 ] | ||
// - [ 0000 HHHH 0000 LLLL ] | ||
// ========================= | ||
// [ *YYY **** *ZZZ **** ] | ||
// | ||
// The end result of this is that YYY is 0b000 if HHHH <= 9, and YYY is 0b111 if HHHH >= 10. | ||
// Similarly, ZZZ is 0b000 if LLLL <= 9, and ZZZ is 0b111 if LLLL >= 10. | ||
// (We don't care about the value of asterisked bits.) | ||
// | ||
// To turn a nibble in the range [ 0 .. 9 ] into hex, we calculate hex := nibble + 48 (ascii '0'). | ||
// To turn a nibble in the range [ 10 .. 15 ] into hex, we calculate hex := nibble - 10 + 65 (ascii 'A'). | ||
// => hex := nibble + 55. | ||
// The difference in the starting ASCII offset is (55 - 48) = 7, depending on whether the nibble is <= 9 or >= 10. | ||
// Since 7 is 0b111, this conveniently matches the YYY or ZZZ value computed during the earlier subtraction. | ||
|
||
// The commented out code below is code that directly implements the logic described above. | ||
|
||
// uint packedOriginalValues = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU); | ||
// uint difference = 0x8989U - packedOriginalValues; | ||
// uint add7Mask = (difference & 0x7070U) >> 4; // line YYY and ZZZ back up with the packed values | ||
// uint packedResult = packedOriginalValues + add7Mask + 0x3030U /* ascii '0' */; | ||
|
||
// The code below is equivalent to the commented out code above but has been tweaked | ||
// to allow codegen to make some extra optimizations. | ||
|
||
// The low byte of the packed result contains the hex representation of the incoming byte's low nibble. | ||
// The adjacent byte of the packed result contains the hex representation of the incoming byte's high nibble. | ||
|
||
// Finally, write to the output buffer starting with the *highest* index so that codegen can | ||
// elide all but the first bounds check. (This only works if 'startingIndex' is a compile-time constant.) | ||
|
||
// The JIT can elide bounds checks if 'startingIndex' is constant and if the caller is | ||
// writing to a span of known length (or the caller has already checked the bounds of the | ||
// furthest access). | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static void ToBytesBuffer(byte value, Span<byte> buffer, int startingIndex = 0, Casing casing = Casing.Upper) | ||
{ | ||
uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U; | ||
uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing; | ||
|
||
buffer[startingIndex + 1] = (byte)packedResult; | ||
buffer[startingIndex] = (byte)(packedResult >> 8); | ||
} | ||
|
||
#if ALLOW_PARTIALLY_TRUSTED_CALLERS | ||
[System.Security.SecuritySafeCriticalAttribute] | ||
#endif | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static void ToCharsBuffer(byte value, Span<char> buffer, int startingIndex = 0, Casing casing = Casing.Upper) | ||
{ | ||
uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U; | ||
uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing; | ||
|
||
buffer[startingIndex + 1] = (char)(packedResult & 0xFF); | ||
buffer[startingIndex] = (char)(packedResult >> 8); | ||
} | ||
|
||
#if ALLOW_PARTIALLY_TRUSTED_CALLERS | ||
[System.Security.SecuritySafeCriticalAttribute] | ||
#endif | ||
public static unsafe string ToString(ReadOnlySpan<byte> bytes, Casing casing = Casing.Upper) | ||
{ | ||
#if NET45 || NET46 || NET461 || NET462 || NET47 || NET471 || NET472 || NETSTANDARD1_0 || NETSTANDARD1_3 || NETSTANDARD2_0 | ||
Span<char> result = stackalloc char[0]; | ||
if (bytes.Length > 16) | ||
{ | ||
var array = new char[bytes.Length * 2]; | ||
result = array.AsSpan(); | ||
} | ||
else | ||
{ | ||
result = stackalloc char[bytes.Length * 2]; | ||
} | ||
|
||
int pos = 0; | ||
foreach (byte b in bytes) | ||
{ | ||
ToCharsBuffer(b, result, pos, casing); | ||
pos += 2; | ||
} | ||
return result.ToString(); | ||
#else | ||
fixed (byte* bytesPtr = bytes) | ||
{ | ||
return string.Create(bytes.Length * 2, (Ptr: (IntPtr)bytesPtr, bytes.Length, casing), (chars, args) => | ||
{ | ||
var ros = new ReadOnlySpan<byte>((byte*)args.Ptr, args.Length); | ||
for (int pos = 0; pos < ros.Length; ++pos) | ||
{ | ||
ToCharsBuffer(ros[pos], chars, pos * 2, args.casing); | ||
} | ||
}); | ||
} | ||
#endif | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static char ToCharUpper(int value) | ||
{ | ||
value &= 0xF; | ||
value += '0'; | ||
|
||
if (value > '9') | ||
{ | ||
value += ('A' - ('9' + 1)); | ||
} | ||
|
||
return (char)value; | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static char ToCharLower(int value) | ||
{ | ||
value &= 0xF; | ||
value += '0'; | ||
|
||
if (value > '9') | ||
{ | ||
value += ('a' - ('9' + 1)); | ||
} | ||
|
||
return (char)value; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.