有沒有一種優雅的方法可以將較大的資料型別轉換為較小的資料型別而不會導致結果溢位?
例如,轉換260為uint8_t應該導致255而不是4.
一個可能的解決方案是:
#include <limits.h>
#include <stdint.h>
inline static uint8_t convert_I32ToU8(int32_t i32)
{
if(i32 < 0) return 0;
if(i32 > UINT8_MAX) return UINT8_MAX;
return (uint8_t)i32;
}
盡管此解決方案有效,但我想知道是否有更好的方法(無需創建大量轉換函式)。
解決方案應使用 C 語言(可選 GCC 編譯器擴展)。
uj5u.com熱心網友回復:
IMO 最好的方法是首先將描述限制的常數映射為具有所需情況的常數。然后定義將使用這個新常量的宏。
所以基本的想法是這樣的:
const int8_t min_of_int8 = INT8_MIN;
const int8_t max_of_int8 = INT8_MAX;
const uint8_t min_of_uint8 = 0;
const uint8_t max_of_uint8 = UINT8_MAX;
....
#define DEFINE_CONVERTER(SRC, DST) \
inline static DST ## _t convert_ ## SRC ## _to_ ## DST (SRC ## _t src) \
{ \
return src < min_of_ ## DST ? min_of_ ## DST : (src > max_of_ ## DST ? max_of_ ## DST : (DST ## _t)src); \
}
DEFINE_CONVERTER(int32, uint8)
DEFINE_CONVERTER(int32, int8)
....
這是使用 C 撰寫的測驗。
仔細測驗它,因為某些隱式轉換可能會潛伏并破壞特定型別對的此宏。
如果您希望函式名稱具有不同的模式(如 this I8 U32),則對常量執行相同的技巧并分別定義typedef哪個名稱將包含所需的短版本型別。
注意 OpenSSL 上使用了類似的方法來為不同的型別提供相同的功能。
uj5u.com熱心網友回復:
在 C11 中,您可以使用新的_Generic選擇功能
#define GET_MIN(VALUE) _Generic((VALUE), \
char : CHAR_MIN, \
signed char : SCHAR_MIN, \
short : SHRT_MIN, \
int : INT_MIN, \
long : LONG_MIN, \
long long : LLONG_MIN, \
default : 0 /* unsigned types */)
#define GET_MAX(VALUE) _Generic((VALUE), \
char : CHAR_MAX, \
unsigned char : UCHAR_MAX, \
signed char : SCHAR_MAX, \
short : SHRT_MAX, \
unsigned short : USHRT_MAX, \
int : INT_MAX, \
unsigned int : UINT_MAX, \
long : LONG_MAX, \
unsigned long : ULONG_MAX, \
long long : LLONG_MAX, \
unsigned long long : ULLONG_MAX)
#define CLAMP(TO, X) ((X) < GET_MIN((TO)(X)) \
? GET_MIN((TO)(X)) \
: ((X) > GET_MAX((TO)(X)) ? GET_MAX((TO)(X)) : (TO)(X)))
您可以洗掉不必要的型別以使其更短。之后就這樣稱呼CLAMP(type, value)它
int main(void)
{
printf("%d\n", CLAMP(char, 1234));
printf("%d\n", CLAMP(char, 12));
printf("%d\n", CLAMP(char, -34));
printf("%d\n", CLAMP(char, -1234));
printf("%d\n", CLAMP(unsigned char, 1234));
printf("%d\n", CLAMP(unsigned char, 12));
printf("%d\n", CLAMP(unsigned char, -34));
printf("%d\n", CLAMP(unsigned char, -1234));
}
通過這種方式,您可以鉗制幾乎任何型別,包括浮點型別,或者_Bool如果您將更多型別添加到支持串列中。使用時注意字體寬度和符號問題
在 Godlbolt 上演示
您還可以使用 GNUtypeof或__auto_type擴展來使CLAMP宏更干凈、更安全
在舊 C 版本中執行此操作的另一種簡單方法是指定目標位寬
// Note: Won't work for (unsigned) long long and needs some additional changes
#define CLAMP_SIGN(DST_BITWIDTH, X) \
((X) < -(1LL << ((DST_BITWIDTH) - 1)) \
? -(1LL << ((DST_BITWIDTH) - 1)) \
: ((X) > ((1LL << ((DST_BITWIDTH) - 1)) - 1) \
? ((1LL << ((DST_BITWIDTH) - 1)) - 1) \
: (X)))
#define CLAMP_UNSIGN(DST_BITWIDTH, X) \
((X) < 0 ? 0 : \
((X) > ((1LL << (DST_BITWIDTH)) - 1) ? \
((1LL << (DST_BITWIDTH)) - 1) : (X)))
// DST_BITWIDTH < 0 for signed types, > 0 for unsigned types
#define CLAMP(DST_BITWIDTH, X) (DST_BITWIDTH) < 0 \
? CLAMP_SIGN(-(DST_BITWIDTH), (X)) \
: CLAMP_UNSIGN((DST_BITWIDTH), (X))
Beside the fact that it doesn't work for long long and unsigned long long without some changes, this also implies the use of 2's complements. You can call CLAMP with a negative bit width to indicate a signed type or call CLAMP_SIGN/CLAMP_UNSIGN direction
Another disadvantage is that it just clamps the values and doesn't cast to the expected type (but you can use typeof or __auto_type as above to return the correct type)
Demo on Godbolt
CLAMP_SIGN(8, 300)
CLAMP_SIGN(8, -300)
CLAMP_UNSIGN(8, 1234)
CLAMP_UNSIGN(8, -1234)
CLAMP(-8, 1234)
CLAMP(-8, -1234)
CLAMP(8, 12)
CLAMP(8, -34)
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/455491.html
上一篇:替代“空”值
