我正在通过stdin读取大量数字,如果数字在[0,2 ^ 32]范围内(可能是2 ^ 32-1,我不确定),我需要找到它,因此它也不接受负数。在某些情况下,有一个以数百个空值开头的数字,我需要忽略它们。 我确定我在使用“ long”作为数据类型时是否对数据类型做错了,因为我认为这总是最大2 ^ 32。所以如果有溢出,我得到一个负数,我可以证明long是否小于0。 但是现在我意识到long的大小也取决于计算机的系统。
有没有人可以告诉我应该选择哪种数据类型和操作来证明这一点? 起始数据类型只是一个char指针。
答案 0 :(得分:5)
比一见钟情有些棘手。原因是当您允许任意长的输入序列时,甚至可能超过可用的最大数据类型,例如,甚至64位也可能会太少。
取决于使用哪种方法读取数字,是否检测到数据类型溢出。例如,如果处理后的整数值不适合无符号长整数,则scanf("%lu",...)
可能导致不确定的行为(例如,与scanf有关的在线c11草稿):
10 ...如果此对象没有适当的类型,或者 转换结果无法在对象中表示, 行为是不确定的。
因此,请勿将scanf
用于任意输入。
函数strtoul
具有定义的溢出行为(同样来自在线c11草案,涉及strtol):
8)strtol,strtoll,strtoul和strtoull函数返回 转换后的值(如果有)。如果无法执行转换,则零为 回到。如果正确的值超出可表示的范围 值,LONG_MIN,LONG_MAX,LLONG_MIN,LLONG_MAX,ULONG_MAX或 返回ULLONG_MAX(根据返回类型和符号 值,如果有的话),宏ERANGE的值存储在errno中。
您可以使用strtol
,因为它将为您提供至少32位的数字,并且告诉您溢出。如果long
是64位,则可以/需要区分一般溢出还是32位溢出。请参见以下代码对此进行说明:
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
void convert(const char* numStr) {
errno=0;
long num = strtol(numStr,NULL,10);
if (errno == ERANGE){
printf("numstr %s is out of long's range, which is %ld..%ld\n", numStr, LONG_MIN,LONG_MAX);
} else if (num < 0) {
printf("numstr %s is negative.\n", numStr);
} else if (num > UINT32_MAX) {
printf("numstr %s is out of 32 bit range, which is 0..%u\n", numStr, UINT32_MAX);
} else {
printf("OK; numstr %s is in 32 bit range, which is 0..%u\n", numStr, UINT32_MAX);
}
}
int main() {
convert("123456789012345678901234567890");
convert("-123");
convert("1234567890123567");
convert("32452345");
convert("0000000000000000000000032452345");
}
输出:
numstr 123456789012345678901234567890 is out of long's range, which is -9223372036854775808..9223372036854775807
numstr -123 is negative.
numstr 1234567890123567 is out of 32 bit range, which is 0..4294967295
OK; numstr 32452345 is in 32 bit range, which is 0..4294967295
OK; numstr 0000000000000000000000032452345 is in 32 bit range, which is 0..4294967295
答案 1 :(得分:2)
这取决于您的具体情况。如果您的应用程序正在读取完全不正确的输入,那么您将需要对传入的文本进行一些预处理。此时,您可以检测到负号(并拒绝输入),并且可以去除前导零。
之后,您可以轻松地检查数字字符串的长度,如果长度超过10位(2 ^ 32 = 4294967296,其中有10位数字),则拒绝输入。如果它小于10位数字,则表示它在[0..2 ^ 32-1]范围内。
如果它完全是10位数字,那么您至少有几个选择:
< 2^32
cmp
可以很容易地实现这一点。您使用哪种类型取决于您的限制。
答案 2 :(得分:2)
您可以在[0,2 32 -1](包括)范围内使用标准的strtoul()
函数。
我建议检查指针strtoul()
分配的指针(下面的ends
)以检测类似"O"
(字母O,不为零)和"1O"
(字母1和字母O)的情况,而不是十);也就是说,输入确实确实有一个十进制数字,并以适当的分隔符或字符串结尾标记结束。
例如:
#include <stdlib.h>
#include <inttypes.h>
#include <errno.h>
const char *next_u32(const char *from, uint32_t *to)
{
const char *ends = from;
unsigned long value;
if (!from) {
errno = EINVAL; /* NULL from pointer */
return NULL;
}
/* Parse the number to 'value' */
errno = 0;
value = strtoul(from, (char **)ends, 10); /* 10 = decimal input */
if (errno)
return NULL;
if (ends == from) {
errno = EDOM; /* Not a valid number */
return NULL;
}
/* Is it too large? */
if (value > 4294967295uL) {
errno = ERANGE; /* Out of valid range */
return NULL;
}
/* Verify the separator is a space or end-of-string. */
if (*ends != '\0' && *ends != '\t' && *ends != '\n' &&
*ends != '\v' && *ends != '\f' && *ends != '\r' &&
*ends != ' ') {
errno = EDOM; /* The number was immediately followed by garbage. */
return NULL;
}
/* Accepted. Save value, and return the pointer to the
first unparsed character. */
if (to)
*to = (uint32_t)value;
return ends;
}
要解析一行中的所有32位无符号整数,可以使用循环:
const char *line; /* Contains the line with decimal integers to parse */
uint32_t value;
while ((line = next_u32(line, &value))) {
/* Use 'value' */
}
如果要解析大量的十进制无符号整数,则标准库的strtoul()
不是最快的选择。
此外,有时仅对输入文件进行内存映射并让OS处理分页会更容易。但是,在那种情况下,末尾没有字符串末尾的NUL字节(\0
),因此需要使用其他接口。
对于这些情况,可以使用以下功能。它获取并更新指向内容中当前位置的指针,但决不超过结束指针。如果输入的下一项是十进制的32位无符号整数,则返回零,否则返回非零错误代码。
#define NEXT_OK 0
#define NEXT_END 1
#define NEXT_BAD -1
#define NEXT_OVER -2
static int next_u32(const char **fromptr, const char *end,
uint32_t *to)
{
const char *from;
uint32_t val = 0;
if (!fromptr)
return NEXT_END;
from = *fromptr;
/* Skip whitespace characters, including NUL bytes. */
while (from < end && (*from == '\0' || *from == '\t' ||
*from == '\n' || *from == '\v' ||
*from == '\f' || *from == '\r' ||
*from == ' '))
from++;
/* At end? */
if (from >= end)
return NEXT_END;
/* Skip a leading + sign. */
if (*from == '+' && end - from > 1)
from++;
/* Must be a decimal digit now. */
if (!(*from >= '0' && *from <= '9'))
return NEXT_BAD;
/* Skip leading zeroes. */
while (from < end && *from == '0')
from++;
/* Parse the rest of the decimal number, if any. */
while (from < end && *from >= '0' && *from <= '9') {
if ((value > 429496729) || (value == 429496729 && *from >= '6'))
return NEXT_OVER;
value = (10 * value) + (*(from++) - '0');
}
/* If not at end, check the character. */
if (from < end && *from != '\0' && *from != '\t' &&
*from != '\n' && *from != '\v' &&
*from != '\f' && *from != '\r' &&
*from != ' ')
return NEXT_BAD;
/* Parsed correctly. */
*fromptr = from;
if (*to)
*to = value;
return NEXT_OK;
}
答案 3 :(得分:1)
我建议使用int64_t
以确保您的编号在每个体系结构上都相同。您将拥有2 ^ 32个“正数或空值”,并且小于零的任何值都是溢出或负数(只有真正的负数才会再次变为正数)。但是您可以通过将符号读为字符而不是数字int64_t来规避此问题,因此任何负数都将溢出(因为您之前捕获了'-'符号)
这是“ PSEUDO -代码”以说明该过程:
char sign = '\0'
uint64_t = 0
read (%c,&sign)
if (c=='-' or c=='+' or isdigit(c))
if isdigit(c)
unget()
read(%ull, &number) //read as unsigned as only negative are 2-complement
if number < 0
// the number is too big
else
//number is < 2^32
else
// number is "truly" negative
答案 4 :(得分:1)
您无法使用32位数字检测32位数字的溢出。如果它是无符号的,它将始终被理解为0到2 ^ 32 -1之间的数字。输入溢出仍然会导致有效输出。一个有符号的32位值在0-2 ^ 31-1范围内将是“有效的”。负值将被视为无效。在-2 ^ 31和0或2 ^ 31和2 ^ 32-1之间的上/下流动输入将导致无效的负数。但是,您可能会发现2 ^ 32以上的数字将再次显示为有效。建议您使用带符号的64位数字,并将负数或大数视为无效输入。这为您提供了可以正确过滤的更大范围的输入值。例如,如果输入受到逗号限制并且省略了逗号,那么仍然可能存在问题。我建议将输入数字作为字符串不应超过限制长度。长度过滤器应允许表示大于2 ^ 32的数字的字符串,但应过滤出大于2 ^ 63的数字。在中间的某个地方。要测试类型的大小,请使用“ sizeof()”。例如sizeof(long),sizeof(long long)等。但是,您的平台通常有明确大小的整数。为了实现可移植性,请使用您自己的类型和typedef,并将与平台相关的代码本地化为仅包含于平台相关性的包含文件。