类型强转和地址强转

1 例子

最近在干一个很复杂的事—读4w多行的项目源码,头都大了有木有!其中有一步是加载一个二进制文件,为了更好的理解代码的含义,我需要一点一点解析二进制文件。在解析到某个位置的时候,有个读浮点数的操作,对应的二进制值为:…CAF249F1…。非常好奇这个值对应的浮点数是多少,所以写代码去求解一下。这里我没有犯一个错误:原始的文件中是按照从低字节到高字节排序的,所以实际的数应该为:0xF149F2CA。但是还是犯了很多错误,最初的代码为:

void test1()
{
    unsigned int a=0xf149f2ca;
    float b=a;

    printf("a is %u,b is %f\n",a,b);
}

想法是通过隐式类型强转获得对应的浮点数,但输出结果为:a is 4048155338,b is 4048155392.000000,完全不是想象中的样子。隐式的类型强转只是将一个int型的数变成一个和原值接近的浮点数,而不是将原始的int值按照地址解析成浮点数。后来一想也对,要不这样强转还有什么意义。注意强转的结果并不和原值一样,这是由float类型的精度造成的,后面我们会详细介绍其中的原理。

上述方法不行,我又想到利用字符串进行转换,代码如下:

void test2()
{
    char* a="caf249f1";
    float b=(float)atof(a);

    printf("a is %s,b is %f\n",a,b);
}

想法也很简单,将连续四个字节利用atof函数解析成浮点数,输出结果为:a is caf249f1,b is 0.000000,再一次不正确!后来仔细查看atof的解释才明白,原来atof函数只能解析符合浮点数书写格式的数,例如0.0314或者3.14E-2。如果字符串完全无法被转换为数字,则返回0。

连续两次都没有解决,我怒了!想了一种操作地址的方式,代码如下:

void test3()
{
    unsigned int a=0xf149f2ca;
    float b=*(float*)&a;

    printf("a is %u,b is %f\n",a,b);
}

代码的思想是首先获得int型的地址,然后将该地址强转成float型地址,然后再获得float的值,这样应该不会错了,输出的结果为:a is 4048155338,b is -1000000015047466219876688855040.000000。这个结果可能不太直观,但是我知道这就是我想要的结果。b输出了一个很大的负数,如果将其转换成科学记数法则为-1E30,正好在代码中有个LOG_ZERO的宏定义为该值,正好对应起来了。该浮点数的含义是对某个变量赋初值,初值为log(0)的近似定义。

2 类型强转

通过上面的例子我们能有什么收获呢?在第一次尝试中,我们虽然没有得到正确的结果,但是却发现了一些问题:将一个int型的值转换成float类型之后,值变得和转换之前不一样,精度损失了不少,为什么会这样?要理解造成差异的原因,需要对浮点数的格式非常了解。在计算机中,一个浮点数(真实值)应该用下述形式来表示:

其中,s决定这个数是负数(s=1)还是正数(s=0);M表示尾数,是一个二进制小数,范围是或者;e是阶码,作用是对浮点数加权,权重是2的e次幂。所以在将一个整数表示成浮点数的时候,我们需要先将整数表示成以2为底的科学计数型,然后按照下面的格式填入相应值即可。

符号 阶码 尾数

如果是单精度(float)类型,上述格式的符号部分为1位,阶码部分为8位,尾数部分为23位;如果是双精度(double)类型,符号部分为1位,阶码部分为11位,尾数部分为52位。

根据阶码的不同,被编码的值可以分为三种情况(最后一种情况有两个变种)。此外,要注意下面的表是IEEE的浮点数表示,而不是原始浮点数的表示。

1. 规格化的

对上述IEEE浮点数进行实际值转换的时候,阶码和尾数都有变化。IEEE标准中的阶码和我们想象中稍有不同,因为它对我们理解中的阶码进行一个偏置操作。针对规格化的浮点数,阶码字段被解释为以偏置形式表示的有符号整数。也就是说,浮点数真实的阶码值e=E-Bias,其中E就是IEEE给出的阶码值,Bias是一个等于(单精度是127,双精度是1023)的偏置值。尾数和我们想象中也稍有不同,我们在表示一个浮点数的尾数时,总会表示成1.形式,由于第1位总是1,我们可以将其省略,从而获得一个额外的精度位,因而M真实值是1.,但表现出来却是0.

2. 非规格化的

在这种情况下,真实值阶码值e=1-Bias,而不是-Bias。尾数则不包含隐含的1,即M=0.

非规格化数有两个用途。首先,它提供了一种表示数值0的方法,因为使用规格化数,我们必须总是使>=1,因此我们就不能表示0。当阶码和尾数都为0时就表示0,不过符号位的不同会产生+0.0与-0.0。其次,非规格化可以表示非常接近于0.0的数。

3a.无穷大

3b.NaN

在《深入理解计算机系统》P72有一个很详细的例子,建议大家仔细阅读。

讲了这么多,我们现在应该明白为什么整数强转成浮点数之后会有精度损失。这是因为float在计算机表示中尾数(float的有效位)只有23位,而int整数有32位,这就导致在将int转换成float的时候,int的低位就会被truncate,最大可能会产生511的误差。

当然并不是所有的强转都会导致精度损失,这也和整数值的有效位数相关,具体指的是前置1和后置1之间的部分。下面给出一个例子:

void test()
{
    int a=0x100010;
    int b=0x8000008;

    printf("a is %d, type cast of a  is %f, b is %d, type cast of b is %f\n",a,(float)a,b,(float)b);
}

上述代码的输出为:a is 1048592, type cast of a  is 1048592.000000, b is 134217736, type castof b is 134217728.000000。可以看到a在强转的时候没有精度损失,但是b在强转时产生了精度损失。这是因为a的有效位数小于23位,在强转时float的尾数可以容纳;但是b的前置和后置1之间的位数为23位,这样在强转的时候最低位的1就会被truncate,从而结果产生了8的误差。

上述误差的产生是由尾数位数过少造成的,针对float类型的阶码部分,我们能有什么收获呢?利用类型强转将int转成float之后,阶码部分就保存了这个整数的最高位次,因而我们可以用阶码来求整数的前置1位置,我会在后面的博客中详细介绍整数前置1的位置求法。

3 地址强转

地址强转算是一项比较高级的操作,直接将原始地址的类型进行改变。语法也比较复杂,首先通过&符号获得原类型的地址,然后通过(type*)将地址类型进行强转,最好再利用*取值获得新类型的值。这种强转在大多数情况下都没有意义,但是一种情况例外,即在将float的地址强转成int地址时会有特殊意义。

由前面的介绍我们知道浮点数在计算机中是通过符号位、阶码和尾数三部分来表示的。给定一个数学上的浮点数x,我们可以将其表示成

时间: 2024-08-26 21:49:13

类型强转和地址强转的相关文章

验证-- email类型输入框(电子邮件地址)--multiple

如果需要一个用来填写电子邮件地址的输入框,可以使用email类型.这样浏览器可以帮我们验证格式是否正确,而不需要自己写验证规则.原文:HTML5新控件 - email类型输入框(电子邮件地址) 1,只允许输入单个邮件地址1<input type="email"/> 2,允许输入多个邮件地址(用逗号分隔)1<input type="email" multiple/>

Mysql查询数字类型结果为Object,强转int报错

问题描述: SQL文:SELECT COUNT(1) NUM FROM test WHERE 1=1 Java代码: 图中强转int失败 问题原因: dal层查询出来的数字是Long型的,所以强转int会失败 解决办法: 将查询出来的Object强转为long型即可

you-get 一个很强的视频地址抓取工具

来源:https://github.com/soimort/you-get 出自于 Mort Yao 大神之手:http://www.geekgrade.com/geeksheet/soimort/blogs 前言(废话): 前段时间,我因公司需求.所以要去研究如何拿到视屏的播放地址.一般普通的网站的视屏播放地址还是很好拿到的.但是对于优酷,腾讯这样的大型视屏资源站就很难拿到视屏资源了.普通的网站你通过网页就可以直接抓取到视屏的播放地址.但是这些大型就不行,这些网站都是做了防盗链的.就拿优酷来讲

2017年世界500强榜单,500强亏损公司,强最赚钱的50家公司

2017年世界500强榜单发布:腾讯阿里首次登榜 2017年07月20日 20:03:51 财富中文网于北京时间2017年7月20日晚与全球同步发布了最新的<财富>世界500强排行榜. 沃尔玛连续四年排名第一位,2016年营业收入达4,858.7亿美元,同比提升0.8%.前三阵营中的其它两家为中国公司--国家电网和中石化.中石油和丰田汽车分列第四和第五.唯一新进入前十阵营的是沃伦巴菲特掌管的保险和投资集团伯克希尔-哈撒韦公司.如今伯克希尔收入中近四分之三来自经营业务而非财务投资,在挣脱巴菲特光

送给你运行Python的六大效率神器!简直强的不能再强了!

对于CPU密集型的程序,可以使用multiprocessing的Process,Pool等封装好的类,通过多进程的方式实现并行计算.但是因为进程中的通信成本比较大,对于进程之间需要大量数据交互的程序效率未必有大的提高. 4. 针对循环的优化 每种编程语言都会强调需要优化循环.当使用Python的时候,你可以依靠大量的技巧使得循环运行得更快.然而,开发者经常漏掉的一个方法是:避免在一个循环中使用点操作.例如,考虑下面的代码: 每一次你调用方法str.upper,Python都会求该方法的值.然而,

23.实现一个名为Person的类和它的子类Employee,Employee有两个子类Faculty 和Staff。 具体要求如下: (1)Person类中的属性有:姓名name(String类型),地址address(String类型), 电话号码telphone(String类型)和电子邮件地址email(String类型); (2)Employee类中的属性有:办公室office(Stri

package banking; public class Person { private String name; public String address; public String telphone; public String email; } package banking; public class Employee extends Person{ private String office; private double wage; private String hireda

扫描同一个二维码根据浏览器类型访问不同下载地址

1 <?php 2 3 $Agent = $_SERVER['HTTP_USER_AGENT']; 4 preg_match('/android|iphone/i',$Agent,$matches); 5 if (strtolower($matches[0]) == 'android') { 6 // echo "安卓"; 7 header("Location: ".$GLOBALS["public_appconfig"]["ap

QF——关于iOS的强引用,弱引用及strong,retain,copy,weak,assignd的关系

强引用和弱引用: 我们已经知道OC中的内存管理是通过“引用计数器”来实现的.一个对象的生命周期取决于它是否还被其他对象引用(是否retainCount=0).但在有些情况下,我们并不希望对象的销毁时间由是否被其他对象引用来决定,而是这个对象本该是什么时候销毁就什么时候被销毁.这时,我们得引入“强引用”和“弱引用”的概念. 强引用:当前对象被其他对象引用时,会执行retain操作,引用计数器+1.当retainCount=0时,该对象才会被销毁.因为我们要进行对象的内存管理,所以这是默认的引用方式

[转载]谭浩强育人

原文地址:http://www.liuren.com/liuren/hero/chenxuyuan/9.htm 谭浩强育人 谭浩强,著名计算机教育专家. 1934年生,广东台山人.1958年清华大学自动控制系毕业. 现任全国高等院校计算机基础教育研究会理事长.教育部全国计算机应用技术证书考试 委员会主任委员.教育部全国计算机等级考试委员会副主任.中国计算机学会教育委员会副 主任等多项职务. 处女作<BASIC语言>一书发行量超过1200万册,获国家科技进步奖.在中央电视台主讲的7种计算机语