C 如何";混合;两个值没有溢出?
考虑以下功能:C 如何";混合;两个值没有溢出?,c,algorithm,integer-overflow,C,Algorithm,Integer Overflow,考虑以下功能: // Return a blended value of x and y: // blend(100, 200, 1, 1) -> 150 // blend(100, 200, 2, 1) -> 133 uint8_t blend(uint8_t x, uint8_t y, uint8_t parts_x, uint8_t parts_y) { uint32_t big_parts_x = parts_x; uint32_t big_parts_y =
// Return a blended value of x and y:
// blend(100, 200, 1, 1) -> 150
// blend(100, 200, 2, 1) -> 133
uint8_t blend(uint8_t x, uint8_t y, uint8_t parts_x, uint8_t parts_y) {
uint32_t big_parts_x = parts_x;
uint32_t big_parts_y = parts_y;
return (uint8_t) ((big_parts_x * x + big_parts_y * y) /
(big_parts_x + big_parts_y));
}
有没有一种方法可以在不需要任何大于uint8\t
的分配的情况下接近适当的返回值?通过执行两个除法,您可以轻松地将其分解(较少舍入)为两个加法。你能只用uint8\t
来做吗
是否有一种方法可以在不需要任何大于uint8\t的分配的情况下接近适当的返回值
理论上,是的:
uint8_t blend(uint8_t x, uint8_t y, uint8_t parts_x, uint8_t parts_y) {
return lookup_table[x][y][parts_x][parts_y];
}
实际上,查找表需要花费4gib的内存,所以这可能不是一个好主意
除此之外,它还取决于“关闭”的含义(“可接受的最坏情况错误”有多大)以及有效的值范围(尤其是对于零件x
和零件y
)
例如(如果零件x
和零件y
的范围仅为1到15):
当然,使用比
uint8\t
更大的东西要容易得多,也要快得多,因此……下面的内容将在不进行任何额外分配的情况下合并。即使
int/unsigned
为16位也可以工作
return (uint8_t) ((1u*parts_x*x + 1u*parts_y*y) / (0u + parts_x + parts_y));
符合标准的C实现保证执行至少16位的算术运算 第6.3.1.1p2节规定: 如果
int
或无符号,则可以在表达式中使用以下内容
int
可用于:
- 整数转换秩小于的整数类型(除
或int
外)的对象或表达式 或等于无符号int
和int
的秩无符号int
- 类型的位字段
,\u Bool
,int
或signed int
unsigned int
int
可以表示原始类型的所有值(如
受宽度限制,对于位字段),值为
转换为int
;否则,它将转换为无符号的
int
。这些被称为整数促销。所有其他
整型促销不会改变类型
第E.1节还规定,int
必须能够支持至少在-32767到32767范围内的值,无符号int
必须支持至少在0到65535范围内的值
由于uint8\u t
的秩低于int
,因此在大多数操作员使用+
、-
、*
和/
时,前者总是会被提升为后者
有鉴于此,您可以通过以下细微修改安全地计算该值:
uint8_t blend(uint8_t x, uint8_t y, uint8_t parts_x, uint8_t parts_y) {
return ((1u*parts_x*x) / (parts_x + parts_y)) + ((1u*parts_y*y) / (parts_x + parts_y));
}
表达式parts_x*x
和parts_y*y
的最大值为65025。这对于16位int
而不是16位无符号int
来说太大了,因此每个值都乘以1u
,以强制按照第6.3.1.8节规定的常规算术转换将值转换为无符号int
对两个操作数执行整数提升。然后
以下规则应用于提升的操作数:
- 如果两个操作数的类型相同,则无需进一步转换
- 否则,如果两个操作数都具有有符号整数类型或都具有无符号整数类型,则类型为较小的操作数 整数转换秩转换为操作数的类型 具有更高的等级
- 否则,如果具有无符号整数类型的操作数的秩大于或等于 其他操作数,则有符号整数类型的操作数为 已转换为具有无符号整数的操作数类型 类型。
uint8\t
的范围内。然后我们可以添加这两个部分,这两个部分将再次位于auint8\t
的范围内
因此,上述表达式保证在符合C标准的编译器上返回正确的精确答案。在完成数学运算之前,所有操作数都将提升为
int
,如果int
至少为4个字节,您的计算将不会溢出,因此我不会担心。不幸的是,对于我的目标体系结构,我只有8位的操作,这就是问题所在。我应该将其表述为“不需要对uint8_t以外的类型进行任何操作”。“对我的目标体系结构进行8位操作”并没有将C代码限制为仅8位整数数学。经典示例:第一台PC使用8位处理器,缺少16位或更宽的数学运算。@Brendan代码不使用int
,而是无符号
。OP特别是指“需要任何拨款”。这里没有这样的中间对象。任何数学运算都会将操作数提升为无符号/int
,如下所示为无符号
。因此,除非OP不想使用任何操作数,+、-、*、/、%、&、^、|、| |、&&&、[]
等,否则转换为无符号/int
必须是答案的一部分。您的代码使用的是无符号int
,它肯定大于8位。我会假设(为了让问题变得有意义),代码是为某种8位微控制器编译的,这种微控制器除了8位整数外不支持任何东西。@Brendan不支持语义。C指定使用至少16位数学(通常是整数编程的一部分)进行2uint8_t
的加法(减法、加法等),如x+y
return (uint8_t) ((1u*parts_x*x + 1u*parts_y*y) / (0u + parts_x + parts_y));
uint8_t blend(uint8_t x, uint8_t y, uint8_t parts_x, uint8_t parts_y) {
return ((1u*parts_x*x) / (parts_x + parts_y)) + ((1u*parts_y*y) / (parts_x + parts_y));
}