C uint16_t到没有库的填充十六进制字符串

C uint16_t to padded hex string without libraries

提问人:Boyfinn 提问时间:10/19/2023 最后编辑:Boyfinn 更新时间:10/20/2023 访问量:80

问:

我正在尝试将值转换为所述数字的十六进制表示形式,作为 C 中的字符串,而无需使用任何外部库。到目前为止,我已经通过这个答案实现了可靠的转换。 但是,我的用例要求,如果值小于 ,则使用前导零填充生成的输出。例如,如果我像这样调用函数:uint16_tuint16_t0x1000

char s[5];
itohexa(s, 1230); //(1230->0x04CE)

我会得到:

04CE

但是,这个函数给了我这个:

4CE

字符串中表示的uint16_t中的实际字节数对于我的用例很重要。我希望该值偶尔会超过,因此仅向任何数字添加前导零字符(到目前为止我一直在这样做作为解决方法)是不够的。0x0FFFuint16_t

这是我尝试过的:

static char *itohexa_helper(char *dest, uint16_t x, uint8_t pad) {
    if (x >= 16 || pad) {
        dest = itohexa_helper(dest, x/16, pad);
        pad = 1;
    }
    *dest++ = "0123456789ABCDEF"[x & 15];
    return dest;
}

char *itohexa(char *dest, uint16_t x) {
    uint8_t padding = (x < 0x1000);
    *itohexa_helper(dest, x, padding) = '\0';
    return dest;
}

在这种情况下,即使该值的前 4 个最高有效位为零,and 是否仍会进行转换? 仅供记录:对上述代码所做的修改仍然会产生与上面链接的答案中提到的原始结果相同的结果。paddingpad

数组 C 字符串 数据转换

评论

2赞 pmg 10/19/2023
你知道你需要确切的“数字”......所以做一个 !而不是递归。4for (int digit = 0; digit < 4; digit++) { /* your code suitably changed */ }
0赞 Eric Postpischil 10/19/2023
dest[4] = '\0': for (int i = 3; 0 <= i; --i, x >>= 4) dest[i] = "0123456789ABCDEF"[x & 0xF]:
1赞 Eric Postpischil 10/19/2023
回复“但是,这个函数给了我这个:”:我不明白这怎么可能是真的。由于 的调用设置为 1,并且它以相同的值 调用自身,因此会发生无限递归,并且当堆栈耗尽时,程序应该崩溃。您是否发布了与执行相同的代码?4CEx < 0x1000itohexa_helperpadpad
0赞 Fe2O3 10/19/2023
好奇。。。为什么要在代码中的某个点使用,但在其他地方使用?坚持使用十六进制常量的代码会更清晰......0x10001516
0赞 Boyfinn 10/19/2023
很抱歉混淆了@EricPostpischil混淆了 和 的类型。我现在已经纠正了这一点。padpadding

答:

0赞 Lundin 10/19/2023 #1

我可能会尽可能明确地写出来,就像这样(评论中的解释):

#define ZERO_PAD false

char* itohexa (char dest[4+1], uint16_t x)
{
  bool remove_zeroes = true;
  char* ptr = dest;

  for(size_t i=0; i<4; i++)
  {
    // mask out the nibble by shifting 4 times byte number:
    uint8_t nibble = (x >> (3-i)*4) & 0xF; 
    
    // binary to ASCII hex conversion:
    char hex_digit = "0123456789ABCDEF" [nibble];
    
    if(!ZERO_PAD && remove_zeroes && hex_digit == '0')
    {
      ; // do nothing
    }
    else
    {
      remove_zeroes = false;
      *ptr = hex_digit;
      ptr++;
    }
  }

  if(remove_zeroes) // was it all zeroes?
  {
    *ptr = '0';
    ptr++;
  }
  *ptr = '\0';

  return dest;
}

测试用例:

char dest[4+1];
puts(itohexa(dest, 0));
puts(itohexa(dest, 1230));
puts(itohexa(dest, 0));
puts(itohexa(dest, 0xC1));
puts(itohexa(dest, 0xABBA));

输出:ZERO_PAD false

0
4CE
0
C1
ABBA

输出:ZERO_PAD true

0000
04CE
0000
00C1
ABBA

ZERO_PAD可以自然地做成一个参数,或者你可以做两个不同的函数,等等。

评论

0赞 Lundin 10/19/2023
作为奖励,由此产生的机器代码看起来比递归玩具程序更有效率。25 条指令 (x86_64) 和 100% 分支免费,goddang it gcc,干得好!
0赞 Fe2O3 10/19/2023
问题:我的答案(昨天)stackoverflow.com/a/77313793/17592432 包括从...与您在这里的版本相比,感谢您对该循环的评论......谢谢。。。uint16_tdo/while()
1赞 Lundin 10/19/2023
@Fe2O3 嗯,主流编译器为那个编译器制作了一些非常难以阅读的、不容易剖析的......由于有许多比较运算符,do-while 循环会产生多个分支,因此,如果避免分支很重要,则可能应该对其进行重新设计以以某种方式减少分支的数量。
1赞 Fe2O3 10/19/2023 #2

有时,使用(更复杂的)递归来解决问题比使用迭代更有趣。你和你的代码很接近!

与其说是你想要的“填充”,不如说是引导“0”。设置,清除填充标志可能会起作用(如果操作正确),但只会给你一个前导零。

这是您的代码重新设计:

static char *itohexa_helper( char *dest, uint16_t x, int n ) {
    if( --n )
        dest = itohexa_helper( dest, x>>4, n ); // recursion
    *dest++ = "0123456789ABCDEF"[x & 0xF]; // mask and offset
    return dest;
}

char *itohexa(char *dest, uint16_t x) {
    *itohexa_helper( dest, x, 4 ) = '\0'; // 4 digits of output...
    return dest;
}

int main( void ) {
    char buf[16]; // big enough

    puts( itohexa( buf, 0xBEEF ) );
    puts( itohexa( buf, 0xCDE ) );
    puts( itohexa( buf, 0xCD ) );

    return 0;
}

输出:

BEEF
0CDE
00CD

请注意,没有任何 base10 常量...


编辑
如果你 100% 确定(这是 0x0064%)你想要 4 个十六进制数字作为 C 字符串,那么就是这样的:(而且它比我的对手在这场友好的争夺战中的代码短 1 条指令...... :-)

char *itohexa(char *dest, uint16_t x) {
    // NB: index values shown are suitable for little endian processors.
    char *hex = "0123456789ABCDEF";
    union {
        uint16_t ui16;
        uint8_t c[2];
    } combo;
    dest[4] = '\0';
    combo.ui16 = ( x & 0x0F0F );
    dest[3] = hex[ combo.c[0] ];
    dest[1] = hex[ combo.c[1] ];
    combo.ui16 = ( x & 0xF0F0 )>>4;
    dest[2] = hex[ combo.c[0] ];
    dest[0] = hex[ combo.c[1] ];

    return dest;
}

您可能需要使用数组索引,具体取决于 MCU 的大/小端。多么有趣!!

评论

1赞 Fe2O3 10/19/2023
@Lundin OP 可能一直在使用这个任务作为征服递归山的学习练习,以便在面对具有自然递归解决方案的问题(如“排列”)时有更多的直觉......谁说呢?:-)
1赞 Boyfinn 10/19/2023
谢谢!这奏效了。@Lundin的回答也不错。但是由于我正在研究微控制器,因此我有点缺乏令牌。
1赞 chux - Reinstate Monica 10/19/2023
“注意没有任何 base10 常量......” --> 16 in ?char buf[16];
1赞 Fe2O3 10/19/2023
@chux-ReinstateMonica Got me...舒达走了,因为我正在考虑做... :-)8
1赞 chux - Reinstate Monica 10/19/2023
“depending on big-/little-endian-nes” 免责声明应该成为代码注释,因为它很容易被遗漏。
1赞 chux - Reinstate Monica #3

一种简单的非递归方法:从最低有效数字到最高有效数字构建字符串。

#include <stdint.h>

char *itohexa(char *dest, uint16_t x) {
  int place = 4;
  dest[place] = '\0';
  while (place > 0) {
    dest[--place] = "0123456789ABCDEF"[x & 15];
    x >>= 4;
  }
  return dest;
}