【计组笔记-5】详细测试c++中类型转换的溢出截断处理
注:
- 测试环境是vs2019。
- 会尽量把能想到的情况都测试&分析一下,有想不到的还望批评指正。
1. 将小数赋值给float
明天写(记得解释精度丢失)
先跳过吧时间太赶了…我先写点别的,其他几篇笔记已经把原理讲完了,这篇只是测试,过几天再写()。
2. 将整数赋值给float
3. 将整数赋值给int
3.1 算术溢出
如果通过算数运算给变量赋的值大于数据类型能容纳的最大值,则会警告算术溢出,但不会报错,如下所示:
int i = 0x7fffffff+1;
来分析一下,已知有符号int为32位,其中1位是符号位,31位是数值位,因此,理论上int可储存的最大值为231-1,用16进制表示为0x7fffffff,而理论上最小的溢出值为0x7fffffff+1,也就是i。
溢出之后,i储存了什么内容呢?打印出来看一下:
翻译成储存的二进制位是1000 0000 0000 0000 0000 0000 0000 0000(理论上是-0,由于不需要存-0,因此这个状态被拿来存-231了,笔记-3中有写),可以发现,如果将其视为无符号数,则刚好是0x7fffffff+1的值。
可见,在这个赋值过程中,编译器是截断32位赋值给左侧int型变量的,并不会因为int有一位是符号位就只截断31还给补足符号位再顺便转换成补码, 而如果只赋值0x7fffffff呢:
int i1 = 0x7fffffff;
int i2 = -0x7fffffff;
可见,只要右侧的数值只需要低于32位的内容来储存,最高位就可以正确的储存符号位,且储存的也是正确的补码(补码后面的笔记再写,这里不多赘述)。
3.2 非算术溢出(等于32位)
上一小节分析了算术溢出,那么如果是非算术溢出,即直接赋一个超过最大值的数呢?如下所示:
int i1 = 0xffffffff;
int i2 = 0x80000000;
注意,由于右侧的值并未超过32位,因此这时候是不会报任何错或警告的!此外,打印出来的i1和i2,分别翻译成储存的二进制内容,如下所示:
- i1:1111 1111 1111 1111 1111 1111 1111 1111
- i2:1000 0000 0000 0000 0000 0000 0000 0000
可见,如果右侧赋值的数大于int能储存的最大值,则这个储存过程是截断32位直接存给int变量指示的那块内存,而并没有添加符号位,再修改为补码这个过程。然而,这个问题是不会报错的,连警告也无!所以还是要自己注意下别超了。
3.3 非算术溢出(大于32位)
上一小节分析了赋的值用32位装得下的问题,那么,如果大于32位,赋值给int会如何截断呢?如下所示:
int i1 = 0x100000000;
int i2 = 0x100000001;
int i3 = 0x180000003;
根据打印的值,i1,i2,i3实际储存的二进制位如下:
- i1:0000 0000 0000 0000 0000 0000 0000 0000(0x00000000)
- i2:0000 0000 0000 0000 0000 0000 0000 0001(0x00000001)
- i3:1000 0000 0000 0000 0000 0000 0000 0011(0x80000003)
如果观察一下,就可以发现,它们刚好就是从低位开始截取了所赋值的32位,除此之外并没有其他改变。
此外,跟上一小节不同的是,在赋的值需要大于32位来储存时,会报以下警告:
可见确实是截断操作,此外还可以获得一个信息,这里右侧的数字是用64位int来存的(不知是否与操作系统是64位有关),在小数里也有这个现象(在小数的小节里分析)。
4. 将小数赋值给int
测试如下赋值方式:
int i1 = 0.9;
int i2 = 0x80000000 + 0.9;
int i3 = 0x100000000 + 0.9;
int i4 = 0x180000003 + 0.9 * 2;
int i5 = 0x180000003 + 0.9 + 1;
int i6 = -0x7fffffff + 0.9;
int i7 = -0x7fffffff - 0.9;
它们报的警告也都是一样的,如下:
可见,虽然i2,i3,i4与i5的赋值过程中出现了第3部分中分析过的问题,如算术溢出,但如果涉及到了类型转换,则只会提示类型转换的警告。
根据打印的值,i1,i2,i3实际储存的二进制位如下:
- i1:0000 0000 0000 0000 0000 0000 0000 0000(0x00000000)
- i2:1000 0000 0000 0000 0000 0000 0000 0000(0x80000000)
- i3:0000 0000 0000 0000 0000 0000 0000 0000(0x00000000)
- i4:1000 0000 0000 0000 0000 0000 0000 0100(0x80000004)
- i5:1000 0000 0000 0000 0000 0000 0000 0100(0x80000004)
- i6:1000 0000 0000 0000 0000 0000 0000 0010(0x80000002)
- i7:1000 0000 0000 0000 0000 0000 0000 0001(0x80000001)
分析打印结果可知,将小数赋值给整数,计算后的小数点部分会被抛弃,并且,这个抛弃是发生在全部计算完之后的,可以理解为右侧的表达式计算得出了类型为double的结果,然后再将这个double类型的数赋值给int。
此外,在抛弃掉小数点后的内容之后,剩余整数部分的处理与上一节是一样的,大于等于32位则直接截断(如i2,i3,i4,i5),小于32位就按照正常流程转为补码储存(i6,i7)。(然而,这里并不会提示进行了截断)
5. int和short互相转换
6. double和float互相转换
7. 总结
- 敲代码的时候要更加注意溢出这个问题,有时溢出了连警告都没有。
- c++好歹还给了明确的数据类型,js这种解释型语言只有number,估计问题更大,有机会要好好测试一波。
- 本篇只来及测试了部分类型和部分转换情况,后续时间充裕会慢慢补全。