簡體   English   中英

以編程方式確定有符號整數類型的最大值

[英]Programmatically determining max value of a signed integer type

這個相關問題是關於在編譯時確定有符號類型的最大值:

C 問題:off_t(和其他有符號整數類型)最小值和最大值

但是,我后來意識到在運行時確定有符號類型(例如time_toff_t )的最大值似乎是一項非常困難的任務。

我能想到的最接近解決方案的是:

uintmax_t x = (uintmax_t)1<<CHAR_BIT*sizeof(type)-2;
while ((type)x<=0) x>>=1;

只要type沒有填充位,這就避免了任何循環,但如果type確實有填充位,則轉換會調用實​​現定義的行為,這可能是信號或無意義的實現定義的轉換(例如剝離符號位)。

我開始認為這個問題無法解決,這有點令人不安,在我看來,這將是 C 標准中的一個缺陷。 有什么想法可以證明我錯了嗎?

讓我們先看看 C 是如何定義“整數類型”的。 摘自ISO/IEC 9899 §6.2.6.2:

6.2.6.2 整數類型
1
對於 unsigned char 以外的無符號整數類型,對象表示的位應分為兩組:值位和填充位(后者不需要)。 如果有 N 個值位,每個位應表示 1 到 2N-1 之間的不同的 2 次冪,因此該類型的對象應能夠使用純二進制表示法表示從 0 到 2N-1 的值; 這應稱為值表示。 任何填充位的值都是未指定的。44)
2
對於有符號整數類型,對象表示的位應分為三組:值位、填充位和符號位。 不需要任何填充位; 應該正好有一個符號位。 作為值位的每個位應與相應無符號類型的對象表示中的同一位具有相同的值(如果有符號類型中有 M 個值位,無符號類型中有 N,則 M ≤ N)。 如果符號位為零,則不應影響結果值。 如果符號位為 1,則應按以下方式之一修改該值:

— 符號位為 0 的相應值取反(符號和大小);
— 符號位的值為 −(2N)(二進制補碼);
— 符號位的值為 −(2N − 1)(一個的補碼)。

其中哪一個適用是實現定義的,就像具有符號位 1 和所有值位為零(對於前兩個),或具有符號位和所有值位 1(對於 1 的補碼)的值是否是陷阱表示或正常值。 在符號和幅度以及補碼的情況下,如果此表示是正常值,則稱為負零。

因此我們可以得出以下結論:

  • ~(int)0可能是一個陷阱表示,即將所有位設置為是一個壞主意
  • int中可能存在對其值沒有影響的填充位
  • 實際表示 2 的冪的位的順序是未定義的; 如果存在,符號位的位置也是如此。

好消息是:

  • 只有一個符號位
  • 只有一位代表值 1


考慮到這一點,有一種簡單的技術可以找到int的最大值。 找到符號位,然后將其設置為 0,並將所有其他位設置為 1。

我們如何找到符號位? 考慮int n = 1; ,這是嚴格的正數,並保證只有一位和一些填充位設置為 1。然后對於所有其他位i ,如果i==0真,則將其設置為 1 並查看結果值是否為負. 如果不是,則將其恢復為 0。否則,我們已找到符號位。

現在我們知道符號位的位置,我們取int n ,將符號位設置為零,將所有其他位設置為 1,並且 tadaa,我們有最大可能的int值。

確定int最小值稍微復雜一些,留給讀者作為練習。



請注意,C 標准幽默地不需要兩個不同的int表現相同。 如果我沒記錯的話,可能有兩個不同的int對象,例如它們各自的符號位位於不同的位置。



編輯:在與 R.. 討論這種方法時(見下面的評論),我已經確信它在幾個方面存在缺陷,更一般地說,根本沒有解決方案。 我看不出有什么方法可以修復這個帖子(除了刪除它),所以我讓它保持不變,以便下面的評論有意義。

在數學上,如果您有一個有限集(X,大小為 n(na 正整數)和一個比較運算符(X 中的 x,y,z;x<=y 和 y<=z 意味着 x<=z),它是一個找到最大值的非常簡單的問題。(而且,它存在。)

解決這個問題最簡單但計算成本最高的方法是生成一個包含所有可能值的數組,然后找到最大值。

第 1 部分。對於具有有限成員集的任何類型,都有有限數量的位 (m) 可用於唯一地表示該類型的任何給定成員。 我們只是創建一個包含所有可能位模式的數組,其中任何給定的位模式都由特定類型的給定值表示。

第 2 部分。接下來我們需要將每個二進制數轉換為給定的類型。 這項任務是我的編程經驗不足使我無法談論如何完成這項任務的地方。 我已經閱讀了一些關於鑄造的內容,也許這可以解決問題? 或者其他一些轉換方法?

第 3 部分。假設上一步已完成,我們現在擁有所需類型的有限值集和該集上的比較運算符。 找到最大值。

但是如果……

...我們不知道給定類型的確切成員數量? 比我們高估了。 如果我們不能產生合理的高估,那么數字應該有物理界限。 一旦我們高估了,我們會檢查所有可能的位模式以確認哪些位模式代表該類型的成員。 丟棄未使用的那些之后,我們現在有一組所有可能的位模式,代表給定類型的某個成員。 這個最近生成的集合是我們現在在第 1 部分中使用的。

...我們沒有那種類型的比較運算符? 比具體問題不僅不可能,而且在邏輯上是不相關的。 也就是說,如果我們的程序無法獲得有意義的結果,如果我們比較給定類型的兩個值,那么我們的給定類型在我們的程序上下文中沒有排序。 沒有排序,就沒有最大值之類的東西。

...我們不能將給定的二進制數轉換為給定的類型? 然后方法中斷。 但與前面的例外類似,如果您不能轉換類型,那么我們的工具集在邏輯上似乎非常有限。

從技術上講,您可能不需要在二進制表示和給定類型之間進行轉換。 轉換的全部意義在於確保生成的列表是詳盡無遺的。

...我們想優化問題? 然后我們需要一些關於給定類型如何從二進制數映射的信息。 例如,無符號整數、有符號整數(2 的補碼)和有符號整數(1 的補碼)都以非常有文檔和簡單的方式從位映射到數字。 因此,如果我們想要 unsigned int 的最高可能值並且我們知道我們正在處理 m 位,那么我們可以簡單地用 1 填充每個位,將位模式轉換為十進制,然后輸出數字。

這與優化有關,因為此解決方案中最昂貴的部分是列出所有可能的答案。 如果我們對給定類型如何從位模式映射有一些先前的了解,我們可以通過生成所有潛在候選者來生成所有可能性的子集。

祝你好運。

更新:謝天謝地,我之前在下面的回答是錯誤的,這個問題似乎有一個解決方案。

intmax_t x;
for (x=INTMAX_MAX; (T)x!=x; x/=2);

該程序要么生成包含T類型的最大可能值的x ,要么生成一個實現定義的信號。

解決信號情況可能是可能的,但困難且計算上不可行(因為必須為每個可能的信號編號安裝信號處理程序),所以我認為這個答案並不完全令人滿意。 POSIX 信號語義可能會提供足夠多的附加屬性使其可行; 我不確定。

有趣的部分是,當(T)x導致實現定義的轉換時,會發生什么,特別是如果您假設自己沒有使用將生成信號的實現。 上述循環的訣竅在於它根本不依賴於實現對轉換值的選擇。 它所依賴的只是當且僅當x適合類型T (T)x==x是可能的,否則x的值超出T類型的任何表達式的可能值范圍。


老想法,錯誤,因為它沒有考慮到上面的(T)x==x屬性:

我想我有一個證明我正在尋找的東西是不可能的草圖:

  1. 讓 X 成為一個符合 C 的實現並假設INT_MAX>32767
  2. 定義一個與 X 相同的新 C 實現 Y,但INT_MAXINT_MIN的值均除以 2。
  3. 證明 Y 是符合 C 語言的實現。

這個大綱的基本思想是,由於與帶符號類型的越界值相關的一切都是實現定義或未定義的行為,因此可以考慮任意數量的有符號整數類型的高值位作為填充位而不實際對實現進行任何更改,除了limits.h的限制宏。

關於這聽起來正確還是虛假的任何想法? 如果它是正確的,我很樂意將賞金獎勵給能夠做得最好的人,使其更加嚴格。

我可能只是在這里寫了一些愚蠢的東西,因為我對 C 比較陌生,但是這不是獲得最大signed嗎?

unsigned x = ~0;
signed y=x/2;

這可能是一種愚蠢的方法,但據我所知, unsigned max是有signed max *2+1。 它不會向后工作嗎?

如果這被證明是完全不充分和不正確的,很抱歉浪費了時間。

不應該像下面的偽代碼那樣做嗎?

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;

不過,對於我自己的代碼,我肯定會進行定義預處理器宏的構建時檢查。

假設修改填充位不會創建陷阱表示,您可以使用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

由於您允許在運行時執行此操作,因此您可以編寫一個函數,該函數實際上執行(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是有符號類型必須具有的最小寬度)。

對於沒有關聯無符號類型名稱的不透明有符號類型,這是無法以可移植方式解決的,因為任何檢測是否存在填充位的嘗試都會產生實現定義的行為或未定義的行為。 通過測試(無需額外知識)可以推斷出的最好結果是至少有K 個填充位。

順便說一句,這並沒有真正回答這個問題,但在實踐中仍然有用:如果假設有符號整數類型T沒有填充位,則可以使用以下宏:

#define MAXVAL(T) (((((T) 1 << (sizeof(T) * CHAR_BIT - 2)) - 1) * 2) + 1)

這可能是人們所能做到的最好的了。 它很簡單,不需要對 C 實現做任何其他假設。

也許我沒有正確回答問題,但是由於 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)) (所有bits set) (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);
}

為什么這會出現問題? 類型的大小在編譯時是固定的,所以確定類型運行時大小的問題就歸結為確定類型編譯時大小的問題。 對於任何給定的目標平台,諸如off_t offset類的聲明將被編譯為使用某個固定大小,然后在目標平台上運行生成的可執行文件時將始終使用該大小。

ETA:您可以通過sizeof(type)獲取類型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 常量的原因是這些是“半透明”類型,其使用僅限於特定域。 在需要較少不透明度的情況下,您通常會找到獲取所需信息的方法,例如可以用來計算 SUSv2 在<unistd.h>描述中提到的off_t有多大的常量。

對於所有真實機器,(二進制補碼且無填充):

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);
};

在問“怎么樣?”之前 ,首先你需要問“為什么?” . 為什么你真的需要在運行時知道這一點? 這與真正的問題有關還是只是一個學術問題?

我真的認為沒有必要在編譯時確定它。 如果程序員正在編寫程序來滿足特定需求,那么他肯定對程序運行的環境有一定的了解。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM