具有范围限制的铸造类型
Cast type with range limit
有没有一种优雅的方法可以在不导致结果溢出的情况下将较大的数据类型转换为较小的数据类型?
例如将 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 编译器扩展)。
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)
....
仔细测试它,因为一些隐式转换可能潜伏并破坏特定类型对的宏。
如果你希望函数名称有不同的模式(像这样 I8
U32
)那么做与常量相同的技巧并定义相应的 typedef
哪个名称将包含所需的短类型的版本。
注意 OpenSSL 使用类似的方法为不同的类型提供相同的功能。
从 C11 开始,您可以使用新的 _Generic
selection 功能
#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, -1234));
printf("%d\n", CLAMP(int8_t, 12));
printf("%d\n", CLAMP(int8_t, -34));
printf("%d\n", CLAMP(unsigned char, 1234));
printf("%d\n", CLAMP(unsigned char, -1234));
printf("%d\n", CLAMP(uint8_t, 12));
printf("%d\n", CLAMP(uint8_t, -34));
}
通过这种方式,您几乎可以限制任何类型,包括 floating-point 类型或 _Bool
(如果您将更多类型添加到支持列表)。使用时注意字体宽度和符号问题
您还可以使用 GNU typeof
or __auto_type
extensions 使 CLAMP
宏更干净、更安全。这些扩展也适用于较旧的 C 版本,因此您可以在无法访问 C11
的情况下使用它们
在旧 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))
除了它不适用于 long long
和 unsigned long long
没有一些变化之外,这也意味着使用 2 的补码。可以用负位宽调用CLAMP
表示有符号类型或者调用CLAMP_SIGN
/CLAMP_UNSIGN
方向
另一个缺点是它只是限定值而不会转换为预期的类型(但您可以使用 typeof
或 __auto_type
如上所述 return 正确的类型)
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)
有没有一种优雅的方法可以在不导致结果溢出的情况下将较大的数据类型转换为较小的数据类型?
例如将 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 编译器扩展)。
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)
....
仔细测试它,因为一些隐式转换可能潜伏并破坏特定类型对的宏。
如果你希望函数名称有不同的模式(像这样 I8
U32
)那么做与常量相同的技巧并定义相应的 typedef
哪个名称将包含所需的短类型的版本。
注意 OpenSSL 使用类似的方法为不同的类型提供相同的功能。
从 C11 开始,您可以使用新的 _Generic
selection 功能
#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, -1234));
printf("%d\n", CLAMP(int8_t, 12));
printf("%d\n", CLAMP(int8_t, -34));
printf("%d\n", CLAMP(unsigned char, 1234));
printf("%d\n", CLAMP(unsigned char, -1234));
printf("%d\n", CLAMP(uint8_t, 12));
printf("%d\n", CLAMP(uint8_t, -34));
}
通过这种方式,您几乎可以限制任何类型,包括 floating-point 类型或 _Bool
(如果您将更多类型添加到支持列表)。使用时注意字体宽度和符号问题
您还可以使用 GNU typeof
or __auto_type
extensions 使 CLAMP
宏更干净、更安全。这些扩展也适用于较旧的 C 版本,因此您可以在无法访问 C11
在旧 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))
除了它不适用于 long long
和 unsigned long long
没有一些变化之外,这也意味着使用 2 的补码。可以用负位宽调用CLAMP
表示有符号类型或者调用CLAMP_SIGN
/CLAMP_UNSIGN
方向
另一个缺点是它只是限定值而不会转换为预期的类型(但您可以使用 typeof
或 __auto_type
如上所述 return 正确的类型)
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)