我在SO上看到了question关于转换的内容,答案中特别提到了数字类型。我想我到现在才理解这一点,当我必须这样做时。
我想添加两个int,除以2并保存到另一个int。所以我创建了一个名为'bucket'的临时变量,它是一个更大的数据类型,添加两个整数并向右移动。
我想出了几种方法来做到这一点,一切似乎都有效,但我认为有些方法是不必要的。请仔细看看,让我知道我误解了K& R。
情况1:
bucket = ( (long) ADCBUF0 + (long) ADCBUF7) >> 1;
rem_volt = bucket;
案例2:
bucket = ( (long) ADCBUF1 + (long) ADCBUF8) >> 1;
loc_volt = (unsigned int) bucket;
案例3:
bucket = (long) ADCBUF2 + (long) ADCBUF9;
current = (unsigned int) (bucket >> 1);
案例4:
bucket = ADCBUF3 + ADCBUFA;
dac_A = (unsigned int) (bucket >> 1);
案例5:
bucket = (long) (ADCBUF4 + ADCBUFB) >> 1;
dac_B = (unsigned int) bucket;
我认为案例4或案例5是正确的方法,因为案例1隐式地将一个长期转换为一个int,案例2我认为是正确的,但更多的打字,案例3我认为不成功地将整数投入到多头当只需要他们的总和时。
感谢您的想法。
编辑: 感谢您的评论到目前为止!为了澄清,我对无签名vs签名很草率,他们都应该是未签名的。我试图避免添加溢出。我的长度比我的int大2个字节。 (所以它真的比需要的要大得多,我可以检查溢出位。)我正在开发一个嵌入式平台,我永远不会移植它(我发誓!)但是我试图考虑任何人来到我身后并尝试弄清楚我正在做什么,所以我将使用除以2而不是比特移位。
答案 0 :(得分:4)
(你说你的变量是整数, a 和 b 是我在下面使用过的,我正在使用这个假设:int a, b;
。)
通过添加然后除以两个数字的平均值的问题是你可能会溢出(正如Laurence Gonsalves在他的评论中提到的那样)。如果您知道总和不会超过INT_MAX
,那么使用int
就可以了,不需要做任何事情:
int avg = (a + b) / 2;
如果总和可能溢出,那么向上移动到不会溢出的类型(例如long
)就可以了;但是,请记住,long
和int
可能大小相同(INT_MAX
可能等于LONG_MAX
),在这种情况下,除{{1} INT_MAX
外,这无济于事会更大 - 至少2,147,483,674。
int avg = ((long)a + b) / 2;
请注意,当 b 添加到 a 时, b 会自动转换为long
。您可能需要将最终结果转换为int
以避免警告(包括来自lint程序):
int avg = (int)(((long)a + b) / 2);
但请记住,整数限制是由实现定义的;真正令人担忧的是,加法是否可能超过INT_MAX
,而强制转换只是一种明确指定类型的方法,从而避免了无意的溢出。
当你想要除以2时,使用/ 2
,不要试图通过位移来获得聪明。编译器已经足够智能多年来优化这种事情,特别是当你使用常量时,代码清晰度更重要。
答案 1 :(得分:3)
这不是回答你提出的实际问题,但它可能有用,而且我喜欢笨拙的黑客......
可以使用一些技巧来计算两个无符号整数的平均值(向下舍入),而不会溢出或使用中间更宽的类型:
avg = (a & b) + ((a ^ b) >> 1)
为什么会起作用:对于二进制加法的每一列,如果该列中的位总和为2,则a & b
给出“1”位,(a ^ b) >> 1
给出“1”位右侧的列(您可以将其视为正在添加的列的1/2),其中该列中的位总和为1.
答案 2 :(得分:2)
你没有提供一些重要的细节,但在一些合理的假设下,4和5被打破,而1-3则相当不错。
我假设您坚持使用更大的中间积分类型,因为您希望避免添加溢出。您是否正在使用已签名的无符号类型以及为什么要将它们混合在一起也不清楚。
无论如何,执行添加和移位的最小方法是
bucket = ((long) ADCBUF0 + ADCBUF7) >> 1;
仅在一个操作数中切换到更大的类型就足够了。当然,如果你更喜欢更“对称”的外观,你可以保留两个演员阵容。
或者你可以在没有任何演员表的情况下完成(假设bucket
被声明为“更大”类型)
bucket = ADCBUF0;
bucket = (bucket + ADCBUF7) >> 1;
至于最终作业,如果收件人本身是unsigned int
(这在你的问题中完全不清楚),除非你试图压制编译器警告,否则不需要任何演员表。我认为它是unsigned int
。在那种情况下只是
rem_volt = bucket;
会奏效。如果它不是unsigned int
,那么显式演员可能有所作为,但没有办法说明,直到你提供这个重要的细节。
如果bucket
具有“更大”类型,则在您的案例中(在第一个或第二个语句中)进行转换并不重要。如果声明bucket
与ADC...
变量的类型相同,则必须提前完成转换(在第一个表达式中)。
至于你的变种:1是好的,但第二次演员在技术上是多余的(风格问题)。 2是可以的,有两次过度演员表演。 3也行,过度投射。 4被打破,因为它不能防止溢出(如果这是你的意图)。 5以相同的方式被打破。
换句话说,在所有变体中,数字1看起来最好(同样,假设转换为unsigned int
是多余的)。
P.S。如果你想将某些东西除以2,最合理的方法是使用除法运算符/
和常量2
作为除数。没有任何有意义的理由将任何转变带入图片中。
Matthew Slattery在他的回答中提供了一种非溢出的方法来计算使用比特操作的平均值。它也可以通过另一种(可以说是)更易读的方式完成
average = ADCBUF0 / 2 + ADCBUF7 / 2 + (ADCBUF0 % 2 & ADCBUF7 % 2);
或者,如果您更喜欢比特笨拙的方法
average = (ADCBUF0 >> 1) + (ADCBUF7 >> 1) + (ADCBUF0 & ADCBUF7 & 1);
答案 3 :(得分:1)
我会使用2或3.在第一行,您需要确保添加完成以防止溢出。在第二行,指定您正在进行向下转换可防止编译器警告。
4和5实际上在long
大于int
的平台上被破坏。
答案 4 :(得分:0)
这已在其他文章中说明,但可能没那么明确。回答你的问题:“我误解了K& R”:
我对OP的问题是你认为“一切似乎都有效”?我找不到一种方法来实现这一点(有意义的数据),即使你的假设和例子有一些插值。你有没有测试过有意义的数据?从变量的名称来看,您似乎只是将示例代码从应用程序中取出。这不是一个测试手头问题的好地方。当你有10个以上的变量时,很容易出错。在专用测试程序中测试不同的选项,尽可能减少变化。
另外还有其他评论,我会提到你可以使用
printf(“Sizes:int =%d long =%d \ n”, sizeof(int),sizeof(long)); // 注意 括号!
检查大小而不必了解limits.h或其他包含文件来定义平台。将此结果包含在您的原始帖子中会在某些人的脑海中清除一个问题,因为在许多当前的桌面平台上,'int'与'long'的大小相同(对于更大的单词大小使用'long long')