如何以优雅有效的方式将无符号/有符号整数/长整数转换为 C 字符串?
Posted
技术标签:
【中文标题】如何以优雅有效的方式将无符号/有符号整数/长整数转换为 C 字符串?【英文标题】:How to convert unsigned/signed integer/long to C string in an elegant and efficient way? 【发布时间】:2017-04-26 04:04:58 【问题描述】:这里有很多人问如何将无符号/有符号整数/长整数转换为 C 字符串。
最常见的答案是使用 sprintf(或 snprintf)。但是,它需要针对不同类型的不同格式字符串(例如 uint32_t、int32_t、uint64_t、int64_t 等)。
我有一个这样的函数模板:
// T can only be uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t
template <type T>
void foo(char* buffer, T value)
// write value to buffer
我可以专门化函数模板来解决我的问题。
我只是想知道是否有更优雅和有效的方式(即没有像 stringstream 这样的临时缓冲区)。
谢谢!
【问题讨论】:
两者都不是。刚好我在工作中遇到它并且好奇是否有一个出色的解决方案:) 无疑有绝妙的解决方案。您是否确定您看到的现有解决方案不够快? STL 中可用的解决方案无疑是经过深思熟虑的,并且适用于所有条件。正如其中一个答案所指出的,即使您提供的函数签名也是脆弱的 - 不能保证您提供的函数的缓冲区足够大 这种操作的成本很复杂。你希望最短的时间吗?最小内存?最少的代码?您是否有一些预期的整数分布,例如大多数都在 65535 以下,你有足够的可用内存吗? @Hei 看看我的手优化to_str,我很好奇这会比你的第二好的版本快多少。 【参考方案1】:也许 C++17 之前最简单的实现是:
std::strcpy(buffer, std::to_string(value).c_str());
这确实需要一个临时缓冲区(一个临时的std::string
),但我会犹豫过早地优化。在我看来,这将是进行转换的最优雅的方式——它简单易懂。
(请注意,您的函数签名无法确保buffer
指针指向的分配足够大以容纳字符串化的值。)
在 C++17 中,您可以只使用 std::to_chars()
(您必须自己使用此函数添加空终止字符;文档没有说明它会为您添加一个)。
也许有一个中间立场,您可以声明一个 trait 以获得每个数字类型的 printf 样式格式说明符?
#include <cstdio>
template <typename T>
struct printf_specifier
static char const * const value;
;
template<> char const * const printf_specifier<char>::value = "%hhd";
template<> char const * const printf_specifier<unsigned char>::value = "%hhu";
template<> char const * const printf_specifier<short>::value = "%hd";
template<> char const * const printf_specifier<unsigned short>::value = "%hu";
template<> char const * const printf_specifier<int>::value = "%d";
template<> char const * const printf_specifier<unsigned int>::value = "%u";
template<> char const * const printf_specifier<long>::value = "%ld";
template<> char const * const printf_specifier<unsigned long>::value = "%lu";
template<> char const * const printf_specifier<long long>::value = "%lld";
template<> char const * const printf_specifier<unsigned long long>::value = "%llu";
template<> char const * const printf_specifier<float>::value = "%f";
template<> char const * const printf_specifier<double>::value = "%f";
template <typename T>
void foo(char *buffer, T value)
std::sprintf(buffer, printf_specifier<T>::value, value);
不过,我建议使用snprintf
,因为如果你给它允许写入的字符数,它不会超出你的缓冲区:
template <typename T>
int foo(char *buffer, std::size_t size, T value)
return std::snprintf(buffer, size, printf_specifier<T>::value, value);
如果这太臃肿,您可以完全自己进行转换:
#include <algorithm>
#include <cstdlib>
template <typename T>
void foo(char *buffer, T value)
static_assert(std::is_integral<T>::value, "Type of value must be an integral type");
if (value < 0)
*(buffer++) = '-';
char *start = buffer;
while (value != 0)
*(buffer++) = '0' + std::abs(value % 10);
value /= 10;
if (buffer == start)
*(buffer++) = '0';
else
std::reverse(start, buffer);
*buffer = '\0';
使用 log10 来计算字符串的长度并将其从后向前写入可能会更快,而不是向后写入然后反转它,但如果你将这个选项留给你练习认为有必要。
【讨论】:
感谢您的快速回复。同意避免所有过早的优化。但它在我的应用程序的关键路径上(需要非常快...... 不幸的是,我仅限于 GCC 4.8.x,它甚至没有实现完整的 C++11(尽管我在这里放了一个 C++11 标签)。 @Hei 我添加了一种可能对您有用的方法。 @cdhowie 这就是 MS std::to_string 的实现方式(AFAIK) @Hei 如果您担心自己的关键路径,我建议您避免使用 snprintf/printf。它将大量代码加载到指令缓存中,这会影响延迟。如果您的应用程序对损失 100 纳秒很敏感,您可能需要自己测量和比较滚动。特别是如果您只需要转换整数。【参考方案2】:只需使用sprintf 或itoa (non portable):
char* append_num(char* buf, int n)
return buf + sprintf(buf, "%d", n);
some std::to_string implementations actually use sprintf 并将结果复制到新的 std::string。
这里有一些可以被认为是优化得很好的东西。与常规 itoa 的不同之处在于,它执行的整数除法减少了两倍,这在大多数 CPU 上并不是微不足道的指令。
static int log10_1(unsigned int num)
int ret;
static_assert(sizeof(num) == 4, "expected 32-bit unsigned int");
// extend this logic for 64 bit numbers
if (num >= 10000)
if (num >= 1000000)
if (num >= 100000000)
ret = (num >= 1000000000) ? 10 : 9;
else
ret = (num >= 10000000) ? 8 : 7;
else
ret = (num >= 100000) ? 6 : 5;
else
if (num >= 100)
ret = num >= 1000 ? 4 : 3;
else
ret = num >= 10 ? 2 : 1;
return ret;
// write string representation of number `n` into buf and return pointer to rterminating null
char* to_str(char* buf, unsigned int n)
static const char dig_[] = "0001020304050607080910111213141516171819"
"20212223242526272829303132333435363738394041424344454647484950515253545556575859"
"60616263646566676869707172737475767778798081828384858687888990919293949596979899";
int len = log10_1(n);
char *p = buf + len;
*p-- = 0;
while (n >= 100)
unsigned int x = (n % 100) * 2;
n /= 100;
*p-- = dig_[x + 1];
*p-- = dig_[x];
if (n >= 10)
unsigned int x = n * 2;
*p-- = dig_[x + 1];
*p-- = dig_[x];
else
*p-- = (char)('0' + n);
return buf + len;
// write string representation of number `n` into buf and return pointer to terminating null
char* to_str(char* buf, int n)
unsigned int l;
if (n < 0)
*buf++ = '-';
if (n == INT_MIN)
static_assert(sizeof(n) == 4, "expected 32-bit int");
memcpy(buf, "2147483648", 10);
return buf + 10;
l = (unsigned int)(-n);
else
l = (unsigned int)n;
return to_str(buf, l);
to_str is more than twice as fast compared to cdhowie's foo 大约是 sprintf 的 6 倍。比较时间:
foo time: 745 ms
to_str time: 327 ms
sprintf time: 1989 ms
已经有一个很好的用于优化 to_string 函数的 *** 页面:C++ performance challenge: integer to std::string conversion。最快的算法和我的基本一样。
【讨论】:
如果您在创建字符串之前知道字符串的长度,例如使用***.com/questions/9721042/… 中的内容,您可以避免使用memcpy
。同样,可以再扩展一步以查看 3 位、4 位等组 - 可能会稍微提高速度,但会增加内存消耗,尽管只有一次。
@Arunas 是的,memcpy 步骤可以很容易地改进为使用多个字节等进行复制。即使使用 memcpy,这个函数仍然表现得很好。
你成功了!现在,当然有两个部门正在发生,而且很糟糕,我想知道linux.die.net/man/3/div 是否提供了任何可以减少周期数的东西
尽管根据en.cppreference.com/w/cpp/numeric/math/div,'在许多平台上,单个 CPU 指令同时获得商和余数,尽管编译器通常能够合并附近的 / 和 %在合适的地方。 ',所以可能没有太大优势。
@Arunas Compiler 肯定会将它们合并为一个。由于除法通常是重量级的,优化器会知道如何处理 / 和 %,如果 div 被实现为返回值的外部,那么它可能会损害性能。以上是关于如何以优雅有效的方式将无符号/有符号整数/长整数转换为 C 字符串?的主要内容,如果未能解决你的问题,请参考以下文章