逆向学习记录001——80位浮点数表示法

写这么一篇文章的原因是因为对推导的80位浮点数表示法有一些疑问,而前面推导搜索的时间也花的比较多,因此将其记录下来,把这个疑问留待日后有机会再继续处理

起因是在做一个CrackMe练习时,该题目将一个整数转换为字符串又从字符串转换为浮点数,最后又从内存中读取几次单精度或双精度浮点数对其进行运算,而在之前只看过王爽那本《汇编语言》的我完全不知道浮点数相关的知识,浮点数表示法在学进制转换时简单了解过一点但也不甚了了,至于浮点数指令都是昨晚临时抱佛脚累积的一点点知识,这篇文章中讲到的浮点数状态码的知识帮助我理解了 fnstsw ax;test al,D;je xxx;所代表的含义是对浮点数运算的异常处理,而讲到浮点数表达形式的博客百科则让我明白从内存中读取的那几个浮点数并不是弹出异常窗口的罪魁祸首。在弄清楚这些之后我对其的探索按说应该是告一段落了,但是,从字符串转换为浮点数的那个函数返回值是存放在x87r7寄存器中,在前面的那两篇文章都只讲到了32位浮点数(单精度浮点数float)和64位浮点数(双精度浮点数double)用二进制编码的方式,而x87r7寄存器却是一个80位的寄存器,在疑惑之下,开始自己推导x87r7寄存器中浮点数表示的形式

最先,那个字符串转浮点数的返回值应该是一个双精度浮点数,因此我用双精度浮点数把转换后的结果1511180.0写了出来,即0x41370F0C00000000,0x413为指数位,按照前面两篇文章所述浮点数的指数位是用该无符号数字减去一个中间数来获得实际的正负指数,我个人的简化推算方式为,指数位最高位若是1,则直接将最高位(因为前面有符号位,所以指数最高位是由高到低数第二位)置0最低位加1求得最终的实际指数,0x413因此转换为0x014即20D,而后面的有效数字位则是1.70F0C0,小数点右移20D位,即5个16进制数位,刚好最终的结果为170F0C.0,即1511180.0

在写出双精度浮点数表达的转换结果后,再看x87r7寄存器中的数值,即0x4013B878600000000000,因为有效数字位是从高到低生效的,所以0x4013B87860中必然隐藏着1511180.0的二进制表达形式即1.70F0C0,而浮点数总是将最前面的1隐式表达,因此0x4013B87860中包含70F0C0的二进制形式,可以很明显的注意到,C0>>1 == 60,因此将70F0C0>>1 == 387860,匹配B87860中除了最高位的全部位数,由此推断得出结论,指数位范围是0x4013及后面的一位

现在继续推论指数位的表达形式,0x40138去除符号位即0x40138<<1 == 0x80270,因此指数值为0x8027,而减去中间值为最高位置0最低位加1,因此最终计算的指数值为0x0028,这个却与前面用双精度浮点数算得的0x014不同了,将其右移一位才是实际使用的最终指数,这个地方是目前所困惑的地方

带着这样的疑问去搜索了“FPU 80位浮点数 表示”关键字,意外的找到了一篇不错的帖子,其中所讲述的80位浮点数表达形式的位数区间刚好与我所推导出来的区间相同,而80位浮点数在其中被称为“拓展精度”,可惜其中并没有讲到具体的转换方式,只是简略的写了一下“可表示的大致范围是2^-16382~2^16383”,而16位无符号指数最大位65535,减去中间值也应该是-32767~32768,右移一位的确是被实际使用的最终指数所进行的运算步骤,不过该帖子中我所看的那些部分并没有提到对这一点的疑问

在此,就将这个问题放在这儿抛砖引玉了,为什么80位浮点数(拓展精度浮点数)的指数位减去中间值后需要右移一位才是最终实际使用的指数呢?

(附注:浮点数的二进制形式由 “符号位”-“指数位”-“有效数字位”组成
符号位长度固定为1表示正负数
指数位在单精度长度为8,双精度长度为11,拓展精度长度为16,指数位是以“无符号整数减去中间值得到最终有符号的实际指数”的方式存储的,中间值一般为最大指数整除2(去除小数),例如8位的指数表示范围为0~255,因此中间值为255/2=127。比较特殊的是拓展精度浮点数的指数位,需要再额外右移一位才能求得最终的实际指数
有效数字位的位数为“总位数-指数位位数-符号位位数”,因为非零的浮点数用科学计数法表示的有效数字总是以1.x开头,因此有效数字并不包括最前面的1,只包括小数点后面的数位,而最终该浮点数所表达的值则将该小数点右移指数位,然后分别计算整数部分与小数部分数值并相加

发表评论