字符串 (const char*, size_t) 到 int?

Posted

技术标签:

【中文标题】字符串 (const char*, size_t) 到 int?【英文标题】:String (const char*, size_t) to int? 【发布时间】:2012-03-08 15:43:29 【问题描述】:

将 (const char*, size_t) 表示的字符串转换为 int 的最快方法是什么?

字符串不是以null结尾的。 这两种方式都涉及我想避免的字符串副本(以及更多)。

是的,这个函数每秒被调用几百万次。 :p

int to_int0(const char* c, size_t sz)

    return atoi(std::string(c, sz).c_str());


int to_int1(const char* c, size_t sz)

    return boost::lexical_cast<int>(std::string(c, sz));

【问题讨论】:

如果您使用的是 C++11,则不会有副本,因为将在传递给 atoi 的临时字符串上调用移动构造函数 @TonyTheLion:您仍然需要复制一次数据以创建string 对象(或以零结尾的字符串)。 【参考方案1】:

给定一个这样的计数字符串,您可以通过自己进行转换来获得一点速度。根据代码需要的健壮程度,这可能相当困难。目前,让我们假设最简单的情况——我们确定字符串是有效的,只包含数字(现在没有负数)并且它表示的数字总是在 int 的范围内。对于这种情况:

int to_int2(char const *c, size_t sz)  
    int retval = 0;
    for (size_t i=0; i<sz; i++)
        retval *= 10;
        retval += c[i] -'0';
    
    return retval;

从那里,您可以随心所欲地处理复杂的事情——处理前导/尾随空格,'-'(但正确处理 2 的补码中的最大负数并不总是微不足道的 [编辑:见 Nawaz 的回答对此的一种解决方案]),数字分组等。

【讨论】:

【参考方案2】:

另一个 slow 版本,用于 uint32:

void str2uint_aux(unsigned& number, unsigned& overflowCtrl, const char*& ch)

    unsigned digit = *ch - '0';
    ++ch;

    number = number * 10 + digit;

    unsigned overflow = (digit + (256 - 10)) >> 8;
    // if digit < 10 then overflow == 0
    overflowCtrl += overflow;


unsigned str2uint(const char* s, size_t n)

    unsigned number = 0;
    unsigned overflowCtrl = 0;

    // for VC++10 the Duff's device is faster than loop
    switch (n)
    
    default:
        throw std::invalid_argument(__FUNCTION__ " : `n' too big");

    case 10: str2uint_aux(number, overflowCtrl, s);
    case  9: str2uint_aux(number, overflowCtrl, s);
    case  8: str2uint_aux(number, overflowCtrl, s);
    case  7: str2uint_aux(number, overflowCtrl, s);
    case  6: str2uint_aux(number, overflowCtrl, s);
    case  5: str2uint_aux(number, overflowCtrl, s);
    case  4: str2uint_aux(number, overflowCtrl, s);
    case  3: str2uint_aux(number, overflowCtrl, s);
    case  2: str2uint_aux(number, overflowCtrl, s);
    case  1: str2uint_aux(number, overflowCtrl, s);
    

    // here we can check that all chars were digits
    if (overflowCtrl != 0)
        throw std::invalid_argument(__FUNCTION__ " : `s' is not a number");

    return number;

为什么这么慢?因为它一个接一个地处理字符。如果我们保证可以访问高达s+16 的字节,我们可以对*ch - '0'digit + 246 使用矢量化。 就像在这段代码中一样:

    uint32_t digitsPack = *(uint32_t*)s - '0000';
    overflowCtrl |= digitsPack | (digitsPack + 0x06060606); // if one byte is not in range [0;10), high nibble will be non-zero
    number = number * 10 + (digitsPack >> 24) & 0xFF;
    number = number * 10 + (digitsPack >> 16) & 0xFF;
    number = number * 10 + (digitsPack >> 8) & 0xFF;
    number = number * 10 + digitsPack & 0xFF;
    s += 4;

范围检查的小更新: 第一个 sn-p 在每次迭代时都有冗余移位(或mov),所以它应该是

unsigned digit = *s - '0';
overflowCtrl |= (digit + 256 - 10);
...
if (overflowCtrl >> 8 != 0) throw ...

【讨论】:

即使这也可能有每次迭代分支(如果没有内联,则调用 str2uint_aux)。它还对可以处理的数字大小施加了任意限制(例如,它不适用于 64 位类型)。这可以扩大,但只能以将已经很大的例程增加大约一倍为代价。处理带符号的数字会增加更多。这可能是值得的,但几乎可以很容易地使用足够的额外缓存来降低整体速度。 @JerryCoffin:这段代码的重点是不用s[i] &gt;= '0' &amp;&amp; s[i] &lt;= '9'。完整或部分循环展开、C 宏与内联函数 - 是此代码的次要方面。 我明白你在追求什么,是的,将溢出合并在一起而不是单独处理它们是一个不错的主意——但除非你预计会有相当多的坏数据,否则分支预测在这里会很好用,因此您的额外工作可能收效甚微(如果有的话)。 @Nawaz:对于非数字字符,overflowCtrl 变量将非零。无需使用 comparisons 和分支 - 只需减去 '0',添加 246,如果不是数字,结果将大于 255。所以,你可以收集溢出并立即检查它们。 @Nawaz: digit 是无符号的,所以'*' - '0'0xf...fffffffa,它大于0xff【参考方案3】:

最快:

int to_int(char const *s, size_t count)

     int result = 0;
     size_t i = 0 ;
     if ( s[0] == '+' || s[0] == '-' ) 
          ++i;
     while(i < count)
     
          if ( s[i] >= '0' && s[i] <= '9' )
          
              //see Jerry's comments for explanation why I do this
              int value = (s[0] == '-') ? ('0' - s[i] ) : (s[i]-'0');
              result = result * 10 + value;
          
          else
              throw std::invalid_argument("invalid input string");
          i++;
     
     return result;
 

由于在上面的代码中,每次迭代都会比较(s[0] == '-'),我们可以通过在循环中将result计算为负数来避免这种情况,如果s[0]确实是@987654328,则返回result @,否则返回-result(这使它成为一个正数,应该是):

int to_int(char const *s, size_t count)

     size_t i = 0 ;
     if ( s[0] == '+' || s[0] == '-' ) 
          ++i;
     int result = 0;
     while(i < count)
     
          if ( s[i] >= '0' && s[i] <= '9' )
          
              result = result * 10  - (s[i] - '0');  //assume negative number
          
          else
              throw std::invalid_argument("invalid input string");
          i++;
     
     return s[0] == '-' ? result : -result; //-result is positive!
 

这是一种进步!


在 C++11 中,您可以使用 std::stoi 系列中的任何函数。还有std::to_stringfamily。

【讨论】:

请注意,对于二进制补码系统上的最大负数,这将失败... @Abyx:就它的作用而言,它的速度与你能得到的一样快。我发布的内容可能会快一点,但只是以不那么健壮为代价(即不尝试处理负数或包含除数字以外的任何内容的字符串)。 当然:让我们暂时假设 16 位整数和一个包含 "-32768" 的字符串。这会尝试将 32768 转换为 int,但这会溢出,因为最大的 16 位有符号 int 是 32767 是的,如果字符串中的数字表示的数字太大而无法放入 int 中,溢出基本上是不可避免的。然而,在这种情况下,字符串表示 可以 适合 int 的数字,但该函数仍然不会产生正确的结果(至少不可靠)。 是的,我想就是这样。希望@Abyx(或投票反对的人)现在将其删除。还有一个小问题:为了获得最大速度,可能最好有两个单独的循环,一个用于正数,另一个用于负数,因此您只需执行一次测试而不是每次迭代。【参考方案4】:
llvm::StringRef s(c,sz);
int n;
s.getAsInteger(10,n);
return n;

http://llvm.org/docs/doxygen/html/classllvm_1_1StringRef.html

【讨论】:

因为它需要真正快,最好不要使用它,因为在引用的子函数GetAsUnsignedInteger()中有基数自动检查的调用和分支。【参考方案5】:

如果您不想避免字符串复制,则必须编写自定义例程或使用 3rd 方库。

您可能不想从头开始编写 atoi(仍然可能在此处产生错误),因此我建议从公共领域或 BSD 许可的代码中获取现有的 atoi 并对其进行修改。例如,您可以从FreeBSD cvs tree 获取现有的 atoi。

【讨论】:

【参考方案6】:

如果你经常运行这个函数,我敢打赌你会多次解析同一个数字。我的建议是将字符串 BCD 编码为静态字符缓冲区(你知道它不会很长,因为 atoi 只能处理 +-2G)当少于 X 位时(X=8 用于 32 位查找, X=16 用于 64 位查找)然后将缓存放在哈希映射中。

当您完成第一个版本时,您可能会发现一些不错的优化,例如完全跳过 BCD 编码并仅使用字符串中的 X 字符(当字符串长度 atoi。

编辑:...或替代 atoi 以替代 Jerry Coffin 的解决方案,这与他们来的一样快。

【讨论】:

以上是关于字符串 (const char*, size_t) 到 int?的主要内容,如果未能解决你的问题,请参考以下文章

str系列库函数--模拟实现

外部代码需要 char* 但我使用的是 const char*

strspn&strcspn

将 size_t 转换为向量<unsigned char>

C++数据类型问题,wchar_t和char;size_t

实现一些字符串操作标准库函数与解决一些字符串笔试问题