INT_MIN的绝对值

13

如何在不溢出的情况下提取INT_MIN的绝对值?请参阅此问题的代码:

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    printf("INT_MAX: %d\n", INT_MAX);
    printf("INT_MIN: %d\n", INT_MIN);
    printf("abs(INT_MIN): %d\n", abs(INT_MIN));

    return 0;
}

输出如下:

INT_MAX: 2147483647
INT_MIN: -2147483648
abs(INT_MIN): -2147483648

我需要这个来检查一个int值是否大于零。

至于这个问题是否与为什么最大负整数-2147483648的绝对值仍然是-2147483648?重复,我不同意,因为这是一个如何(HOW)问题,而不是为什么(WHY)问题。


5
在二进制补码中,最小数的绝对值不存在。请更详细地描述您想要实现的目标,我相信还有其他方法可以达成。 - deviantfan
3
看一下这篇帖子:https://dev59.com/UGgu5IYBdhLWcg3wj3v5 - Junior Dussouillez
@MorganWilde 请阅读此处的顶部评论。在2s补码中,如何可能将INT_MIN的绝对值存储在“unsigned”中? - Brandin
@Brandin:当然可以将INT_MIN的绝对值存储在unsigned int中... - Oliver Charlesworth
1
@ninjalj:没错,但你能说出多少个实际的 C 语言实现不遵循这个规则的例子呢? - Oliver Charlesworth
显示剩余9条评论
6个回答

5
printf格式字符串中,%d转换说明符将相应的参数转换为有符号十进制整数,在这种情况下,对于int类型,其将发生溢出。C标准明确提到了有符号整数溢出是未定义的行为。你需要在格式字符串中使用%u。另外,你需要分别包含stdio.hstdlib.h头文件以获得printfabs函数的原型。
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>

// This solves the issue of using the standard abs() function
unsigned int absu(int value) {
    return (value < 0) ? -((unsigned int)value) : (unsigned int)value;
}

int main(void) {
    printf("INT_MAX: %d\n", INT_MAX);
    printf("INT_MIN: %d\n", INT_MIN);
    printf("absu(INT_MIN): %u\n", absu(INT_MIN));

    return 0;
}

这是在我的32位计算机上的输出结果:
INT_MAX: 2147483647
INT_MIN: -2147483648
absu(INT_MIN): 2147483648

4
这是否确实正确,还是由于实现定义的行为而正常工作? - Brandin
11
请注意,这仍会触发未定义行为;来自abs的C99标准:“如果结果无法表示,则行为是未定义的”。 - Oliver Charlesworth
1
@ajay: 哎呀,不行; abs 的返回类型是 int - Oliver Charlesworth
1
一个更简单的解决方案不是将INT_MIN的值转换为字符串表示,然后去掉负号吗? - Brandin
2
在这里发表评论的每个人都需要到最上面去阅读deviantfan的评论:“补码中最小数的绝对值实际上不存在。”尝试将INT_MIN强制转换为无符号数是不可表示的。就像那条评论一样,你需要描述你在这里实际上想要达成什么目标。 - Brandin
显示剩余23条评论

3
如何呢?
printf ("abs(INT_MIN) = %ld", -((long int) INT_MIN));

如果你的long不比int更长:

printf ("abs(INT_MIN) = %lld", -((long long int) INT_MIN));

或者您可以接受这样的事实: abs(INT_MIN) 总是等于 INT_MAX + 1

printf ("abs(INT_MIN) = %u", ((unsigned int) INT_MAX ) + 1 );

6
这假设 sizeof(long) > sizeof(int),但并非总是如此... - Oliver Charlesworth
我有一个类似的想法,但是当我需要 LONG LONGabs() 时会发生什么? - Morgan Wilde
在这种情况下,请使用 long long int(已编辑以修复此问题)。 - abligh
@abligh 好的,这在某种程度上是可行的,所以我会点赞,但这不是一个完美的解决方案。 - Morgan Wilde
@MorganWilde,没有二进制补码有符号整数类型能够表示最大负数的绝对值。 - abligh
显示剩余2条评论

2

没有一种可移植的方式可以将最小负数的绝对值作为整数提取出来。ISO C标准规定(§6.2.6.2¶2):

每个值位必须具有与相应无符号类型的对象表示中相同的值(如果有M个值位在有符号类型中,N个值位在无符号类型中,则M≤N)。

请注意,它使用的是 ≤,而不是 <。

由于2的补码中的符号位具有值 −(2M),每个值位具有介于 12M-1 之间的二的幂次方的值,因此在 M=N 的实现上,无符号整数无法表示 2N,它只能表示最多 2N-1 = 1+2+...+2N-1


那么仅使用 unsigned int 就可以表示一个额外的值 2^(N-1)?这很令人困惑。unsigned int 的范围不是从 02^(N) - 1 吗?标准还说(6.2.5:9)-“有符号整数类型的非负值范围是相应无符号整数类型的子范围”。因此,两个范围之间的差异只有一个值 2^(N-1) - ajay
使用无符号并不能保证你能获得更多的值。但是,你可以检查 limits.h 中的宏 INT_MAXUINT_MAX 等,以查看你所使用的平台是否为无符号整数提供了比有符号整数更严格的非负整数值范围。 - ninjalj
在我的机器上,UINT_MAX2^32 - 1,而INT_MAX2^31 - 1。我想知道为什么它不是保证的,那使用无符号类型有什么用呢?假设signedunsigned拥有相同的范围(除了2^(N-1)),有符号类型在无符号类型中,符号位为1时表示哪个值? - ajay
整数可以有填充位。一些整数值可能是陷阱表示(类似于浮点数中的NaN)。60年代和70年代的旧机器以及DSP会做一些奇怪的事情。C标准的编写是为了支持已知运行C的每个古怪机器。 - ninjalj
标准并未说明有多少填充位。它还说可能没有填充位。我怎么知道我的机器上是否有填充位?这真的很奇怪,我从来不知道这一点。试图编写在所有使用过 C 的机器上运行的 C 程序确实很困难! - ajay
你可以通过使用 sizeofCHAR_BIT 来判断一个平台是否具有填充位。我不知道现代平台中是否有这样的情况,但可能一些 DSPs 会有。 - ninjalj

0
在C语言中,函数int abs(int j)只有整型版本。您可以在头文件stdlib.h下使用另一个函数labs。它的原型是:long int labs(long int j);
#include <limits.h> 
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    printf("INT_MAX: %d\n", INT_MAX);
    printf("INT_MIN: %d\n", INT_MIN);
    printf("abs(INT_MIN): %ld\n", labs((long)INT_MIN));

    return 0;
}

http://www.cplusplus.com/reference/climits/ 表示这些限制的实际值取决于具体实现。因此,仅当 INT_MIN 不等于 LONG_MIN 时,此解决方案才有效。 - xmoex
是的,谢谢你提醒我。 - xuefu

0
强制转换为下一个可用的更大整数类型应该可以解决问题,但你必须使用相应的 abs 变体(在这种情况下是 llabs(...))。
printf("llabs(INT_MIN): %lld\n", llabs((long long int)INT_MIN));

编辑:

你可以通过比较 INT_MINLONG_MINLLONG_MIN 来检查下一个更大类型。也许在你的情况下,将其转换为 long 就足够了。

printf("labs(INT_MIN): %ld\n", labs((long int)INT_MIN));

请注意,实际上显式转换是不必要的,因为函数本身会隐式地转换参数。

-2

首先,你必须要#include <math.h>才能正确使用abs函数。

其次,如果你想要实现的唯一目标是打印在limits.h中定义的INT_MIN的绝对值,你可以将其作为unsigned integerlong long integer打印出来,像这样:

printf( "abs(INT_MIN): %u\n", abs( INT_MIN ) );     // %u for unsigned int
printf( "abs(INT_MIN): %lld\n", abs( INT_MIN ) );   // %lld for long long int

由于您想要绝对值,这肯定是无符号的,所以这应该没问题。

如果您不想包含math.h,您可以自己这样实现:

// ternary implementation of the function abs
printf( "abs(INT_MIN): %u\n", ( INT_MIN > 0 ) ? INT_MIN : -INT_MIN );

如果您想将其用于其他目的,那么可以将abs(INT_MIN)值存储在unsigned intlong long int变量中。


2
( INT_MIN > 0 ) ? INT_MIN : -INT_MIN - 这是什么啊? - Oliver Charlesworth
2
你必须包含 math.h 才能正确使用 abs。abs 在 stdlib.h 中... - Junior Dussouillez
@OliCharlesworth 无论怎样,这就是确切的翻译;但是当然,它始终只是-INT_MIN - Utkan Gezer
@JuniorDussouillez 我并没有说它不在 stdlib.h 中,是吗?它只是同时存在于两个库中。 - Utkan Gezer
@ThoAppelsin:确实如此,但在实践中-INT_MIN == INT_MIN(尽管在技术上是未定义的行为)。 - Oliver Charlesworth
@OliCharlesworth:这仅适用于二进制补码。标准允许使用有符号数和反码,我怀疑某些DSP仍在使用它们。 - ninjalj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接