csapp-homework-2.75(1)
在做题目2.75的时候,发现课后习题是跟课文内容强关联的。所以我觉得后期优化csapp学习的方法就是,先快速浏览一遍内容,尽量按照习题去过基础内容。
2.75题目是关于补码和无符号整数的乘法运算,这里翻看课本重新回顾下之前的无符号和补码的乘法运算。
比较重要的是:补码的乘法结果 = 无符号数的乘法结果取模后再转换为补码形式的结果。
发现不会做,直接看答案:
/*
* unsigned-high-prod.c
*/
#include <stdio.h>
#include <assert.h>
#include <inttypes.h>
int signed_high_prod(int x, int y) {
int64_t mul = (int64_t) x * y;
return mul >> 32;
}
unsigned unsigned_high_prod(unsigned x, unsigned y) {
/* TODO calculations */
int sig_x = x >> 31;
int sig_y = y >> 31;
int signed_prod = signed_high_prod(x, y);
return signed_prod + x * sig_y + y * sig_x;
}
/* a theorically correct version to test unsigned_high_prod func */
unsigned another_unsigned_high_prod(unsigned x, unsigned y) {
uint64_t mul = (uint64_t) x * y;
return mul >> 32;
}
int main(int argc, char* argv[]) {
unsigned x = 0x12345678;
unsigned y = 0xFFFFFFFF;
assert(another_unsigned_high_prod(x, y) == unsigned_high_prod(x, y));
return 0;
}
看代码的时候,发现有人评论:
can I ask one question?
whyint64_t mul = (int64_t) x * y;
must here explicit cast then implicit cast?(I thought I have seen related contents in csapp, but search "explicit" found nothing related)
额,为什么在x * y的时候强制类型转换为64位的。这里蕴含着两个问题:
- 这个强制类型转换做了什么操作
- 为什么要做这个操作
其实我现在知道的是x和y的补码乘积会溢出(例如x = INT_MIN、y = INT_MIN时,x*y的结果会超出32位的范围),溢出后计算机会自动将其高32位去除,保留低32位。这样也等效于结果取2的32次方的模。
所以需要将其计算结果转为64位,然后才能保存乘法运算中溢出的高位数据。
但是这个(int64_t) x*y
,是先将x和y转为64位后(等价于(int64_t) x * (int64_t) y
)再运算呢还是将计算结果(等价于(int64_t)x*y
)转为64位呢?我不了解,因为我不清楚乘法运算符和类型转换符号的先后顺序,以及能否在得到结果后再强制类型转换。
那就首先解决这个类型转换和乘除法的优先级问题。
在菜鸟教程C 强制类型转换中,示例代码非常贴合现在我们遇到的问题:
#include <stdio.h>
int main()
{
int sum = 17, count = 5;
double mean;
mean = (double) sum / count;
printf("Value of mean : %f\n", mean );
}
下面对这个代码的解释也很到位:
这里要注意的是强制类型转换运算符的优先级大于除法,因此 sum 的值首先被转换为 double 型,然后除以 count,得到一个类型为 double 的值。
关于强制类型转换和乘除法的运算优先级,查询chat-gpt:
由于
x
被强制类型转换为int64_t
,整个表达式会采用更宽的整数类型(int64_t
)进行计算,因此y
会隐式地升级到int64_t
,避免了整数溢出的问题。
其实这样就解决了这个问题的两个疑惑:
-
这个强制类型转换做了什么操作?
答:将参数
x
的类型转换为int64_t
。 -
为什么要做这个操作?
答:想让x和y的乘积不溢出。这需要让结果类型为64位,这样就需要将两个参数进行升级。
但是现在可以利用C语言的类型升级(涉及两种类型的运算,两个值会被分别转换成两种类型的更高级别)来简化代码书写规则:只需要将其中一个参数x升级为64位的类型,另一个低级类型参数y在参与运算的过程中会被自动转换为64位的数据。
这样两个64位的数据得到的结果是64位的数据,正好可以保存32位int类的乘法溢出的高位数据,解决了溢出问题。
(存量数据,写于2024年2月4号)