在 C/C++ 中高效地在十六进制、二进制和十进制之间进行转换

Posted

技术标签:

【中文标题】在 C/C++ 中高效地在十六进制、二进制和十进制之间进行转换【英文标题】:Efficiently convert between Hex, Binary, and Decimal in C/C++ 【发布时间】:2010-10-23 14:23:12 【问题描述】:

我有 3 种正整数的基本表示:

    十进制,在 unsigned long 变量中(例如 unsigned long int NumDec = 200)。 十六进制,在字符串变量中(例如 string NumHex = "C8") 二进制,在字符串变量中(例如string NumBin = "11001000"

我希望能够以最有效的方式在所有 3 种表示形式中的数字之间进行转换。 IE。实现以下6个功能:

unsigned long int Binary2Dec(const string & Bin) 
unsigned long int Hex2Dec(const string & Hex) 
string Dec2Hex(unsigned long int Dec) 
string Binary2Hex(const string & Bin) 
string Dec2Binary(unsigned long int Dec) 
string Hex2Binary(const string & Hex) 

对他们每个人来说最有效的方法是什么?我可以使用 C 和 C++,但不能使用 boost。

编辑:“效率”是指时间效率:最短的执行时间。

【问题讨论】:

您的前两个函数名称极具误导性。您没有返回十进制表示。您将返回一个无符号长整数,具有未定义、不透明(除非您执行实现定义的操作)的内部表示。 您建议的函数名称是什么? Binary2Int 和 Hex2Int 更有意义。当然这些函数在 c 库中使用 strtol 是不需要的。 Binary2ULong 和 HexToULong 会好很多。 @IgorOks:令人困惑的原因是数字数据类型没有基数,因此不是“基数 10”。 【参考方案1】:

正如其他人所指出的,我将从sscanf()printf() 和/或strtoul() 开始。它们对于大多数应用程序来说足够快,而且它们不太可能出现错误。然而,我要说的是,这些函数比你想象的更通用,因为它们必须处理非 ASCII 字符集,以及以任何基数表示的数字等等。对于某些领域,有可能击败库函数。

所以,先测量,如果这些转换的性能确实是个问题,那么:

1) 在某些应用程序/领域中,某些数字经常出现,例如零、100、200、19.95,可能非常常见,因此优化您的函数以使用一堆 if() 语句转换这些数字是有意义的,然后回退到通用库函数。 2)如果最常见的 100 个数字使用表查找,然后回退到库函数。请记住,大型表可能不适合您的缓存,并且可能需要对共享库进行多次间接访问,因此请仔细测量这些内容以确保不会降低性能。

您可能还想查看 boost lexical_cast 函数,尽管根据我的经验,后者与良好的旧 C 函数相比。

尽管很多人都说过,但值得一遍又一遍地重复:在您有证据证明它们存在问题之前,不要优化这些转化。如果您确实进行了优化,请测量您的新实现以确保它更快并且确保您为自己的版本进行大量单元测试,因为您会引入错误:-(

【讨论】:

【参考方案2】:

让我们暂时考虑一下任务的一半 - 从字符串化的基数 n 转换为无符号长整数,其中 n 是 2 的幂(二进制的基数为 2,十六进制的基数为 16)。

如果您的输入是合理的,那么这项工作只不过是一个比较、一个减法、一个移位和一个或每个数字。如果你的输入不健全,那么,这就是它变得丑陋的地方,不是吗?进行超快转换并不难。在任何情况下都做好是一项挑战。

所以让我们假设您的输入是理智的,那么您转换的核心是:

unsigned long PowerOfTwoFromString(char *input, int shift)

    unsigned long val = 0;
    char upperLimit = 'a' + (1 << shift)
    while (*input) 
        char c = tolower(*input++);
        unsigned long digit = (c > 'a' && c < upperLimit) ? c - 'a' + 10 : c - '0';
        val = (val << shift) | digit;
    
    return val;
 

 #define UlongFromBinaryString(str) PowerOfTwoFromString(str, 1)
 #define UlongFromHexString(str) PowerOfTwoFromString(str, 4)

看看这有多容易?它会在非理智的输入上失败。你的大部分工作都是为了让你的输入变得理智,而不是表现。

现在,此代码利用了两次移位的优势。它很容易扩展到base 4,base 8,base 32等。它不适用于两个base的非幂。对于那些,你的数学必须改变。你得到

val = (val * base) + digit

这组操作在概念上是相同的。乘以基数将等同于移位。所以我很可能会使用一个完全通用的例程。并在清理输入的同时清理代码。到那时,strtoul 可能是你最好的选择。这是 strtoul 的a version 的链接。几乎所有的工作都在处理边缘条件——这应该让你知道你的精力应该集中在哪里:正确、有弹性的代码。与说不因输入错误而崩溃的情况相比,使用位移所节省的费用将是微乎其微的。

【讨论】:

【参考方案3】:

我建议只使用sprintf 和sscanf。

另外,如果您对它的实现方式感兴趣,可以查看source code 的glibc, the GNU C Library。

【讨论】:

两个答案: 1. 测试所有解决方案,看看哪个更快。 2. 请记住,C 标准库中的代码通常是经过专业编写和高度优化的 - 此类问题是标准库存在的全部原因,因此程序员可以获得针对极其常见问题的专业编写的解决方案,而不必不断地去自己重新发明***。 还请记住,sprintf 和 sscanf 已经过广泛测试,不会出现您在尝试自己进行转换时可能引入的小错误。 不幸的是,%b 不是二进制的标准 printf 转换说明符。不过,我仍然会考虑使用它。【参考方案4】:

为什么这些例程必须如此省时?这种说法总是让我感到好奇。您确定像 strtol() 这样的明显转换方法太慢,还是您可以做得更好?系统功能通常非常有效。它们有时支持通用性和错误检查的速度较慢,但​​您需要考虑如何处理错误。如果bin 参数包含除“0”和“1”以外的字符,那会怎样?中止?传播大量错误?

为什么用“Dec”来表示内部表示?应该使用 Dec、Hex 和 Bin 来指代字符串表示。 unsigned long 没有小数点。您是否正在处理以十进制显示数字的字符串?如果不是这样,你会混淆这里的人,并且会混淆更多人。

二进制和十六进制文本格式之间的转换可以通过查找表快速有效地完成,但涉及十进制文本格式的任何事情都会更加复杂。

【讨论】:

【参考方案5】:

为什么不只使用宏也将格式作为输入。如果你至少在 C 中。

#define TO_STRING( string, format, data) \
sprintf( string, "##format##", data)
// Int
TO_STRING(buf,%d,i);
// Hex ( Two char representation )
TO_STRING(buf,%02x,i);
// Binary
TO_STRING(buf,%b,i);

或者你可以直接使用 sprintf:或者你可以有多个宏。

#define INT_STRING( buf, data) \
sprintf( buf, "%d", data)
#define HEX_STRING( buf, data) \
sprintf( buf, "%x", data)
#define BIN_TO_STRING( buf, data) \
sprintf( buf, "%b", data)

BIN_TO_STRING( loc_buf, my_bin );

【讨论】:

【参考方案6】:

听起来很像家庭作业问题,但到底是什么......

简短的回答是使用两个查找表从 long int 转换为您的字符串。每个表应该有 256 个条目。一个将一个字节映射到一个十六进制字符串:0 ->“00”、1 ->“01”等。另一个将一个字节映射到一个位字符串:0 ->“00000000”、1 ->“00000001”。

然后,对于 long int 中的每个字节,您只需查找正确的字符串,并将它们连接起来。

要将字符串转换回长字符串,您只需将每个字符的数值乘以 16 或 2 的适当幂,然后将结果相加,即可将十六进制字符串和位字符串转换回十进制数。

编辑:您还可以使用相同的查找表进行反向转换,方法是进行二进制搜索以找到正确的字符串。这需要对您的字符串进行 log(256) = 8 次比较。不幸的是,我没有时间分析比较字符串是否比乘法和加法快得多。

【讨论】:

关于字符串到长转换:它会比 srotul() 工作得更快吗?【参考方案7】:

这取决于您的优化目标,您所说的“高效”是什么意思?重要的是转换速度快、占用内存少、程序员时间少、其他程序员阅读代码的WTFs更少,还是什么?

为了可读性和易于实现,您至少应该通过调用@987654322@ 来实现Dec2Hex()Dec2Binary()。这使得它们成为单行,这对于至少对上述单词的某些解释来说非常有效。

【讨论】:

“效率”是指时间效率:最短的执行时间。感谢您澄清这一点。

以上是关于在 C/C++ 中高效地在十六进制、二进制和十进制之间进行转换的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C/C++ 中获得大数的精确二进制表示?

C语言 十进制和二进制相互转换

第四十课前置操作符和后置操作符

详解2进制,10进制,16进制,8进制,36进制

进制与位运算

如何在 C/C++ 中随机翻转 char 的二进制位