atoi() 表示int128_t类型

atoi() for int128_t type

本文关键字:类型 int128 表示 atoi      更新时间:2023-10-16

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

#include <iostream>
int main(int argc, char **argv) {
__int128_t value = atoint128_t(argv[1]);
}

几乎所有发布的答案对我来说都足够好,但我正在为我当前的代码选择一个单独的解决方案,所以也请查看其他答案。

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

__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;
}

下面是一个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");
}

如果你想测试它,那么这里有一个流运算符。

性能

我进行了一些性能测试。我生成 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;
}

在这里添加一个纯 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 暂存空间逐位读取数字,请参阅算法的双重涉猎(此处颠倒)。这比通常进行多次乘以 10 要有效得多。*)

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

char scratch[digits];

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

并添加一个

free(scratch);

在函数的末尾。

当然,上面的代码与原始atoi()具有相同的限制(例如,它会在溢出时产生"随机"垃圾,并且无法检查)..如果您需要strtol()式保证和错误检查,请自己扩展它(问题不大,只需努力即可)。


*) 当然,在 C 语言中实现双重 dabble 总是会遇到你不能使用"硬件携带"的事实,所以需要额外的位屏蔽和测试操作。另一方面,"天真地"乘以 10 可能非常有效,只要平台提供宽度"接近"您的目标类型的乘法指令。因此,在典型的x86_64平台上(具有乘以 64 位整数的说明),此代码可能比朴素十进制方法慢得多。但它可以更好地扩展到非常大的整数(例如,您可以使用uintmax_t数组来实现)。

有什么

方法可以让这段代码工作吗?

"实施你自己的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);

这个答案都准备好处理溢出并像strtol()一样标记errno。 只需要一些更改:

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;
}

一个完整的long int strtol(const char *nptr, char **endptr, int base);功能也会在016base使用特殊代码处理其他基础。 @chqrlie

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 = '';
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=%sn",
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=%sn",
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=%sn",
utoa128(buf, v2, 10), utoa128(xbuf, v2, 16), p2, ebuf);
}
return 0;
}