【问题标题】:atoi() for int128_t typeatoi() 用于 int128_t 类型
【发布时间】:2018-01-18 09:22:50
【问题描述】:

如何在int128_t 支持下使用argv 值?我知道atoi()<cstdlib> 公开的一系列函数,但不知何故我找不到int128_t 固定宽度整数的函数。这可能是因为 type 不受 c 或 c++ 标准的支持,但我有什么办法可以使这段代码工作吗?

#include <iostream>

int main(int argc, char **argv) {
    __int128_t value = atoint128_t(argv[1]);
}

几乎所有发布的答案对我来说都足够好,但我选择的是我当前代码的直接解决方案,所以也要看看其他答案。

【问题讨论】:

  • intmax_t 的大小是多少?
  • 实现自己的atoint128_t 怎么样?一旦 128 位整数成为标准的一部分,您将把实现切换为调用标准函数的包装器。
  • 我怀疑是否有可用的功能。尝试编写自己的实现。一个快速的谷歌搜索发现了一些东西:github.com/apache/orc/blob/master/c%2B%2B/src/Int128.cc#L355 - 否则你可以看看一些可用的大整数库。虽然我不喜欢推荐图书馆,但您可能想看看。
  • 在 C11 中,如果存在 int128_t 之类的东西,那么 intmax_t 应该覆盖它......(但是,它没有,有 __int128_t......
  • atoi() 在设计上已经很糟糕了,因为它不使用错误处理。你要找的是strtolll,如果存在long long long这样的类型的话。

标签: c++ c int


【解决方案1】:

这是一个 C++ 实现:

#include <string>
#include <stdexcept>

__int128_t atoint128_t(std::string const & in)
{
    __int128_t res = 0;
    size_t i = 0;
    bool sign = false;

    if (in[i] == '-')
    {
        ++i;
        sign = true;
    }

    if (in[i] == '+')
    {
        ++i;
    }

    for (; i < in.size(); ++i)
    {
        const char c = in[i];
        if (not std::isdigit(c)) 
            throw std::runtime_error(std::string("Non-numeric character: ") + c)
        res *= 10;
        res += c - '0';
    }

    if (sign)
    {
        res *= -1;
    }

    return res;
}

int main()
{
  __int128_t a = atoint128_t("170141183460469231731687303715884105727");
}

如果你想测试它,那么有一个流操作符here

性能

我进行了一些性能测试。我生成了 100,000 个随机数,均匀分布在 __int128_t 的整个支持中。然后我将它们中的每一个转换了 2000 次。所有这些(200,000,000)次转换都在约 12 秒内完成。 使用此代码:

#include <iostream>
#include <string>
#include <random>
#include <vector>
#include <chrono>

int main()
{
    std::mt19937 gen(0);
    std::uniform_int_distribution<> num(0, 9);
    std::uniform_int_distribution<> len(1, 38);
    std::uniform_int_distribution<> sign(0, 1);

    std::vector<std::string> str;

    for (int i = 0; i < 100000; ++i)
    {
        std::string s;
        int l = len(gen);
        if (sign(gen))
            s += '-';
        for (int u = 0; u < l; ++u)
            s += std::to_string(num(gen));
        str.emplace_back(s);
    }

    namespace sc = std::chrono;
    auto start =  sc::duration_cast<sc::microseconds>(sc::high_resolution_clock::now().time_since_epoch()).count();
    __int128_t b = 0;
    for (int u = 0; u < 200; ++u)
    {
        for (int i = 0; i < str.size(); ++i)
        {
            __int128_t a = atoint128_t(str[i]);
            b += a;
        }
    }
    auto time =  sc::duration_cast<sc::microseconds>(sc::high_resolution_clock::now().time_since_epoch()).count() - start;
    std::cout << time / 1000000. << 's' << std::endl;
}

【讨论】:

  • @Jonas Paul R 实际上意味着您在这里遗漏了所有负数。例如-12 仍然是有效的__int128_t
  • @Jonas 如果您创建一个包含 C 字符串的 second 向量,则很容易将其分解。也就是说,如果目标 CPU 支持至少 64 位数字相乘,那么实际性能可能会非常好,而我的方法的缺点是我不能在 C 中使用“硬件进位”,因此浪费时间检查单个位。因此,对于“仅”128 位,您的“幼稚”方法很有可能更快。但是,数字越大 - 你知道;)
  • @Jonas 我会将其添加到我的答案中,因此它看起来不像试图“抹黑”这种幼稚的方法。
  • 你应该检查整数溢出。
  • 看到这段代码比经过精心打磨的 C 版本获得更多的选票,我很难过。我想java或javascript版本会更有价值。
【解决方案2】:

在这里添加一个“不那么天真的”纯 C 实现,它仍然很简单:

#include <stdio.h>
#include <inttypes.h>

__int128 atoi128(const char *s)
{
    while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '+') ++s;
    int sign = 1;
    if (*s == '-')
    {
        ++s;
        sign = -1;
    }
    size_t digits = 0;
    while (s[digits] >= '0' && s[digits] <= '9') ++digits;
    char scratch[digits];
    for (size_t i = 0; i < digits; ++i) scratch[i] = s[i] - '0';
    size_t scanstart = 0;

    __int128 result = 0;
    __int128 mask = 1;
    while (scanstart < digits)
    {
        if (scratch[digits-1] & 1) result |= mask;
        mask <<= 1;
        for (size_t i = digits-1; i > scanstart; --i)
        {
            scratch[i] >>= 1;
            if (scratch[i-1] & 1) scratch[i] |= 8;
        }
        scratch[scanstart] >>= 1;
        while (scanstart < digits && !scratch[scanstart]) ++scanstart;
        for (size_t i = scanstart; i < digits; ++i)
        {
            if (scratch[i] > 7) scratch[i] -= 3;
        }
    }

    return result * sign;
}


int main(int argc, char **argv)
{
    if (argc > 1)
    {
        __int128 x = atoi128(argv[1]);
        printf("%" PRIi64 "\n", (int64_t)x); // just for demo with smaller numbers
    }
}

它使用移位的 BCD 暂存空间逐位读取数字,请参阅Double dabble 了解算法(此处颠倒过来)。这比进行多次乘以 10一般要高效得多。 *)

这依赖于VLA,没有它们,你可以替换

char scratch[digits];

char *scratch = malloc(digits);
if (!scratch) return 0;

并添加一个

free(scratch);

在函数的末尾。

当然,上面的代码与原始atoi() 有相同的限制(例如,它会在溢出时产生“随机”垃圾并且无法检查).. 如果你需要strtol() 风格的保证和错误检查,自己扩展它(不是什么大问题,只是工作去做)。


*) 当然,在 C 中实现双重涉猎总是会遇到不能使用“硬件进位”的事实,因此需要额外的位掩码和测试操作。另一方面,“天真地”乘以 10 会非常有效,只要平台提供的乘法指令的宽度“接近”您的目标类型。因此,在典型的x86_64 平台上(具有乘以 64 位整数的指令),此代码可能比简单的十进制方法慢很多。但它可以更好地扩展到真正的 huge 整数(例如,使用 uintmax_t 的数组来实现)。

【讨论】:

  • 如果 VLA 不可用,alloca() 也是一个选项。如果char scratch[digits]; 可以接受,char *scratch = alloca( digits ); 也可以接受。
  • @AndrewHenle alloca() 不在任何标准的 AFAIK 中。如果可用,VLA可能也可用,只要它不是仅 C89 的编译器。
  • 相同的角落问题min_value UB+ sign
  • @chux + 被考虑在内。最小值不是,如果它的绝对值大于最大值。顺便说一句,这不是微不足道的。
  • @FelixPalmen 我现在看到'+' 位于while() 的末尾,但此代码现在通过"+++123",与aoti() 不同。
【解决方案3】:

这是一个简单的实现方法:

__int128_t atoint128_t(const char *s)
{
    const char *p = s;
    __int128_t val = 0;

    if (*p == '-' || *p == '+') {
        p++;
    }
    while (*p >= '0' && *p <= '9') {
        val = (10 * val) + (*p - '0');
        p++;
    }
    if (*s == '-') val = val * -1;
    return val;
}

此代码检查每个字符是否为数字(带有可选的前导 + 或 -),如果是,则将当前结果乘以 10 并添加与该数字关联的值。然后,如果需要,它会反转符号。

注意这个实现不检查溢出,这与atoi的行为一致。

编辑:

通过根据符号添加或减去每个数字的值并跳过前导空格来涵盖 int128_MIN 情况的修订实现。

int myatoi(const char *s)
{
    const char *p = s;
    int neg = 0, val = 0;

    while ((*p == '\n') || (*p == '\t') || (*p == ' ') ||
           (*p == '\f') || (*p == '\r') || (*p == '\v')) {
        p++;
    }
    if ((*p == '-') || (*p == '+')) {
        if (*p == '-') {
            neg = 1;
        }
        p++;
    }
    while (*p >= '0' && *p <= '9') {
        if (neg) {
            val = (10 * val) - (*p - '0');
        } else {
            val = (10 * val) + (*p - '0');
        }
        p++;
    }
    return val;
}

【讨论】:

  • 带有int128_MIN 的角盒UB。通常的 UB 是可以接受的,但仍然是 UB。
  • @chux 很好。修改为处理int128_MIN
  • @chqrlie 谢谢。也添加了。
  • @dbush: isspace((unsigned char)*p) 如果进行 6 次测试,将比一包更有效。
【解决方案4】:

我有什么办法可以让这段代码工作吗?

“如何实现你自己的 atoint128_t ?” @Marian


推出自己的atoint128_t() 并不难。

需要考虑的要点。

  1. 可表示的负值比正值多 0 或 1 个。使用负数累加值提供了更大的范围。

  2. 没有为 atoi() 定义溢出。也许提供一个上限值并设置errno?检测潜在的 OF 可预防 UB。

  3. __int128_t 常量需要仔细的代码才能正确形成。

  4. 如何处理异常输入? atoi() 在几年前就速度/大小而言相当宽松且有意义,但如今通常需要更少的 UB。候选案例:""" ""-""z""+123""999..many...999""the min int128""locale_specific_space" + " 123" 甚至非字符串 NULL

  5. 执行atoi()atoint128_t() 的代码只需在类型、范围和名称上有所不同。算法是一样的。

    #if 1
      #define int_t __int128_t
      #define int_MAX (((__int128_t)0x7FFFFFFFFFFFFFFF << 64) + 0xFFFFFFFFFFFFFFFF)
      #define int_MIN (-1 - int_MAX)
      #define int_atoi atoint128_t
    #else
      #define int_t int
      #define int_MAX INT_MAX
      #define int_MIN INT_MIN
      #define int_atoi int_atoi
    #endif
    

示例代码:按需定制。依赖于 C99 或更高版本的 negative/positive% 功能。

int_t int_atoi(const char *s) {
  if (s == NULL) {  // could omit this test
    errno = EINVAL;
    return 0;
  }
  while (isspace((unsigned char ) *s)) {  // skip same leading white space like atoi()
    s++;
  }
  char sign = *s;  // remember if the sign was `-` for later
  if (sign == '-' || sign == '+') {
    s++;
  }

  int_t sum = 0;
  while (isdigit((unsigned char)*s)) {
    int digit = *s - '0';
    if ((sum > int_MIN/10) || (sum == int_MIN/10 && digit <= -(int_MIN%10))) {
      sum = sum * 10 - digit;  // accumulate on the - side
    } else {
      sum = int_MIN;
      errno = ERANGE;
      break; // overflow
    }
    s++;
  }

  if (sign != '-') {
    if (sum < -int_MAX) {
      sum = int_MAX;
      errno = ERANGE;
    } else {
      sum = -sum;  // Make positive
    }
  }

  return sum;
}

正如@Lundin 评论缺少溢出检测等。在strtol() 之后建模字符串-->int128 是一个更好的主意。

为简单起见,请考虑__128_t strto__128_base10(const char *s, char *endptr);

这个答案所有准备好处理溢出和标志errno,如strtol()。只需要一些更改:

  bool digit_found = false;
  while (isdigit((unsigned char)*s)) { 
    digit_found = true;  

      // delete the `break` 
      // On overflow, continue looping to get to the end of the digits.
      // break;


  // after the `while()` loop:
  if (!digit_found) {  // optional test
    errno = EINVAL;
  }
  if (endptr) {
    *endptr = digit_found ? s : original_s;
  }

base016 时,完整的类似long int strtol(const char *nptr, char **endptr, int base); 的功能也可以处理具有特殊代码的其他基。 @chqrlie

【讨论】:

    【解决方案5】:

    C 标准不强制要求支持 128 位整数。

    然而,现代编译器通常支持它们:gccclang 都支持 __int128_t__uint128_t 类型,但令人惊讶的是仍然将 intmax_tuintmax_t 限制为 64 位。

    除了基本的算术运算符之外,对这些大整数的支持并不多,尤其是在 C 库中:没有scanf()printf() 转换说明符等。

    这是strtoi128()strtou128()atoi128() 的实现,它与C 标准的atoi()strtol()strtoul() 规范一致。

    #include <ctype.h>
    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    /* Change these typedefs for your local flavor of 128-bit integer types */
    typedef __int128_t i128;
    typedef __uint128_t u128;
    
    static int strdigit__(char c) {
        /* This is ASCII / UTF-8 specific, would not work for EBCDIC */
        return (c >= '0' && c <= '9') ? c - '0'
            :  (c >= 'a' && c <= 'z') ? c - 'a' + 10
            :  (c >= 'A' && c <= 'Z') ? c - 'A' + 10
            :  255;
    }
    
    static u128 strtou128__(const char *p, char **endp, int base) {
        u128 v = 0;
        int digit;
    
        if (base == 0) {    /* handle octal and hexadecimal syntax */
            base = 10;
            if (*p == '0') {
                base = 8;
                if ((p[1] == 'x' || p[1] == 'X') && strdigit__(p[2]) < 16) {
                    p += 2;
                    base = 16;
                }
            }
        }
        if (base < 2 || base > 36) {
            errno = EINVAL;
        } else
        if ((digit = strdigit__(*p)) < base) {
            v = digit;
            /* convert to unsigned 128 bit with overflow control */
            while ((digit = strdigit__(*++p)) < base) {
                u128 v0 = v;
                v = v * base + digit;
                if (v < v0) {
                    v = ~(u128)0;
                    errno = ERANGE;
                }
            }
            if (endp) {
                *endp = (char *)p;
            }
        }
        return v;
    }
    
    u128 strtou128(const char *p, char **endp, int base) {
        if (endp) {
            *endp = (char *)p;
        }
        while (isspace((unsigned char)*p)) {
            p++;
        }
        if (*p == '-') {
            p++;
            return -strtou128__(p, endp, base);
        } else {
            if (*p == '+')
                p++;
            return strtou128__(p, endp, base);
        }
    }
    
    i128 strtoi128(const char *p, char **endp, int base) {
        u128 v;
    
        if (endp) {
            *endp = (char *)p;
        }
        while (isspace((unsigned char)*p)) {
            p++;
        }
        if (*p == '-') {
            p++;
            v = strtou128__(p, endp, base);
            if (v >= (u128)1 << 127) {
                if (v > (u128)1 << 127)
                    errno = ERANGE;
                return -(i128)(((u128)1 << 127) - 1) - 1;
            }
            return -(i128)v;
        } else {
            if (*p == '+')
                p++;
            v = strtou128__(p, endp, base);
            if (v >= (u128)1 << 127) {
                errno = ERANGE;
                return (i128)(((u128)1 << 127) - 1);
            }
            return (i128)v;
        }
    }
    
    i128 atoi128(const char *p) {
        return strtoi128(p, (char**)NULL, 10);
    }
    
    char *utoa128(char *dest, u128 v, int base) {
        char buf[129];
        char *p = buf + 128;
        const char *digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
        *p = '\0';
        if (base >= 2 && base <= 36) {
            while (v > (unsigned)base - 1) {
                *--p = digits[v % base];
                v /= base;
            }
            *--p = digits[v];
        }
        return strcpy(dest, p);
    }
    
    char *itoa128(char *buf, i128 v, int base) {
        char *p = buf;
        u128 uv = (u128)v;
        if (v < 0) {
            *p++ = '-';
            uv = -uv;
        }
        if (base == 10)
            utoa128(p, uv, 10);
        else
        if (base == 16)
            utoa128(p, uv, 16);
        else
            utoa128(p, uv, base);
        return buf;
    }
    
    static char *perrno(char *buf, int err) {
        switch (err) {
        case EINVAL:
            return strcpy(buf, "EINVAL");
        case ERANGE:
            return strcpy(buf, "ERANGE");
        default:
            sprintf(buf, "%d", err);
            return buf;
        }
    }
    
    int main(int argc, char *argv[]) {
        char buf[130];
        char xbuf[130];
        char ebuf[20];
        char *p1, *p2;
        i128 v, v1;
        u128 v2;
        int i;
    
        for (i = 1; i < argc; i++) {
            printf("%s:\n", argv[i]);
            errno = 0;
            v = atoi128(argv[i]);
            perrno(ebuf, errno);
            printf("  atoi128():   %s  0x%s  errno=%s\n",
                   itoa128(buf, v, 10), utoa128(xbuf, v, 16), ebuf);
            errno = 0;
            v1 = strtoi128(argv[i], &p1, 0);
            perrno(ebuf, errno);
            printf("  strtoi128(): %s  0x%s  endptr:\"%s\"  errno=%s\n",
                   itoa128(buf, v1, 10), utoa128(xbuf, v1, 16), p1, ebuf);
            errno = 0;
            v2 = strtou128(argv[i], &p2, 0);
            perrno(ebuf, errno);
            printf("  strtou128(): %s  0x%s  endptr:\"%s\"  errno=%s\n",
                   utoa128(buf, v2, 10), utoa128(xbuf, v2, 16), p2, ebuf);
        }
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-06-20
      • 2012-10-26
      • 1970-01-01
      • 2014-06-02
      • 1970-01-01
      • 1970-01-01
      • 2020-10-05
      • 2015-01-31
      相关资源
      最近更新 更多