てくメモ

trivial な notes

【C#】バイト列→16進数文字列、int→2進数文字列

N進数文字列への変換のなかではやりたいがちな2種について。


まずはバイト列→16進数文字列。
ちなみに、需要が高いのか以下のようなガイドがある。
16 進文字列と数値型の間で変換する方法 - C# プログラミング ガイド - C# | Microsoft Learn


まずBitConverter.ToStringを使う方法がある。

private static ReadOnlySpan<byte> Bytes => "Hello, World"u8;
private byte[] array = Bytes.ToArray();

public string ByBitConverter() => BitConverter.ToString(array);
// 48-65-6C-6C-6F-2C-20-57-6F-72-6C-64

この方法ではセパレーターがハイフン固定で、上述ガイドでは不要ならReplace("-", "")してくださいとなっている。

また、Spanは受け付けてくれない。


Convert.ToHexStringを使う方法もある。

public string ByConvertToHexString() => Convert.ToHexString(Bytes);
// 48656C6C6F2C20576F726C64

比較的新しいAPIだからかSpanを受け付けてくれ、しかも内部でSIMD分岐をかける最適化が図られているよう。
ただ、出力はセパレーター無し固定。

                    Method |     Mean |     Error |   StdDev |   Gen0 | Allocated |
-------------------------- |---------:|----------:|---------:|-------:|----------:|
            ByBitConverter | 52.69 ns | 33.496 ns | 1.836 ns | 0.0306 |      96 B |
      ByConvertToHexString | 27.33 ns |  7.049 ns | 0.386 ns | 0.0229 |      72 B |


単純に16進数文字列を得る場合はConvert.ToHexStringで、セパレーターが入っていてほしくてSpanを渡す必要がなければBitConverter、となりそう。


以下の折りたたみは参考。勉強として書いた、BitConverterを見本にSpanと任意のセパレーターを渡せる感じにしたもの。

折りたたみ

public string ByBitConverterPort()
{
    Span<char> buf = stackalloc char[Bytes.Length * 3 - 1];
    ToHexString(Bytes, buf);
    return new(buf);
    // 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64
}

private static void ToHexString(scoped ReadOnlySpan<byte> bytes, scoped Span<char> buf, char separator = ' ')
{
    int i = 0;
    int j = 0;

    byte b = bytes[i++];
    buf[j++] = ToCharUpper(b >> 4);
    buf[j++] = ToCharUpper(b);

    while (i < bytes.Length)
    {
        b = bytes[i++];
        buf[j++] = separator;
        buf[j++] = ToCharUpper(b >> 4);
        buf[j++] = ToCharUpper(b);
    }
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static char ToCharUpper(int value)
{
    value &= 0xF;
    value += '0';

    if (value > '9')
    {
        value += ('A' - ('9' + 1));
    }

    return (char)value;
}
                    Method |     Mean |     Error |   StdDev |   Gen0 | Allocated |
-------------------------- |---------:|----------:|---------:|-------:|----------:|
        ByBitConverterPort | 58.76 ns | 39.613 ns | 2.171 ns | 0.0305 |      96 B |


次にint→2進数文字列。ここでは、桁数固定(0埋め)を考える。
なお、2進数用の書式指定子は現状存在しない


【追記】
.NET 8 で二進数指定子が追加された。
標準の数値書式指定文字列 - .NET | Microsoft Learn


これには、Convert.ToStringを用いる。

// int bits;
public string ByConvertToString() => Convert.ToString(bits, 2).PadLeft(32, '0');


こちらは、16進数でのConvert.ToHexStringのような相対的に新しいAPIはない。
ただ、汎用化を図らなくていいのであればString.Createで直接0と1を書くというのも敷居は低い。

public string ByStringCreate() => string.Create(32, bits, static (buf, bits) =>
    {
        const uint MASK = ((uint)int.MaxValue) + 1;
        for (int i = 0; i < buf.Length; i++)
        {
            buf[i] = (bits & MASK) != 0 ? '1' : '0';
            bits <<= 1;
        }
    });
            Method |      Mean |     Error |   StdDev |   Gen0 | Allocated |
------------------ |----------:|----------:|---------:|-------:|----------:|
 ByConvertToString | 264.90 ns |  3.777 ns | 0.207 ns | 0.0558 |     176 B |
    ByStringCreate |  58.61 ns | 38.253 ns | 2.097 ns | 0.0280 |      88 B |


16進数の方と違ってこちらは自前でかなり速くなるので並べたけれど、String.Createに魅入られてわざわざ書こうとしない限り、既に存在し危険も入り込まないConvert.ToStringでよい、という身も蓋もない結論で〆。