具有整数溢出条件的整数的反转
Posted
技术标签:
【中文标题】具有整数溢出条件的整数的反转【英文标题】:Reverse of a Integer with integer overflow condition 【发布时间】:2017-10-28 23:45:07 【问题描述】:long long reverse(long long x)
long long reversednum=0;
int sign=1;
if(x<0)
sign=-1;
x=abs(x);
while(x)
reversednum=reversednum*10;
reversednum=reversednum+(x%10);
x=x/10;
return (reversednum<INT_MAX || reversednum>INT_MIN)?reversednum*sign:0;
我的大多数测试用例都得到了满足,除了这个 1534236469 返回输出 1056389759。我看到了很多建议并将其更改为 long long。但仍然产生相同的结果。希望有人能帮助我。提前致谢!! 输入假定为 32 位有符号整数。当反转整数溢出时,我的函数应该返回 0
【问题讨论】:
有符号整数溢出未定义;您必须在 执行操作之前检查溢出。INT_MAX
和 long long
也来自不同的整数域。
@ensc:很可能 OP 的 long long
比 int
s 更宽,并且不会溢出他的值。
即使我使用 LLONG_MAX 和 LLONG_MIN 也会显示相同的结果。
return (reversednum<INT_MAX || reversednum>INT_MIN)?reversednum*sign:0;
应该是return (reversednum<INT_MAX && reversednum>INT_MIN)?reversednum*sign:0;
【参考方案1】:
您正在使用 INT_MAX 和 INT_MIN,而您可能应该使用 LLONG_MAX 和 LLONG_MIN
long long reverse(long long x)
long long reversednum = 0;
int sign = 1;
if (x<0)
sign = -1;
x = abs(x);
while (x)
reversednum = reversednum * 10;
reversednum = reversednum + (x % 10);
x = x / 10;
return (reversednum<LLONG_MAX || reversednum>LLONG_MIN) ? reversednum*sign :
0;
int main()
long long int i;
scanf_s("%lld", &i);
printf("%lld",reverse(i));
scanf_s("%d", &i);
已在值 1232 和您的给定值上进行了测试,结果显示不佳 (1534236469)。
【讨论】:
但我的代码也适用于 1232 和其他小值,但它不满足输入 1534236469 @AnandhKishan,您必须接近极限(以 10 为底,仅接近LLONG_MAX
极限的 1% 才会遇到麻烦。请参阅我的答案。【参考方案2】:
在反转之前检查数字是相当复杂的,所以:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
long long reverse(long long x)
long long reversednum = 0;
// just in case
// please be aware that the minimum size for "int" is 16-bit!
// you might think about changing it to an actual number if you
// really need 32-bit
long long int_max = INT_MAX;
long long int_min = INT_MIN;
int sign = 1;
// abs() would have been wrong for "long long", it should have been "llabs()"
// also: you already checked if x is negative, no need for "abs()" here
if (x < 0)
sign = -1;
x = -x;
while (x)
reversednum = reversednum * 10;
reversednum = reversednum + (x % 10);
x = x / 10;
// you want "reversednum" to be in the range int_min < reversednum < int_max
// hence the need to check both limits
return (reversednum < int_max
&& reversednum > int_min) ? reversednum * sign : 0;
int main(int argc, char **argv)
long long x;
if (argc != 2)
fprintf(stderr, "Usage: %s integer\n", argv[0]);
exit(EXIT_FAILURE);
errno = 0;
x = strtoll(argv[1], NULL, 10);
if ((errno == ERANGE && (x == LLONG_MAX || x == LLONG_MIN))
|| (errno != 0 && x == 0))
fprintf(stderr, "Input %s caused an error in converting: %s\n", argv[1],
strerror(errno));
exit(EXIT_FAILURE);
printf("IN : %lld\n", x);
x = reverse(x);
printf("OUT : %lld\n", x);
exit(EXIT_SUCCESS);
正如 cmets 中所说,但也值得在此重复:限制 int_*
的最低保证只有 16 位 (ISO/IEC 9899:2011 5.2.4.2.1)!
【讨论】:
【参考方案3】:我确定您必须在 printf 中使用 %ld...使用 %lld!简单的错误:)
分析发生了什么:
当你进入 1534236469 结果应该是函数正确生成的 9646324351(是的!)。
在二进制中,9646324351 写为 00000010 00111110 11110111 00111010 01111111 。现在您的编译器为 long 分配 4 个字节,为 long long 分配 8 个字节。因此,当您打印 long (应该是 long long 的地方)时,编译器将简单地从二进制文件中获取前 4 个字节并丢弃其余字节。这意味着您的编译器只需要 00111110 11110111 00111010 01111111 ,这相当于十进制的 1056389759。因此输出......所以这意味着你的功能和逻辑是正确的(phewwww....!!!!)但是你犯了一个愚蠢的错误。
【讨论】:
不,不是,9,646,324,351
,这是正确的结果(不是你在答案中输入的那个,错过了前一个到最后一个数字 5
)是 FAR超过 32 位数字的 32 位限制,即使是无符号数,也就是 4,294,967,296。您必须在发布之前重新考虑您的答案!!!您的问题无法解决二进制算术,因为您正在转换以 10 为基数的数字......并且乘以 10 可能导致数字溢出远低于 MAX_INT
或 MAX_UINT
限制。
@Luis Colorado Dude...我自己写了 964632431...不是从终端复制的。所以可能是笔误。而且我 100% 确定我的答案是正确的。放开你的自我。甚至操作员也将其标记为正确。如果存在逻辑错误或者您有更好的解决方案。请赐教。
是的,这不是自我的问题。你写的号码根本没有失败,是 PO 写的失败了。由于我的回答中暴露的原因,它失败了,而不是因为我的自我。你是唯一一个提到别人的自我的人,相信我,你的答案是不正确的。对于一个在 32 位整数中失败的数字,您至少需要十位数字,而您写的数字只有九位。您已经编辑了您的回复(我明白了),所以再试一次,发现它失败了,尽管您使用了打印它的格式。
老兄.. 对不起,如果冒犯了自我部分,但这只是一个错字,我错过了 5。0kay 做一件事。 op提供的输出..将其转换为二进制。并将(您的输出)正确的输出转换为二进制。匹配前 4 个字节。你会明白为什么我是对的,为什么它迫使我说这个操作错误地使用了 %d 或 %ld 而不是 %lld。 long long 通常有 8 个字节的长度。 long 和 int 一般有 4 个。所以如果输出 long long 成 long 或 int... 只会考虑前 4 个字节。这就是编译器架构的基本概念。它的工作方式!
@Luis Colorado 如果您仍然不满意,请告诉我。我有更多这样的例子(甚至更复杂)来证明我的观点。而且,如果一个人已经编程了很长时间,相信我,这个逻辑甚至不是逻辑。它相当于 2+2=4。它就是它的工作方式。真的很抱歉自我部分,顺便说一句,没有冒犯。【参考方案4】:
您的问题的问题是您假设问题出在符号位上(并且您不能用有效位溢出),但它不存在,您在接近符号位之前就遇到了错误。在分析您的代码后,在最后一次乘以 10 时会出现最终的无符号 32 位整数溢出(乘以 10,而不是乘以 2,因此每次乘法最多可以得到四个新位,从而更容易溢出) , 即使你做unsigned
算术(推荐这样你不需要玩符号)
只需修改一点代码以使用unsigned
算术,并在reverse
函数中对该版本的程序发出一些跟踪:
#include <stdio.h>
#include <stdlib.h>
unsigned reverse(unsigned x, unsigned base)
unsigned reversednum=0;
while(x)
unsigned digit = x % base;
reversednum *= base;
reversednum += digit;
x /= base;
printf("reverse: digit=%u, reversednum=%u, x=%u, base=%u\n",
digit, reversednum, x, base);
return reversednum;
int main()
char buffer[100];
while ( fgets(buffer, sizeof buffer, stdin) )
unsigned x = (unsigned) atol(buffer);
printf("reverse(%u) => %u\n", x, reverse(x, 10));
/* while */
/* main */
使用您的输入执行:
$ reversed
23
reverse: digit=3, reversednum=3, x=2, base=10
reverse: digit=2, reversednum=32, x=0, base=10
reverse(23) => 32
1534236469
reverse: digit=9, reversednum=9, x=153423646, base=10
reverse: digit=6, reversednum=96, x=15342364, base=10
reverse: digit=4, reversednum=964, x=1534236, base=10
reverse: digit=6, reversednum=9646, x=153423, base=10
reverse: digit=3, reversednum=96463, x=15342, base=10
reverse: digit=2, reversednum=964632, x=1534, base=10
reverse: digit=4, reversednum=9646324, x=153, base=10
reverse: digit=3, reversednum=96463243, x=15, base=10
reverse: digit=5, reversednum=964632435, x=1, base=10 <-- after multipliying
by base, unsigned
overflows here to
9,646,324,350 -
2*4,294,967,296 ==
1,056,389,758
reverse: digit=1, reversednum=1056389759, x=0, base=10
reverse(1534236469) => 1056389759
您的代码的主要问题是您的号码(十位数字)中的最后一位数字可以是0..9
,并且您的十位数字中的第一位数字不能超过4
(如果是4
,那么第二个不能超过2
...或者发生溢出,给出一个大于无符号最大可能值的数字)例如,所有大于1,000,000,000的数字以 5 或更多结束的数字将溢出,从以 4 结束的数字集合中,所有以 2 或更多结束的数字将从以 24 结束的数字中溢出,
在924
中完成,其前一个数字大于4
,所有都将溢出,......所以直到在基数中形成数字MAX_UINT
,您正在进行计算。
签名数字也会发生这种情况,因此您必须限制函数的域不溢出。由于最后一位为5
的所有十位数字都会溢出,我建议为第一个溢出的数字设置最大值1,000,000,005
。对于不同的基数,您将有不同的限制,因此您必须小心(我不应该允许大于或等于 base^(floor(log(MAX_UINT)/log(base))) ==> 1,000,000,000
的数字,因为您将开始获得溢出的数字窗口,并穿插不t --- 在 32 位无符号的情况下,第一个失败的数字是 1,000,000,005
,而对于有符号的数字,它是 1,000,000,003
)
对于 64 位 unsigned
s,限制应为 1.0E19
(== 10^((int)(log((double)MAX_ULONG_LONG)/log(10.0)))
),因为 10,000,000,000,000,000,002
是第一个失败的数字。对于signed
s,限制应该是1.0E18
,因为1,000,000,000,000,000,039
应该是第一个失败的数字。
只是为了完成
要考虑使用一组 32 位整数正常工作的函数,您应该像您所做的那样做出断言,但不要使用 INT_MAX
或 INT_MIN
,您必须使用实际的数字边界限制.类似的东西
#include <stdint.h>
#include <assert.h>
int32_t reverse(int32_t x)
assert(x > -1000000000 && x < 1000000000);
int32_t reversednum = 0;
while(x)
int digit = x % 10;
reversednum *= 10;
reversednum += digit;
x /= 10;
/* while */
return reversednum;
/* reverse */
甚至可以在运行时禁用生产代码的范围检查。 base
已固定为 10
,因为这是您的函数,而 assert(3)
限制取决于编号基数的选择。
【讨论】:
以上是关于具有整数溢出条件的整数的反转的主要内容,如果未能解决你的问题,请参考以下文章