typedef签名类型的最大值

4
我是一名有用的助手,可以为您翻译文本。
我正在阅读John Regehr的博客,了解他如何给他的学生分配关于饱和算术的任务。有趣的部分在于代码必须按原样编译,同时使用typedef来指定不同的整数类型,参见以下完整标题摘录:
typedef signed int mysint;
//typedef signed long int mysint;

mysint sat_signed_add (mysint, mysint);

mysint sat_signed_sub (mysint, mysint);

相应的无符号版本很容易实现(尽管我实际上不确定填充位是否会使它也变得棘手),但我实际上不知道如何在C中获取未知有符号类型的最大(或最小)值,而不使用MAX_MIN_宏或导致未定义的行为。

我错过了什么,还是这个任务有缺陷(或更可能是我错过了他给学生的一些关键信息)?


limits.h文件包含所有你所需要的限制。 - user3629249
5个回答

6

我不认为有任何方法可以在不做出假设或调用实现定义 (不一定是未定义) 的行为的情况下完成此操作。 但是,如果您假设 mysintuintmax_t 表示中不存在填充位,则可以像这样计算最大值:

mysint mysint_max = (mysint)
    ((~(uintmax_t)0) >> (1 + CHAR_BITS * (sizeof(uintmax_t) - sizeof(mysint))));

最小值是-mysint_max(符号/大小或补码)或-mysint_max - 1(二进制补码),但确定哪个是有点棘手的。你不知道哪一位是符号位,而且对于不同的表现形式可能存在陷阱表示方式。您还必须小心评估表达式,因为“通常算术转换”可能将值转换为表示具有不同属性的类型,而不是您正在尝试探测的类型。
然而,您可以通过计算-1mysint表示的按位取反来区分负值表示的类型。对于二进制补码,结果的mysint值为0,对于二进制反码,它为1,对于符号/大小它为mysint_max-1
如果您假设所有有符号整数类型都具有相同类型的负值表示,则可以使用默认的字面量上的普通表达式执行此类测试。但是,您不需要做出这种假设。相反,您可以通过union直接在类型表示的位模式上执行操作。
union mysint_bits {
    mysint i;
    unsigned char bits[sizeof(mysint)];
} msib;

int counter = 0;

for (msib.i = -1; counter < sizeof(mysint); counter += 1) {
    msib.bits[counter] = ~msib.bits[counter];
}

只要初始假设成立(即类型mysint的表示中没有填充位),msib.i必须是所需结果的有效表示。

@rici,你是对的,该死。我查了一下,但显然没有完全阅读。 - John Bollinger
编辑以记录有关陷阱表示的额外假设。 - John Bollinger
编辑以添加解决trap表示问题的解决方法。 - John Bollinger
最终编辑,提供一个简单的检测算法,用于无需额外假设的有符号整数表示。 - John Bollinger
注意:如果您需要允许类型mysint的表示具有填充位的可能性,那么我认为无法可靠地计算使用的表示形式,因为您不能安全地假设填充位的值。您可能可以应用一种启发式方法来确定类型的属性,但是您通过启发式得出的任何结论都可能是不正确的。 - John Bollinger
显示剩余4条评论

4
我看不到在C语言中如何确定未知有符号整数类型的最大和最小可表示值,除非知道更多信息。(在C++中,您可以使用std::numeric_limits,因此这很容易。)
无符号整数类型的最大可表示值是(myuint)(-1)。独立于填充位,保证可以工作(§ 6.3.1.3/1-2):
当具有整数类型的值转换为另一种整数类型时......如果新类型是无符号的,则通过重复添加或减去可以在新类型中表示的最大值加一来将值转换为新类型范围内的值。
因此,要将-1转换为无符号类型,您需要将一个可以表示为最大值加1的数值加到它上面,结果必须是最大可表示值。(标准明确指出“重复添加或减去”的含义是数学上的。)
现在,如果您知道有符号类型中填充位的数量与无符号类型中填充位的数量相同[但请参见下文],则可以从最大可表示无符号值计算出最大可表示有符号值:
(mysint)( (myuint)(-1) / (myuint)2 )

很不幸,这还不足以计算最小可表示的有符号值,因为标准允许最小值可以是最大值的负值减一(二进制补码表示)或者是最大值的负值(一进制补码或符号/大小表示)。
此外,标准实际上并没有保证有符号类型中填充位的数量与无符号类型中填充位的数量相同。它保证的仅仅是有符号类型中的值位数不超过无符号类型中的值位数。特别地,如果无符号类型比相应的有符号类型多一个填充位,那么它们将具有相同数量的值位,并且最大可表示值将相同。[注:值位既不是填充位也不是符号位。]
简而言之,如果你知道(例如被告知)架构是二进制补码,并且相应的有符号和无符号类型具有相同数量的填充位,那么你肯定可以计算出有符号的最小值和最大值。
myuint max_myuint = (myuint)(-1);
mysint max_mysint = (mysint)(max_myuint / (my_uint)2);
mysint min_mysint = (-max_mysint) - (mysint)1;

最后,将超出范围的无符号整数强制转换为有符号整数并不是未定义的行为,尽管大多数其他有符号溢出都是如此。 根据§6.3.1.3/3所示,转换是实现定义行为:

否则,新类型为有符号类型,并且该值无法在其中表示; 结果是实现定义的或引发实现定义的信号。

实现定义的行为需要由实现记录在文档中。 因此,假设我们知道实现是gcc。 然后我们可以查看gcc文档,在“C实现定义的行为”部分,我们将读到以下内容:
  • 有符号整数类型是使用符号和幅度、二补数还是一补数表示,以及异常值是陷阱表示还是普通值(C99 6.2.6.2)。

    GCC仅支持二进制补码整数类型,并且所有位模式都是普通值。

  • 将整数转换为无法在该类型的对象中表示的有符号整数类型时的结果或引发的信号(C90 6.2.1.2、C99 6.3.1.3)。

    对于宽度为N的类型的转换,该值对2^N取模以在类型范围内;不会引发信号。

知道有符号整数是二进制补码,并且无符号到有符号的转换不会陷入陷阱,而是产生预期的低位模式后,我们可以从最广泛的无符号类型uintmax_t的最大可表示值开始找到任何有符号类型的最大值和最小值:
uintmax_t umax = (uintmax_t)(-1);
while ( (mysint)(umax) < 0 ) umax >>= 1;
mysint max_mysint = (mysint)(umax);
mysint min_mysint = (-max_mysint) - (mysint)1;

请注意,尽管OP引用的赋值提供了(可能)相应的有符号和无符号类型,但实际问题严格涉及确定typedef隐藏的有符号类型的特征。也许您可以考虑修改您出色的答案,以讨论相应的无符号类型未知的情况? - John Bollinger
@JohnBollinger:好的,这次我测试过了。我并不是很喜欢这个循环,但我还没有找到更好的解决方案;我将再次走出门去看看是否能够想到正确的解决方案,而不是一个错误报告 :) - rici
向下看几个答案,可以找到一种不需要循环的方法来解决这个问题,前提是与“编译器是GCC”一致但不那么严格。 :) - John Bollinger
我很难决定在你和@JohnBollingers的优秀回答之间,所以我决定给你悬赏,并接受John的答案,因为你还包括了标准和GCC文档中的引用。希望大家都能接受这个决定 :) PS:关于将无符号整数转换为有符号整数在溢出情况下不会导致未定义行为的有趣细节也很有意思,每天都能学到新东西。 - Voo

1

这是一个建议,可以获取使用typedef设置的特定类型的MAX值,而不需要使用任何库。

typedef signed int mysint;
mysint size; // will give the size of the type
size=sizeof(mysint)*(mysint)8-(mysint)1; // as it is signed so a bit
                                        // will be deleted for sign bit
mysint max=1;//start with first bit
while(--size)
{
   mysint temp;
   temp=(max<<(mysint)1)|(mysint)1;// set all bit to 1
   max=temp;
}
/// max will contain the max value of the type mysint

0

我猜这应该可以工作,无论负数表示如何

// MSB is 1 and rests are zero is minimum number in both 2's and 1's
// compliments representations.
mysint min =  (1 << (sizeof(mysint) * 8 - 1));
mysint max = ~x;

0

如果您假设使用八位的char和二进制补码表示(对于现代硬件来说都是合理的,除了一些嵌入式DSP设备),那么您只需要用sizeof(mysint)*8 - 1个1组成一个无符号整数(使用uintmax_t确保它足够大),然后将其转换为mysint。对于最小值,取最大值的相反数并减去一。

如果您不想假设这些条件,那么仍然有可能实现,但您需要通过limits.h进行更多的挖掘以弥补字符大小和符号表示的差异。


即使硬件使用二进制补码,如果您调用未定义的行为,现代编译器也会很乐意让您失望。例如,bool overflows(int a) { return a + 1 < a; } 将被优化为 return false; - Voo
1
你可以使用CHAR_BIT来避免依赖于8位字符,虽然在这种情况下并没有什么帮助。 - Voo
1
@Voo 这就是为什么你要在无符号上下文中执行它。在无符号整数上溢出是定义良好的(尽管这种方法甚至没有使用它)。 - Sneftel
@Voo 不,使用符号-大小或补码是无效的。这就是为什么如果你不假定是二进制补码,那么你需要做一些额外的工作。我猜一个有趣的技巧是使用 INT_MAX-INT_MIN 之间的差异。 - Sneftel
据我所知,标准并不要求所有有符号整数类型使用相同的负值表示方式,因此 INT_MIN-INT_MAX 之间的差异(必须是这样,而不是反过来)仅对 int 的表示方式具有明确意义。 - John Bollinger
显示剩余5条评论

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