【ICS】CS:APP3e Homework 2.97 关于整型转单精度浮点数的方法讨论

2024-03-01 20:38

本文主要是介绍【ICS】CS:APP3e Homework 2.97 关于整型转单精度浮点数的方法讨论,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

CS:APP3e Homework 2.97 关于整型转单精度浮点数的方法讨论

  • 关于整型转单精度浮点数的方法讨论
    • CS:APP原题
    • 题目分析
    • 实现代码
    • 原理解释
    • 其他方法
    • 启示与思考

关于整型转单精度浮点数的方法讨论

CS:APP即著名的计算机系统书籍《Computer Systems: A Programmer’s Perspective》(《深入理解计算机系统》),本篇博客基于其第三版第二章课后题2.97,讨论基于移位操作的整型转单精度浮点数底层实现,提供了笔者自己的原创代码以及网络上一些现有的实现代码。

CS:APP原题

2.97 遵循位级浮点编码规则, 实现具有如下原型的函数:
/*Compute (float)i */
float_bits float_i2f(int i);
对于整数i,这个函数计算(float) i 的位级表示。
测试你的函数,对参数f可以取的所有223个值求值,将结果与你使用机器的浮点运算得到的结果相比较。

题目分析

作为该章课后作业的最后一题,以及书中标注的四星难度,这个题还是需要花费一些时间才能做出来的。题目要求读者自己通过一些逻辑、位运算这样较为底层的操作实现C语言中从整型到单精度浮点型类型转换,而不能使用浮点数据类型、运算或者常数。其他限制这里不再列举,具体参照原书。
C语言中从整型至单精度浮点数的强制类型转换,并非像无符号数和补码转换位不变,直接映射,而是转换成对应的值。
所以,对于两者的转换,我们能够采用的方法就是根据IEEE754标准中浮点数的构成原则与整数补码的位关系找到规律,从而进行映射。
关于IEEE754标准及补码的二进制知识,本文不再赘述。

实现代码

这里首先给出笔者经过一段时间研究与测试得到的最终代码。

#include <stdio.h>typedef unsigned float_bits;/* Compute (float)i */
float_bits float_i2f(int i);
float u2f(unsigned x);int main()
{int i = 0;printf("If you want to stop, just type 0.\n");do{printf("Please input an integer number:");scanf("%d",&i);printf("%g\t%g\n",(float)i,u2f(float_i2f(i)));}while(i!=0);return 0;
}float_bits float_i2f(int i)
{if (i == 0)return 0;/*因为无符号数和浮点数均可用0x00000000表示0,所以当整数i为0时可直接返回*/unsigned s = i>>31<<31;/*通过左移和右移使除符号位外均为0,以便最后的或操作*/if ((s>>31) == 1)i = ~(i-1);/*若输入的整数为负数,只需要单独讨论其符号位。因为对于浮点数来讲相反数的其它位是相同的,所以我们先把其按照补码规则转化为其相反数(正数),然后就可以和正数同样处理,最后通过或操作可以保证其符号位*/unsigned m_bits = 0;//补码除去第一位后的有效位数(原因参照浮点数规格化数的表示)unsigned e;//阶码unsigned m = (unsigned)i;//未经处理的尾数unsigned isLow = 0;//确定尾数长短位置的Flagint j = 0;for (j = 0; j<32; j++){if (i>>(31-j)){m_bits = 32-j;break;}}/*确定补码的有效位数,通过循环移位,直到移位后结果不是0,从而确定其有效长度,忽略前面的0位。如果是负数,因为之前我们已经将其最高位通过转为为对应相反数,故不会因为最高位而影响有效位数。 */m_bits--;m = m<<(32-m_bits)>>(32-m_bits);/*根据规格化数构成方法排除第一位影响*/e = (m_bits+127)<<23;/*根据规格化数构成方法确定阶码*/if (m_bits <23)isLow = 1;if (isLow)m = m <<(23-m_bits);elsem = m >>(m_bits-23);/*对于单精度浮点数,符号位1位,阶码位8位,尾数位23位,以此作为参照选择左移右移,得到最终正确位置的尾数*/return s|e|m;/*通过或操作,合并符号位、阶码、尾数*/
}float u2f(unsigned x)
{return *(float*)&x;/*位不变的将无符号数转化为对应单精度浮点数*/
}

原理解释

其实这个题的原理,书中已经给出。
《深入理解计算机系统 第三版》P82
理解了这个,我们只需要把这个原理“翻译”成C语言代码。简单的说就是获取对应的尾数以及阶码,并注意一下对于正负的讨论。归根到底还是考察对IEEE754标准的理解。
具体的解释可以参照给出的代码注释。

其他方法

笔者在起初做题时尝试去参考网上一些方法,后来觉得尝试去理解其他人的方法不如自己试试能否实现。当然,笔者的代码中也有一些现成思路的影子。尽管还没有研究明白,但这里引用下网上其他的方法,供大家参考。
方法一:

#include <stdio.h>
#include <limits.h>typedef unsigned float_bits;float_bits float_i2f(int i);
unsigned bits_length(int x);
unsigned bits_mask(unsigned x);
float u2f(unsigned x);int main()
{int i = 123;printf("%f\t%f\n", (float)i, u2f(float_i2f(i)));i = -123;printf("%f\t%f\n", (float)i, u2f(float_i2f(i)));i = 0;printf("%f\t%f\n", (float)i, u2f(float_i2f(i)));i = (~0);printf("%f\t%f\n", (float)i, u2f(float_i2f(i)));i = (1 << 31);printf("%f\t%f\n", (float)i, u2f(float_i2f(i)));return 0;
}float_bits float_i2f(int i)
{unsigned sign, exp, frac, bias;bias = 127;if (i == 0) return 0;if (i == INT_MIN) { // -1sign = 1;exp = 31 + bias; frac = 0; // -1是整数,没有小数部分return sign << 31 | exp << 23 | frac;}sign = i > 0 ? 0 : 1;if (i < 0)i = -i;unsigned bits_num = bits_length(i);unsigned fbits_num = bits_num - 1;unsigned fbits;exp = bias + fbits_num;fbits = i & bits_mask(1 << fbits_num - 1);if (fbits_num <= 23)frac = fbits << (23 - fbits_num);else {unsigned offset = fbits_num - 23;frac = fbits >> offset;unsigned round_mid = 1 << (offset - 1);unsigned round_part = fbits & bits_mask(1 << offset - 1);if (round_part > round_mid)++frac;else if (round_part == round_mid) {if (frac & 0x1)++frac;}}return sign << 31 | exp << 23 | frac;
}unsigned bits_length(int x)
{unsigned ux = (unsigned) x;unsigned count = 0;while (ux > 0) {ux >>= 1;++count;} return count;
}unsigned bits_mask(unsigned x)
{x |= x >> 1;x |= x >> 2;x |= x >> 4;x |= x >> 8;x |= x >> 16;return x;
}float u2f(unsigned x)
{return *(float *)&x;
}

原文地址:https://www.cnblogs.com/chritran-dlay/p/9279184.html

方法二:

/** float-i2f.c*/
#include <stdio.h>
#include <assert.h>
#include <limits.h>
#include "float-i2f.h"/** Assume i > 0* calculate i's bit length** e.g.* 0x3 => 2* 0xFF => 8* 0x80 => 8*/
int bits_length(int i) {if ((i & INT_MIN) != 0) {return 32;}unsigned u = (unsigned)i;int length = 0;while (u >= (1<<length)) {length++;}return length;
}/** generate mask* 00000...(32-l) 11111....(l)** e.g.* 3  => 0x00000007* 16 => 0x0000FFFF*/
unsigned bits_mask(int l) {return (unsigned) -1 >> (32-l);
}/** Compute (float) i*/
float_bits float_i2f(int i) {unsigned sig, exp, frac, rest, exp_sig /* except sig */, round_part;unsigned bits, fbits;unsigned bias = 0x7F;if (i == 0) {sig = 0;exp = 0;frac = 0;return sig << 31 | exp << 23 | frac;}if (i == INT_MIN) {sig = 1;exp = bias + 31;frac = 0;return sig << 31 | exp << 23 | frac;}sig = 0;/* 2's complatation */if (i < 0) {sig = 1;i = -i;}bits = bits_length(i);fbits = bits - 1;exp = bias + fbits;rest = i & bits_mask(fbits);if (fbits <= 23) {frac = rest << (23 - fbits);exp_sig = exp << 23 | frac;} else {int offset = fbits - 23;int round_mid = 1 << (offset - 1);round_part = rest & bits_mask(offset);frac = rest >> offset;exp_sig = exp << 23 | frac;/* round to even */if (round_part < round_mid) {/* nothing */} else if (round_part > round_mid) {exp_sig += 1;} else {/* round_part == round_mid */if ((frac & 0x1) == 1) {/* round to even */exp_sig += 1;}}}return sig << 31 | exp_sig;
}

原文地址:https://dreamanddead.gitbooks.io/csapp-3e-solutions/chapter2/2.97.html

启示与思考

这段时间在学习计算机系统的相关知识,涉及到一些底层二进制实现原理,包括一些整数、浮点数编码与转换。本题就是一个典型的例子,涉及到许多相关知识。尽管笔者的代码很多地方写的还是比较幼稚,但是在一定程度上帮助笔者加深对于计算机底层编码实现的一些理解。
具体到这个题,我们要学会利用书中给出的原理,尝试去自己实现,并灵活运用各种位及逻辑操作,善作总结,这样有助于我们更好的理解书中内容。

这篇关于【ICS】CS:APP3e Homework 2.97 关于整型转单精度浮点数的方法讨论的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/763735

相关文章

Nginx设置连接超时并进行测试的方法步骤

《Nginx设置连接超时并进行测试的方法步骤》在高并发场景下,如果客户端与服务器的连接长时间未响应,会占用大量的系统资源,影响其他正常请求的处理效率,为了解决这个问题,可以通过设置Nginx的连接... 目录设置连接超时目的操作步骤测试连接超时测试方法:总结:设置连接超时目的设置客户端与服务器之间的连接

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

C#中读取XML文件的四种常用方法

《C#中读取XML文件的四种常用方法》Xml是Internet环境中跨平台的,依赖于内容的技术,是当前处理结构化文档信息的有力工具,下面我们就来看看C#中读取XML文件的方法都有哪些吧... 目录XML简介格式C#读取XML文件方法使用XmlDocument使用XmlTextReader/XmlTextWr

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

oracle DBMS_SQL.PARSE的使用方法和示例

《oracleDBMS_SQL.PARSE的使用方法和示例》DBMS_SQL是Oracle数据库中的一个强大包,用于动态构建和执行SQL语句,DBMS_SQL.PARSE过程解析SQL语句或PL/S... 目录语法示例注意事项DBMS_SQL 是 oracle 数据库中的一个强大包,它允许动态地构建和执行