问题描述
我被这个难题困扰了几个小时。我在Google上寻找解决此问题的好方法,以了解其他人如何解决它,而我遇到了该解决方案。我试图弄清楚它是如何工作的。我对此唯一不了解的是四舍五入过程和丢失的位?丢了什么?
我非常感谢有人向我解释此解决方案。
预先感谢你们,优胜美地:)
/*
* ezThreeFourths - multiplies by 3/4 rounding toward 0,* Should exactly duplicate effect of C expression (x*3/4),* including overflow behavior.
* Examples: ezThreeFourths(11) = 8
* ezThreeFourths(-9) = -6
* ezThreeFourths(1073741824) = -268435456 (overflow)
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 12
* Rating: 3
*/
int ezThreeFourths(int x) {
/*
*sets y to make up for the lost bits while rounding. Multiplies
*by three by adding x 3 times. Records the sign since
* what is added will be different depending on whether the sign
*is positive or negative and adds this to to the number. Then
*divedes by 8 by shifting.
*/
int y = 3;
x =x+x+x;
int z = x>>31;
z = y & z;
x = z + x;
x = x>>2;
return x;
}
这是我从以下位置获得的链接:link该个人没有在文件中标记他的名字,所以我不知道他的名字
解决方法
在这里所做的所有工作都是为了匹配iso C定义除法向零取整的事实。乘以3/4的方法更简单,就是乘以3并乘以2:
return (x+x+x) >> 2;
不幸的是,它具有wrong behaviour表示负值(它将底数减小而不是四舍五入)。
要解决此问题,我们需要在负数项前添加3 * ,然后再将其移位2以通过截断模拟舍入。我们这样做如下:
int z = x >> 31; // Arithmetic right shift extracts sign mask (0xffffffff or 0x0)
int y = 3 & z; // 3 for negative values,0 for nonnegative
return (x + x + x + y) >> 2; // Multiply and divide with rounding
更正确地说,我们应该考虑到通过更改代码,我们实际上并不知道整数的大小为32:
int three_fourths(int x) {
return (x + x + x + (3 & (x >> (8 * sizeof(x) - 1)))) >> 2;
}
您可以在操作here中看到此最终实现。
* 这是将地板分隔线变成天花板分隔线的惯用法。如果您将x / N
向下舍入,则可以将表达式更改为(x + N - 1) / N
来向上舍入。对于N==4
,转换将为x / 4
-> (x + 3) / 4
未定义的行为
我们终于有了一些假设。当然,x + x + x
可能会溢出并产生未定义的行为,但这与x * 3
没什么不同(忽略问题作者的建议,即溢出具有相同的行为,它将在某些实现上得到保证,但这不能保证)。 / p>
实现定义的行为的最大部分是算术右移。这是C标准在此问题上必须说的(ISO/IEC 9899:TC3§6.5.7¶5):
E1 >> E2
的结果是E1
个右移E2
位的位置。如果E1
具有未签名的类型 或如果E1
具有带符号的类型和非负值,则结果的值是整数E1
/ 2 E2 的商的一部分。如果E1
具有带符号的类型和负值,则 结果值是实现定义的。
因此,仅当将任何负值右移小于int宽度的值时,才产生掩码值,掩码值提取仅在所有位位置中都产生一个带有一个值的值。在common implementation中,这适用于二进制补码系统(可能还有您将使用的任何系统),但是该标准在技术上不能保证。