二进制数到十六进制字符串的有效转换

时间:2017-08-09 18:53:53

标签: c windows assembly 64-bit

我正在编写一个程序,将二进制值的十六进制表示转换为常规字符串。因此十六进制表示中的每个字符都将转换为字符串中的两个十六进制字符。这意味着结果将是两倍大小;一个字节的十六进制表示在字符串中需要两个字节。

十六进制字符

0123456789                    ;0x30 - 0x39
ABCDEF                        ;0x41 - 0x46

实施例

0xF05C1E3A                    ;hex
4032568890                    ;dec

会变成

0x4630354331453341            ;hex
5057600944242766657           ;dec

问题?

是否有任何优雅/替代(/有趣)方法在这些状态之间进行转换,而不是查找表,(按位运算,移位,模数等)? 我不是在寻找库中的函数,而是如何实现/应该如何实现。有什么想法吗?

6 个答案:

答案 0 :(得分:5)

这是一个只有移位和/或加/减的解决方案。没有循环。

uint64_t x, m;
x = 0xF05C1E3A;
x = ((x & 0x00000000ffff0000LL) << 16) | (x & 0x000000000000ffffLL);
x = ((x & 0x0000ff000000ff00LL) << 8)  | (x & 0x000000ff000000ffLL);
x = ((x & 0x00f000f000f000f0LL) << 4)  | (x & 0x000f000f000f000fLL);
x += 0x0606060606060606LL;
m = ((x & 0x1010101010101010LL) >> 4) + 0x7f7f7f7f7f7f7f7fLL;
x += (m & 0x2a2a2a2a2a2a2a2aLL) | (~m & 0x3131313131313131LL);

以上是经过一段时间思考后我想出的简化版本。以下是原始答案。

uint64_t x, m;
x = 0xF05C1E3A;
x = ((x & 0x00000000ffff0000LL) << 16) | (x & 0x000000000000ffffLL);
x = ((x & 0x0000ff000000ff00LL) << 8) | (x & 0x000000ff000000ffLL);
x = ((x & 0x00f000f000f000f0LL) << 4) | (x & 0x000f000f000f000fLL);
x += 0x3636363636363636LL;
m = (x & 0x4040404040404040LL) >> 6;
x += m;
m = m ^ 0x0101010101010101LL;
x -= (m << 2) | (m << 1);

查看实际操作:http://ideone.com/nMhJ2q

答案 1 :(得分:4)

使用pdep

可以轻松地将半字节扩展为字节
spread = _pdep_u64(raw, 0x0F0F0F0F0F0F0F0F);

现在我们必须将0x30添加到0-9范围内的字节和0x41更高字节。这可以通过SWAR从每个字节中减去10然后使用符号来选择要添加的数字来完成,例如(未测试)

H = 0x8080808080808080;
ten = 0x0A0A0A0A0A0A0A0A
cmp = ((spread | H) - (ten &~H)) ^ ((spread ^~ten) & H); // SWAR subtract
masks = ((cmp & H) >> 7) * 255;
// if x-10 is negative, take 0x30, else 0x41
add = (masks & 0x3030303030303030) | (~masks & 0x3737373737373737);
asString = spread + add;

可能会优化SWAR比较,因为您不需要完全减法来实现它。

这里有一些不同的建议,包括SIMD:http://wm.ite.pl/articles/convert-to-hex.html

答案 2 :(得分:2)

LUT(查找表)C ++变体。我没有检查生成的实际机器代码,但我相信任何现代的C ++编译器都能抓住这个想法并编译好。

static const char nibble2hexChar[] { "0123456789ABCDEF" };
     // 17B in total, because I'm lazy to init it per char

void byteToHex(std::ostream & out, const uint8_t value) {
    out << nibble2hexChar[value>>4] << nibble2hexChar[value&0xF];
}

// this one is actually written more toward short+simple source, than performance
void dwordToHex(std::ostream & out, uint32_t value) {
    int i = 8;
    while (i--) {
        out << nibble2hexChar[value>>28];
        value <<= 4;
    }
}
编辑:对于C代码,您只需要从std::ostream切换到其他输出方式,遗憾的是您的问题缺少任何细节,您实际想要实现的目标以及为什么不使用内置printf C函数族。

例如,像这样的C可以写入一些char*输出缓冲区,转换任意数量的字节:

/**
 * Writes hexadecimally formatted "n" bytes array "values" into "outputBuffer".
 * Make sure there's enough space in output buffer allocated, and add zero
 * terminator yourself, if you plan to use it as C-string.
 * 
 * @Returns: pointer after the last character written.
 */
char* dataToHex(char* outputBuffer, const size_t n, const unsigned char* values) {
    for (size_t i = 0; i < n; ++i) {
        *outputBuffer++ = nibble2hexChar[values[i]>>4];
        *outputBuffer++ = nibble2hexChar[values[i]&0xF];
    }
    return outputBuffer;
}

最后,一旦有人进行代码审查,我确实提供了帮助,因为他确实有十六进制格式的性能瓶颈,但我做了代码变量转换,没有LUT,整个过程和其他答案+性能测量可能是指导对你来说,你可能会发现最快的解决方案并不是盲目地转换结果,而是实际上与主要操作混淆,以达到更好的整体性能。所以这就是为什么我想知道你要解决的是什么,因为整个问题可能经常允许更优化的解决方案,如果你只是询问转换,printf("%x",..)是安全的。

以下是“转换为十六进制”的另一种方法: fast C++ XOR Function

答案 3 :(得分:2)

从整数到字符串的任何基数从2到数字的长度

更加得体
char *reverse(char *);

const char digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char *convert(long long number, char *buff, int base)
{
    char *result = (buff == NULL || base > strlen(digits) || base < 2) ? NULL : buff;
    char sign = 0;

    if (number < 0)
    {
         sign = '-';
        number = -number;
    }
    if (result != NULL)
    {
        do
        {
            *buff++ = digits[number % base];
            number /= base;
        } while (number);
        if(sign) *buff++ = sign;
        *buff = 0;
        reverse(result);
    }
    return result;
}


char *reverse(char *str)
{
    char tmp;
    int len;

    if (str != NULL)
    {
        len = strlen(str);
        for (int i = 0; i < len / 2; i++)
        {
            tmp = *(str + i);
            *(str + i) = *(str + len - i - 1);
            *(str + len - i - 1) = tmp;

        }
    }
    return str;
}

示例 - 在基数23中从-50到50的十进制计数

-24     -23     -22     -21     -20     -1M     -1L     -1K     -1J     -1I     -1H     -1G     -1F     -1E     -1D
-1C     -1B     -1A     -19     -18     -17     -16     -15     -14     -13     -12     -11     -10     -M      -L
-K      -J      -I      -H      -G      -F      -E      -D      -C      -B      -A      -9      -8      -7      -6
-5      -4      -3      -2      -1      0       1       2       3       4       5       6       7       8       9
A       B       C       D       E       F       G       H       I       J       K       L       M       10      11
12      13      14      15      16      17      18      19      1A      1B      1C      1D      1E      1F      1G
1H      1I      1J      1K      1L      1M      20      21      22      23      24

答案 4 :(得分:1)

  1. Decimal -> Hex
  2. 只需遍历字符串并将每个字符转换为int,然后就可以执行

    printf("%02x", c);
    

    或使用sprintf保存到另一个变量

    1. Hex -> Decimal
    2. 代码

      printf("%c",16 * hexToInt('F') + hexToInt('0'));
      
      
      int hexToInt(char c)
      {
          if(c >= 'a' && c <= 'z')
              c = c - ('a' - 'A');
      
          int sum;
      
          sum = c / 16 - 3;
          sum *= 10;
          sum += c % 16;
      
          return (sum > 9) ? sum - 1 : sum;
      }
      

答案 5 :(得分:1)

下面的文章比较了将数字转换为字符串的不同方法,不包括十六进制数字,但从dec转换为十六进制似乎不是一个大问题

Integers

Fixed and floating point

@EDIT 感谢您指出上述答案无关紧要。 没有LUT的常见方法是将整数分割成半字节并将它们映射到ASCII

#include <stdio.h>
#include <stdint.h>
#include <string.h>

#define HI_NIBBLE(b) (((b) >> 4) & 0x0F)
#define LO_NIBBLE(b) ((b) & 0x0F)

void int64_to_char(char carr[], int64_t val){
    memcpy(carr, &val, 8);
}

uint64_t inp = 0xF05C1E3A;
char tmp_st[8];

int main()
{
    int64_to_char(tmp_st,inp);
    printf("Sample: %x\n", inp);
    printf("Result: 0x");
    for (unsigned int k = 8; k; k--){
        char tmp_ch = *(tmp_st+k-1);
        char hi_nib = HI_NIBBLE(tmp_ch);
        char lo_nib = LO_NIBBLE(tmp_ch);
        if (hi_nib || lo_nib){
            printf("%c%c",hi_nib+((hi_nib>9)?55:48),lo_nib+((lo_nib>9)?55:48));
        }
     }
     printf("\n");
    return 0;
}

另一种方法是使用艾里森的算法。我是ASM中的总菜鸟,所以我用google搜索它的形式发布了代码。

变式1:

ADD AL,90h
DAA
ADC AL,40h
DAA

变式2:

CMP  AL, 0Ah
SBB  AL, 69h
DAS