如何以优雅有效的方式将无符号/有符号整数/长整数转换为 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 字符串?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 x86(32 位)程序集中将无符号整数转换为浮点数?

将无符号整数存储在指针中

使用啥 ffmpeg 命令将无符号整数列表转换为音频文件?

如何从 Python 将无符号值发送到 dBus

将无符号整数输入属性传递给顶点着色器

有没有办法在核心数据中存储 unsigned long ?