这个相关问题是关于在编译时确定签名类型的最大值:
C question: off_t (and other signed integer types) minimum and maximum values
但是,我已经意识到在运行时确定签名类型(例如time_t
或off_t
)的最大值似乎是一项非常困难的任务。
我能想到的最接近解决方案的是:
uintmax_t x = (uintmax_t)1<<CHAR_BIT*sizeof(type)-2;
while ((type)x<=0) x>>=1;
只要type
没有填充位,这就避免了任何循环,但如果type
确实有填充位,则强制转换会调用实现定义的行为,这可能是一个信号或一个无意义的实现 - 定义转换(例如剥离符号位)。
在我看来,我开始认为问题无法解决,这有点令人不安,并且会成为C标准的缺陷。任何证明我错误的想法?
答案 0 :(得分:4)
让我们首先看看C如何定义“整数类型”。取自ISO/IEC 9899,§6.2.6.2:
6.2.6.2整数类型
1
对于unsigned char以外的无符号整数类型,对象的位 表示应分为两组:值位和填充位(需要 不是后者中的任何一个)。如果有N个值位,则每个位应表示不同的值 2的功率在1和2N-1之间,因此该类型的物体应具备能力 使用纯二进制表示来表示从0到2N - 1的值;这应该是 称为价值表示。任何填充位的值都未指定.44)
2
对于有符号整数类型,对象表示的位应分为三个 groups:值位,填充位和符号位。不需要任何填充位; 应该只有一个符号位。作为值位的每个位应具有与相应无符号类型的对象表示中的相同位相同的值(如果有符号类型中有M个值位且无符号类型中有N,则M≤N)。如果符号位 为零,它不会影响结果值。如果符号位为1,则值应为 通过以下方式之一进行修改:- 符号位0的对应值被否定(符号和幅度);
- 符号位的值为 - (2N)(二进制补码);
- 符号位的值为 - (2N - 1)(1'补码)。
这些适用中的哪一个是实现定义的,以及符号位1的值是否正确 并且所有值位为零(前两个),或者符号位和所有值位1(对于' 补码),是陷阱表示或正常值。在签名和 幅度和1'补码,如果这个表示是一个正常值,它被称为a 负零。
因此我们可以得出以下结论:
~(int)0
可能是一个陷阱表示,即设置所有位是个坏主意int
中可能存在对其值没有影响的填充位好消息是:
考虑到这一点,有一种简单的技术可以找到int
的最大值。找到符号位,然后将其设置为0并将所有其他位设置为1.
我们如何找到符号位?考虑int n = 1;
,它是严格正的,并保证只有一位,也许一些填充位设置为1.然后对于所有其他位i
,如果i==0
成立,则设置它为1并查看结果值是否为负数。如果不是,请将其还原为0.否则,我们找到了符号位。
现在我们知道符号位的位置,我们取int n
,将符号位设置为零,将所有其他位设置为1,tadaa,我们有最大可能的int
值
确定int
最低稍微复杂一些,并留给读者练习。
请注意,C标准幽默地不需要两个不同的int
s来表现相同。如果我没有弄错的话,可能会有两个不同的int
个对象,例如他们各自的标志位在不同的位置。
编辑:在与R ...讨论这种方法时(见下面的评论),我已经确信它在几个方面存在缺陷,更一般地说,根本就没有解决方案。我找不到修复此帖子的方法(除了删除它),所以我保持不变,以便下面的评论有意义。
答案 1 :(得分:3)
数学上,如果你有一个有限集(X,大小为n(na正整数)和一个比较运算符(x,y,z在X中; x&lt; = y和y&lt; = z意味着x&lt; = z) ,找到最大值是一个非常简单的问题。(另外,它存在。)
解决这个问题的最简单方法,但计算成本最高的是生成一个包含所有可能值的数组,然后找到最大值。
第1部分。对于具有有限成员集的任何类型,存在有限数量的位(m),其可用于唯一地表示该类型的任何给定成员。我们只创建一个包含所有可能位模式的数组,其中任何给定的位模式由特定类型中的给定值表示。
第2部分。接下来,我们需要将每个二进制数转换为给定的类型。这项任务是我的编程缺乏经验使我无法谈论如何实现这一目标。我已经阅读了一些关于铸造的内容,也许这样可以解决问题?还是其他一些转换方法?
第3部分。假设前一步骤已完成,我们现在拥有所需类型的有限值集和该集合上的比较运算符。找到最大值
但是如果......
...我们不知道给定类型的确切成员数量?比我们高估了。如果我们不能产生合理的高估,那么应该有数量上的物理界限。一旦我们有一个高估,我们检查所有这些可能的位模式,以确认哪些位模式代表该类型的成员。在丢弃那些未使用的那些之后,我们现在有一组所有可能的位模式,它们代表给定类型的某个成员。这个最近生成的集合就是我们现在在第1部分中使用的集合。
......我们在那种类型中没有比较运算符?比具体问题不仅不可能,而且在逻辑上无关紧要。也就是说,如果我们的程序无法提供有意义的结果,如果我们比较给定类型的两个值,那么我们的程序在我们的程序上下文中没有排序。没有排序,就没有最大值。
...我们无法将给定的二进制数转换为给定的类型?然后该方法中断。但与之前的例外类似,如果你不能转换类型,那么我们的工具集似乎在逻辑上非常有限。
从技术上讲,您可能不需要在二进制表示和给定类型之间进行转换。转换的整个过程是确保生成的列表是详尽无遗的。
......我们想优化问题?我们需要一些关于给定类型如何从二进制数映射的信息。例如,unsigned int,signed int(2恭维)和signed int(1恭维),每个映射从一个位到数字,以一种非常文档化和简单的方式。因此,如果我们想要unsigned int的最高可能值并且我们知道我们使用m位,那么我们可以简单地用1填充每个位,将位模式转换为十进制,然后输出数字。
这与优化有关,因为此解决方案中最昂贵的部分是列出所有可能的答案。如果我们先前有一些关于给定类型如何从位模式映射的知识,我们可以通过制作所有可能的候选者来生成所有可能性的子集。
祝你好运。答案 2 :(得分:2)
更新:值得庆幸的是,我之前的回答是错误的,似乎有一个问题的解决方案。
intmax_t x;
for (x=INTMAX_MAX; (T)x!=x; x/=2);
该程序要么生成x
,其中包含类型T
的最大可能值,要么生成实现定义的信号。
在信号情况下工作可能是困难的,但在计算上也是不可行的(因为必须为每个可能的信号编号安装一个信号处理程序),所以我认为这个答案并不完全令人满意。 POSIX信号语义可以提供足够的附加属性以使其可行;我不确定。
有趣的部分,特别是如果你很自在地假设你没有生成一个信号的实现,那么当(T)x
导致实现定义的转换时会发生什么。上述循环的技巧是它完全不依赖于实现的转换值选择。所有它依赖的是(T)x==x
当且仅当x
符合T
类型时才有可能,因为否则x
的值超出了<{1}}的可能值范围em> T
类型的任何表达式。
旧想法,错误,因为它没有考虑上述(T)x==x
属性:
我想我有一个证明我正在寻找的东西是不可能的:
INT_MAX>32767
。INT_MAX
和INT_MIN
的值均除以2。这个大纲的基本思想是,由于与带有签名类型的越界值相关的所有内容都是实现定义或未定义的行为,任意数量的高值位有符号整数类型的em>可以被视为填充位,除了limits.h
中的限制宏之外,实际上没有对实现进行任何更改。
关于这听起来是否正确或虚假的任何想法?如果这是正确的,我很乐意将奖金奖励给能够做得更加严谨的人。
答案 3 :(得分:1)
我可能只是在这里编写愚蠢的东西,因为我对C来说相对较新,但是这不是为了获得signed
的最大值吗?
unsigned x = ~0;
signed y=x/2;
这可能是一种愚蠢的方式,但据我所见,unsigned max
值为signed max
* 2 + 1。它不会倒退吗?
如果这被证明是完全不合适且不正确的话,那就浪费时间了。
答案 4 :(得分:0)
不应该像下面的伪代码一样做这个工作吗?
signed_type_of_max_size test_values =
[(1<<7)-1, (1<<15)-1, (1<<31)-1, (1<<63)-1];
for test_value in test_values:
signed_foo_t a = test_value;
signed_foo_t b = a + 1;
if (b < a):
print "Max positive value of signed_foo_t is ", a
或者更简单,为什么不应该做以下工作呢?
signed_foo_t signed_foo_max = (1<<(sizeof(signed_foo_t)*8-1))-1;
对于我自己的代码,我肯定会去定义一个预处理器宏的构建时检查。
答案 5 :(得分:0)
假设修改填充位不会创建陷阱表示,您可以使用unsigned char *
循环并翻转各个位,直到您按下符号位。如果您的初始值为~(type)0
,则可以获得最大值:
type value = ~(type)0;
assert(value < 0);
unsigned char *bytes = (void *)&value;
size_t i = 0;
for(; i < sizeof value * CHAR_BIT; ++i)
{
bytes[i / CHAR_BIT] ^= 1 << (i % CHAR_BIT);
if(value > 0) break;
bytes[i / CHAR_BIT] ^= 1 << (i % CHAR_BIT);
}
assert(value != ~(type)0);
// value == TYPE_MAX
答案 6 :(得分:0)
由于你允许它在运行时,你可以编写一个事实上执行(type)3
的迭代左移的函数。如果您在价值低于0
时停止,这将永远不会给您一个陷阱表示。并且迭代次数 - 1将告诉您符号位的位置。
仍然是左移的问题。由于仅使用运算符<<
会导致溢出,这将是未定义的行为,因此我们无法直接使用运算符。
最简单的解决方案是不使用上面的移位3
,而是迭代位位置并始终添加最低位。
type x;
unsigned char*B = &x;
size_t signbit = 7;
for(;;++signbit) {
size_t bpos = signbit / CHAR_BIT;
size_t apos = signbit % CHAR_BIT;
x = 1;
B[bpos] |= (1 << apos);
if (x < 0) break;
}
(起始值7
是签名类型必须具有的最小宽度,我认为。)
答案 7 :(得分:0)
为什么会出现问题?类型的大小在编译时是固定的,因此确定类型的运行时大小的问题减少了确定类型的编译时大小的问题。对于任何给定的目标平台,诸如off_t offset
之类的声明将被编译为使用一些固定大小,然后在目标平台上运行生成的可执行文件时将始终使用该大小。
ETA: 您可以通过type
获取sizeof(type)
类型的尺寸。然后,您可以与常见的整数大小进行比较,并使用相应的MAX
/ MIN
预处理器定义。您可能会发现使用起来更简单:
uintmax_t bitWidth = sizeof(type) * CHAR_BIT;
intmax_t big2 = 2; /* so we do math using this integer size */
intmax_t sizeMax = big2^bitWidth - 1;
intmax_t sizeMin = -(big2^bitWidth - 1);
仅仅因为某个值可由底层“物理”类型表示,并不意味着该值对“逻辑”类型的值有效。我想象没有提供max和min常数的原因是这些是“半透明”类型,其使用仅限于特定域。如果需要较少的不透明度,您通常会找到获取所需信息的方法,例如可以用来计算its description of <unistd.h>
中SUSv2提到的off_t
有多大的常量。
答案 8 :(得分:0)
对于不具有相关无符号类型名称的不透明带符号类型,这无法通过便携式方式解决,因为任何尝试检测是否存在填充位的尝试都会产生实现定义的行为或未定义的行为行为。通过测试可以得出的最好的结论(没有其他知识)是至少有 K 个填充位。
顺便说一句,这并不能真正回答问题,但是在实践中仍然有用:如果假设有符号整数类型T
没有没有填充位,则可以使用以下宏:
#define MAXVAL(T) (((((T) 1 << (sizeof(T) * CHAR_BIT - 2)) - 1) * 2) + 1)
这可能是最好的。它很简单,不需要假设其他有关C的实现。
答案 9 :(得分:0)
也许我没有正确地回答问题,但是由于C为您提供了3种可能的带符号整数(http://port70.net/~nsz/c/c11/n1570.html#6.2.6.2)表示形式:
,并且其中任何一个的最大值应为2^(N-1)-1
,您应该能够通过获取相应的无符号的最大值,>>1
进行移位并将其转换为正确的类型来获得(应该适合)。
如果陷阱表示妨碍了我不知道如何获得相应的最小值,但是如果不这样做,最小值应该是(Tp)((Tp)-1|(Tp)TP_MAX(Tp))
(所有位都设置)(Tp)~TP_MAX(Tp)
找出来应该很简单。
示例:
#include <limits.h>
#define UNSIGNED(Tp,Val) \
_Generic((Tp)0, \
_Bool: (_Bool)(Val), \
char: (unsigned char)(Val), \
signed char: (unsigned char)(Val), \
unsigned char: (unsigned char)(Val), \
short: (unsigned short)(Val), \
unsigned short: (unsigned short)(Val), \
int: (unsigned int)(Val), \
unsigned int: (unsigned int)(Val), \
long: (unsigned long)(Val), \
unsigned long: (unsigned long)(Val), \
long long: (unsigned long long)(Val), \
unsigned long long: (unsigned long long)(Val) \
)
#define MIN2__(X,Y) ((X)<(Y)?(X):(Y))
#define UMAX__(Tp) ((Tp)(~((Tp)0)))
#define SMAX__(Tp) ((Tp)( UNSIGNED(Tp,~UNSIGNED(Tp,0))>>1 ))
#define SMIN__(Tp) ((Tp)MIN2__( \
(Tp)(((Tp)-1)|SMAX__(Tp)), \
(Tp)(~SMAX__(Tp)) ))
#define TP_MAX(Tp) ((((Tp)-1)>0)?UMAX__(Tp):SMAX__(Tp))
#define TP_MIN(Tp) ((((Tp)-1)>0)?((Tp)0): SMIN__(Tp))
int main()
{
#define STC_ASSERT(X) _Static_assert(X,"")
STC_ASSERT(TP_MAX(int)==INT_MAX);
STC_ASSERT(TP_MAX(unsigned int)==UINT_MAX);
STC_ASSERT(TP_MAX(long)==LONG_MAX);
STC_ASSERT(TP_MAX(unsigned long)==ULONG_MAX);
STC_ASSERT(TP_MAX(long long)==LLONG_MAX);
STC_ASSERT(TP_MAX(unsigned long long)==ULLONG_MAX);
/*STC_ASSERT(TP_MIN(unsigned short)==USHRT_MIN);*/
STC_ASSERT(TP_MIN(int)==INT_MIN);
/*STC_ASSERT(TP_MIN(unsigned int)==UINT_MIN);*/
STC_ASSERT(TP_MIN(long)==LONG_MIN);
/*STC_ASSERT(TP_MIN(unsigned long)==ULONG_MIN);*/
STC_ASSERT(TP_MIN(long long)==LLONG_MIN);
/*STC_ASSERT(TP_MIN(unsigned long long)==ULLONG_MIN);*/
STC_ASSERT(TP_MAX(char)==CHAR_MAX);
STC_ASSERT(TP_MAX(signed char)==SCHAR_MAX);
STC_ASSERT(TP_MAX(short)==SHRT_MAX);
STC_ASSERT(TP_MAX(unsigned short)==USHRT_MAX);
STC_ASSERT(TP_MIN(char)==CHAR_MIN);
STC_ASSERT(TP_MIN(signed char)==SCHAR_MIN);
STC_ASSERT(TP_MIN(short)==SHRT_MIN);
}
答案 10 :(得分:0)
对于所有真实机器,(补码且无填充):
type tmp = ((type)1)<< (CHAR_BIT*sizeof(type)-2);
max = tmp + (tmp-1);
使用C ++,您可以在编译时进行计算。
template <class T>
struct signed_max
{
static const T max_tmp = T(T(1) << sizeof(T)*CO_CHAR_BIT-2u);
static const T value = max_tmp + T(max_tmp -1u);
};
答案 11 :(得分:-2)
在提问“怎么样?”之前,首先你需要问一个问题“为什么?”。为什么在运行时真的需要知道?这与真正的问题有关还是只是一个学术问题?
我真的认为没有必要在编译时确定它。如果一个程序员正在编写一个程序来满足特定的需求,那么他肯定对运行它的环境有一些了解。