133 lines
5.7 KiB
C#
133 lines
5.7 KiB
C#
// 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);
|
|
}
|
|
|
|
[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);
|
|
}
|
|
|
|
public static unsafe string ToString(ReadOnlySpan<byte> bytes, Casing casing = Casing.Upper)
|
|
{
|
|
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();
|
|
}
|
|
|
|
[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;
|
|
}
|
|
}
|
|
} |