CSAPP Data Lab

news2024/9/21 14:36:13

CSAPP 的第一个 Lab,对应知识点为书中的第 2 章(信息的表示与处理),要求使用受限制的运算符和表达式实现一些位操作。主要分为两个部分:整数部分和浮点数部分。其中整数部分限制较多,比较偏重技巧性,部分题个人认为很有难度。而浮点数部分则比较基础,主要考察对 IEEE 754 标准的熟悉程度,代码较长,但思路相对简单。

bitXor

思路

使用德-摩根定律进行推导,推导过程如下:

请添加图片描述

代码

int bitXor(int x, int y) {
    // 德-摩根定律
    return ~(~(x & ~y) & ~(~x & y));
}

tmin

思路

最小整数即最高位(负数权重)为 1,其余(正数权重)为 0。

代码

int tmin(void) {
    return 1 << 31;
}

isTmax

思路

由于不能使用左移运算符,因此没办法直接构造出 tmax,需要仔细考虑 tmax 的性质:tmax = 0x7fffffff ,而 tmax + 1 = 0x80000000 ,这两个数的二进制位完全互补,因此满足:tmax + tmax + 1 = 0xffffffff,结果全为 1,对该结果取反即可得到 0,取非得到 1。

但这里还要考虑一个特殊情况:当 x = 0xffffffff 时,x + 1 + x 也满足等于 0xffffffff,因此需要借助异或运算进行特判。

代码

int isTmax(int x) {
    int case1 = !((x + 1) ^ 0);  // case = x == 0xffffffff ? 1 : 0;
    return !(~(x + 1 + x)) & !case1;
}

allOddBits

思路

首先构造一个掩码 mask,奇数位全为 1,偶数位全为 0。将 mask 与 x 进行按位与,如果 x 的奇数位全为 1,那么按位与的结果仍然为 mask。然后便可以借助异或和非的组合,将结果转换为 0 或 1。

代码

int allOddBits(int x) {
    int mask = 0xaa;            // mask = 0x000000aa
    mask = (mask << 8) | 0xaa;  // mask = 0x0000aaaa
    mask = (mask << 8) | 0xaa;  // mask = 0x00aaaaaa
    mask = (mask << 8) | 0xaa;  // mask = 0xaaaaaaaa
    return !((x & mask) ^ mask);
}

negate

思路

补码表示法的重要特性,取反加一即可。

代码

int negate(int x) {
    return ~x + 1;
}

isAsciiDigit

思路

这里我用了比较笨的逐位判断的方法。首先判断第 4 到第 31 位是否为 0x3,然后只需要关注低 4 位的二进制表示了:若第 3 位为 0,则一定位于指定范围之内,再加上两个特例(1000 和 1001)即可。

最后将运算符的个数刚好卡在 15 个,勉强过关。

代码

int isAsciiDigit(int x) {
    // 0011 0000 <= x <= 0011 1001
    int high = !((x >> 4) ^ 0x3);         // 4 ~ 31 位是否为 0x3
    int case1 = ((x & 0xf) >> 3) ^ 0x1;   // bit3 = 0
    int case2 = !((x & 0xf) ^ 0x8);       // bit0~3 = 1000
    int case3 = !((x & 0xf) ^ 0x9);       // bit0~3 = 1001
    return high & (case1 | case2 | case3);
}

conditional

思路

很容易想到根据 x 的值是否非 0 构造出全 0 或者全 1 的数据 flag,然后将 flag 和 flag 取反后的值分别与 y 和 z 进行按位与,这样必然得到两个数:一个为 y 或 z 本身,另一个为 0,再将结果按位或即可。

构造的方法比较巧妙,需要注意到全 0 和全 1 分别代表整数 0 和 -1,它们分别是 0 和 1 的相反数,而 0 和 1 我们可以根据表达式是否非 0,使用非运算符构造出来,再将构造的结果取反加一即可。

代码

int conditional(int x, int y, int z) {
    int flag = ~(!x) + 1;     // flag = x ? 0 : -1;
    int yp = ~flag & y;       // flag = 0, yp = y; flag = -1, yp = 0;
    int zp = flag & z;        // flag = 0, zp = 0; flag = -1, zp = z;
    return yp | zp;
}

isLessOrEqual

思路

判断两个数的大小关系,很容易想到使用作差的方法,判断 x + ~y + 1 的结果是否小于等于 0,即全为 0 或者最高位为 1。

不过这里还需要考虑溢出:由于同号相减必定不会导致溢出,因此我们只需要考虑异号的情况。而如果两个数异号,那它们之间的大小关系就显而易见了。

代码

int isLessOrEqual(int x, int y) {
    int sign1 = ((x >> 31) & 1) & ((y >> 31) ^ 1);  // sign1 = (x < 0 && y > 0) ? 1 : 0;
    int sign2 = ((x >> 31) ^ 1) & ((y >> 31) & 1);  // sign2 = (x > 0 && y < 0) ? 1 : 0;
    int z = x + ~y + 1;                             // z = x - y
    int sub = !(x ^ y) | ((z >> 31) & 1);           // z <= 0    
    return (sub | sign1) & !sign2;
}

logicalNeg

思路

这题从二进制位的角度不好思考,不妨从其表示的十进制数的角度出发:

当 x = 0 时,-x = x ,即 x 和 -x 的最高位相同,都为 0;当 x != 0 时,x 和 -x 的最高位必定有一个为 1。

可以利用这一特性将 x | nx 右移 31 位,由于整数进行的是符号右移,因此当最高位为 0 时,右移的结果全为 0,当最高位为 1 时,右移的结果全为 1。再将右移结果加 1,即可构造出 1 或者 0,且刚好与零和非零对应。

代码

int logicalNeg(int x) {
    int nx = ~x + 1;
    return ((x | nx) >> 31) + 1;
}

howManyBits

思路

这题看到限制 90 个运算符就给吓着了,实际上也确实很困难,自己想了半天也没有思路,于是在网上参考了别人的解法,感觉相当精妙,在这里介绍一番:

对于正整数 x 而言,可以使用二分搜索的方式来确定所需的位数。首先判断 x 是否需要 16 位来表示,即 x 右移 16 位是否为 0,如果是,则右移 16 位,否则不做处理,然后再判断是否需要 8 位来处理,以此类推。最后将上述过程中的右移次数累加起来再加一(正整数首位需要为 0),即为总共需要的位数。

对于负整数 x 而言,它所需的位数与 x 取反得到的整数所需位数相同,证明没整明白。。。

代码

int howManyBits(int x) {
    int absx = (x >> 31) ^ x;
    int b16, b8, b4, b2, b1, b0;

    // 二分搜索
    b16 = (!!(absx >> 16)) << 4;
    absx = absx >> b16;
    b8 = (!!(absx >> 8)) << 3;
    absx = absx >> b8;
    b4 = (!!(absx >> 4)) << 2;
    absx = absx >> b4;
    b2 = (!!(absx >> 2)) << 1;
    absx = absx >> b2;
    b1 = (!!(absx >> 1));
    absx = absx >> b1;
    b0 = absx;

    return b16 + b8 + b4 + b2 + b1 + b0 + 1;
}

floatScale2

思路

这题主要是要对规格化数和非规格化数进行分类讨论:

当 uf 为规格化数,即阶码不为 0 时,乘二相当于将阶码位加 1。

当 uf 为非规格化数,即阶码为 0 时,此时 uf 的值完全由尾数来表示,且不含隐含 0,因此乘二相当于将尾数乘二,即左移 1 位。

需要注意的是,当 uf 为非规格化数且尾数最高位为 1 时,尾数左移会导致最高位的 1 移动到阶码的最低位。但经过验证,此时的结果仍然符合预期,即非规格化数无缝衔接到了规格化数,不禁感叹 IEEE 754 标准浮点数的设计之精妙。

代码

unsigned floatScale2(unsigned uf) {
    unsigned t = (uf >> 23) & 0xff;  // 阶码
    unsigned m = uf & 0x7fffff;      // 尾数
    if (t == 0xff) return uf;        // 无穷大或者 NaN
    // 非规格化数
    if (t == 0x00) {
        m <<= 1;
        uf &= 0xff800000;
        uf |= m;
    }
    // 规格化数
    else {
        t += 1;
        uf &= 0x807fffff;
        uf |= (t << 23);
    }
    return uf;
}

floatFloat2Int

思路

首先确定整数所能表示的上下界的值:当阶码小于 127,即指数位小于 0 时,此时浮点数 uf 小于 1,对应的整数为 0;当阶码大于 150,即指数位大于 23 时,此时单精度浮点数的精度(尾数长度)不足以正确表示对应的整数,返回 0x80000000。

对于在合理范围内的 uf,将其转换为对应的整数,首先需要尾数最高位的高一位加上规格化数隐含的 1,再根据阶码的大小将尾数进行右移,阶码越大,右移位数越少。最后根据符号位的值选择是否将结果取反加一。

代码

int floatFloat2Int(unsigned uf) {
    unsigned s = (uf >> 31) & 0x1;   // 符号
    unsigned t = (uf >> 23) & 0xff;  // 阶码
    unsigned m = uf & 0x7fffff;      // 尾数
    int val;
    // 小于 1
    if (t < 127) {
        val = 0;
    }
    // 大于 1 且不溢出
    else if (t <= 150) {
        val = m | 0x800000;
        val >>= (23 - (t - 127));
        if (s == 1) {
            val = ~val + 1;
        }
    }
    // 溢出
    else {
        val = 0x80000000;
    }
    return val;
}

floatPower2

思路

同样是对规格化数和非规格化数的分类讨论:

x >= -150 && x < -127 时,结果为非规格化数,此时浮点数表示只有一个位为 1,其余全为 0。直接根据指数 x 的值确定该位的位置即可。

x >= -127 && x < 128 时,结果为规格化数,此时浮点数表示的尾数全为 0,只有阶码用来表示指数的值。根据指数 x 的值确定阶码的值,然后构造出浮点数即可。

代码

unsigned floatPower2(int x) {
    unsigned val;
    // 太小
    if (x < -150) {
        val = 0;
    }
    // 非规格化数
    else if (x < -127) {
        val = 1 << (150 + x);
    }
    // 规格化数
    else if (x < 128) {
        unsigned t = x + 127;
        val |= (t << 23);
    }
    // 太大
    else {
        val = 0x7f800000;
    }
    return val;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2096544.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Red Hat Enterprise Linux 9—Red Hat 9.4Linux系统 Mac电脑虚拟机安装【保姆级教程】

Mac分享吧 文章目录 效果一、下载软件二、安装软件与配置1、安装2、配置 三、查看基本信息安装完成&#xff01;&#xff01;&#xff01; 效果 一、下载软件 下载软件 地址&#xff1a;www.macfxb.cn 二、安装软件与配置 1、安装 2、配置 三、查看基本信息 安装完成&#xf…

【国考】特值法

特值法 题干中存在乘除关系&#xff0c;且对应量未知。 例3&#xff1a;甲、乙、丙三个工程队的效率比为6&#xff1a;5&#xff1a;4,现将A、B两项工作量相同的工程交给这三个工程队,甲队负责A工程,乙队负责B工程,丙队参与A工程若干天后转而参与B工程.两项工程同时开工,耗时16…

【PyQt6 应用程序】视频百叶窗效果一键生成模块

在现代的多媒体创作中,音频和视频的结合是提升作品感染力的关键因素之一。尤其是短视频的制作,往往需要根据音频的节奏进行精细的剪辑和特效添加。PyQt6 作为一个功能强大的 Python GUI 库,为我们提供了极大的便利,使得我们可以轻松地创建功能丰富的应用程序。 本教程将一…

J.U.C并发工具集实战及原理分析

​在J.U.C里提供了很多的并发控制工具类&#xff0c;这些工具类可以使得线程按照业务的某种约束来执行。本节包含CountDownLatch、Semaphore、CyclicBarrier等工具类。目的是了解他们基本使用、原理及实际应用。 1. CountDownLatch主题 1.1 CountDownLatch简介 CountDownLat…

ShardingSphere-JDBC实现数据加解密

一、什么是ShardingSphere&#xff1f; ShardingSphere定位为轻量级 Java 框架&#xff0c;在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库&#xff0c;以 jar 包形式提供服务&#xff0c;无需额外部署和依赖&#xff0c;可理解为增强版的 JDBC 驱动&#xff0c;完…

最优化理论(一)

什么是最优化问题 最优化问题是决策问题&#xff0c;选择一下可以执行的策略来使得目标最优。 一个最优化问题包括&#xff1a; 决策变量一个或多个目标函数一个由科兴策略组成的集合&#xff0c;可由等式或者不等式刻画 最优化问题的基本形式&#xff1a; 最优化问题的分类…

upload-labs通关攻略

Pass-1 这里上传php文件说不允许上传 然后咱们开启抓包将png文件改为php文件 放包回去成功上传 Pass-2 进来查看提示说对mime进行检查 抓包把这里改为image/jpg; 放包回去就上传成功了 Pass-3 这里上传php文件它说不允许上传这些后缀的文件 那咱们就可以改它的后缀名来绕过…

Guitar Pro 8.2.1 Build 32+Soundbanks Win/Mac音色库 开心激活版 音乐软件Guitar Pro 8中文破解版

音乐软件Guitar Pro 8中文破解版是一个受吉他手喜爱的吉他和弦、六线谱、BASS 四线谱绘制、打印、查看、试听软件&#xff0c;它也是一款优秀的 MIDI 音序器&#xff0c;MIDI 制作辅助工具&#xff0c;可以输出标准格式的 MIDI。GP 的过人之处就在于它可以直接用鼠标和键盘按标…

过滤器 与 拦截器

文章目录 过滤器 与 拦截器一、过滤器&#xff08;Filter&#xff09;1、特点2、生命周期3、实现4、过滤器链1&#xff09;配置 order2&#xff09;执行顺序 二、拦截器 Inteceptor1、特点2、生命周期3、实现4、拦截器链1&#xff09;配置 order2&#xff09;执行顺序&#xff…

【Linux】保姆级 Linux 常见命令使用

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录 1. Linux 是什么1.1 Linux 是什么1.2 关于 Linux 我们需要学什么 2. 需提前准备的东西2.1 环境 —— 如何获取…

关于PowerDesigner的使用

1.PowerDesigner概述&#xff1a; 1.PowerDesigner是一款开发人员常用的数据库建模工具&#xff0c;用户利用该软件可以方便地制作 数据流程图、概念数据模型 、 物理数据模型 &#xff0c;它几乎包括了数据库模型设计的全过程&#xff0c;是Sybase公司为企业建模和设计提供的…

蓝色炫酷碎粒子HTML5导航源码

源码介绍 蓝色炫酷碎粒子HTML5导航源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 效果预览 源码获取 蓝色炫酷碎粒…

火焰传感器详解(STM32)

目录 一、介绍 二、传感器原理 1.原理图 2.引脚描述 三、程序设计 main.c文件 IR.h文件 IR.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 火焰传感器是一种常用于检测火焰或特定波长&#xff08;760nm-1100nm&#xff09;红外光的传感器。探测角度60左右&am…

MQTT学习:MQTT vs AMQP,mosquitto安装,调试工具mqttfx mqttx

前言 物联网vs互联网? 数据量/数据源:物联网的数据多是设备的自动采集,其数量远远超过互联网,互联网的数据更多是人工生成的 MQTT 协议(Message Queuing Telemetry Transport)vs AMQP 协议(Advanced Message Queuing Protocol)是两种在物联网中广泛使用的协议。 物联网…

推荐一款灵活,可靠和快速的开源分布式任务调度平台

今天给大家推荐一款灵活&#xff0c;可靠和快速的开源分布式任务调度平台——SnailJob。 前言 什么是任务调度&#xff1f; 任务调度&#xff0c;是指在多任务的环境下&#xff0c;合理地分配系统资源&#xff0c;调度各个任务在什么时候&#xff0c;由哪一个处理器处理&…

【简单】 猿人学web第一届 第15题 备周则意怠,常见则不疑

数据接口分析 数据接口 https://match.yuanrenxue.cn/api/match/15 请求时需要携带 page 页码&#xff0c;m为加密参数 cookie中没有加密信息&#xff0c;携带 SessionId请求即可 加密参数还原 查看数据接口对应的 requests 栈 m参数 是通过 window.m() 方法执行后得到的 打上…

分布式系统中的Dapper与Twitter Zipkin:链路追踪技术的实现与应用

目录 一、什么是链路追踪&#xff1f; 二、核心思想Dapper &#xff08;一&#xff09;Dapper链路追踪基本概念概要 &#xff08;二&#xff09;Trace、Span、Annotations Trace Span Annotation 案例说明 &#xff08;三&#xff09;带内数据与带外数据 带外数据 带…

『 C++ 』多线程相关

文章目录 极短临界区互斥锁的短板原子操作类 atomicatomic 原子操作原理 CASCAS 操作解决多线程创建链表的节点丢失问题多线程下的 shared_ptr 智能指针最简单的单例模式 极短临界区互斥锁的短板 如果两个线程同时对一个共享资源变量x进行自增操作将会出现线程安全问题,这个线程…

官方宣布Navicat免费使用!

官方宣布Navicat免费使用&#xff01; 对于开发者和数据库管理员来说&#xff0c;Navicat一直是不可或缺的工具之一。官方宣布Navicat可以免费使用&#xff0c;这无疑是个令人振奋的消息&#xff01;虽然是精简版&#xff0c;但足够日常使用。文末有下载链接。 无论你是管理M…

Linux 文件接口和文件管理

目录 一、回顾c语言文件操作 二、系统调用的文件操作 系统调用文件接口 open&#xff1a; close&#xff1a; write&#xff1a; 代码测试&#xff1a; ​编辑 ​编辑 read&#xff1a; 语言和系统函数间的关系&#xff1a; flags的实现思路 三、OS内文件的管理 语…