定点数乘法运算:Booth算法(补码一位乘法)C 实现
文章目录
- 综述
- 定点数的乘法运算
- Booth算法分析
- Booth算法的C实现
综述
在计算机中参与运算的机器数有两大类:无符号数和有符号数。
下面主要讲解有符号数:
在机器中,数的“正”、“负”号是无法识别的,有符号数用“0”表示正,用“1”表示负号,从而将符号数值化,并通常约定二进制数的最高位是符号位,即符号位放在有效数字的前面,组成有符号数。
有符号数的机器表示有原码、补码、反码和移码。
根据小数点的位置是否固定,在计算机中的有两种数据格式:定点表示和浮点表示。接下来我们将介绍的算法都是定点表示。
有关浮点数的内存存储及介绍请参考我的另一篇博文:
《浮点数在内存中的存储方式与运算陷阱》
定点表示即约定机器数中的小数点位置是固定不变的,小数点不再使用 “ . ” 表示,而是约定它的位置。理论上,小数点位置固定在哪一位都是可以的,但在计算机中通常采用两种简单的约定
- 将小数点的位置固定在数据的最高位之前,一般称为定点小数
- 将小数点的位置固定在数据的最高位之后,一般称为定点整数
定点小数是纯小数,约定小数点位置在符号位之后、有效数值部分最高位之前。
定点整数是纯整数,约定小数点位置在符号位有效数值部分最低位之后。
本文将介绍定点数的乘法运算。
定点数的乘法运算
在计算机中,乘法运算由累加和右移操作实现。根据机器数的不同,可分为
- 原码一位乘法
- 补码一位乘法
原码一位乘法的规则比补码一位乘法简单。我们下面主要介绍补码一位乘法(Booth)算法。
Booth算法分析
这是一种有符号数的乘法,采用相加和相减操作计算补码数据的乘积。
设 [X]补 = Xs.X1X2···Xn, [Y]补 = Ys.Y1Y2···Yn,则运算规则如下:
- 符号位参与运算,运算的数均以补码表示
- 被乘数一般取双符号位参与运算,部分积取双符号位,初值为0,乘数可取单符号位
- 乘数末尾增设附加位Yn+1,且初值为0
- 根据(Yn,Yn+1)的值来确定操作,具体见下表
- 移位按照补码右移规则进行
- 按照上述算法进行 n+1 步操作,但 n + 1 步不再移位(共进行 n + 1 次累加和 n次右移),仅根据Yn 与 Yn+1 的比较结果做相应的运算。
接下来我们分析一个实例:
设机器字长为5位(含一位符号位,n = 4),x = -0.1101,y = 0.1101,采用Booth算法求解 x · y。
Booth算法的C实现
通过上述分析,我们通过C语言来实现Booth算法。
问题描述:使用纯C实现Booth算法,不允许调用除输入输出外的其他功能库函数。
问题分析:
我们需要完成下述功能:
- 输入x、y,计算出 [X]补、[-X]补、 [Y]补
- 将X改为双符号位数
- 部分积取双符号位,初值为0
- 分割乘数,获取(Yn,Yn+1)的值
- 根据(Yn,Yn+1)的值执行相应操作
完整设计如下:
/**
* Copyright: Copyright(c) 2019
* Created on 26/4/2019
* Author : ZYZMZM
* Version 1.0
* Title : Booth Algorithm
**/#include <stdio.h>/* 存储被乘数 x 的补码 */
char xCom[20] = { 0 };/* 存储 -x 的补码 */
char mxCom[20] = { 0 };/* 存储乘数 y 的补码 */
char yCom[20] = { 0 };/* 存储乘数 y 末位增加 0 */
char multiNum[20] = { 0 };/* 存储部分积的初值 */
char multiSrc[20] = { 0 };/* 计算字符串长度 */
int length(char* ch)
{int len = 0;while (*ch != NULL){++len;++ch;}return len;
}/* 拷贝字符串 */
char* copy(char* dest, const char* src)
{char* tmp = dest;while (*dest++ = *src++) {}return tmp;
}/* 字符串比较 */
int compare(const char* dest, const char* src)
{int tmp = 0;while (!(tmp = *dest - *src) && *dest && *src){dest++;src++;}if (tmp > 0) { return 1; }else if (tmp < 0) { return -1; }else { return 0; }
}/* 字符串截取:截取从src中的begin下标到end下标的字符串,结果存储在res中 */
char* intercept(char* src, char *res, int begin, int end)
{int i = begin;int j = 0;while (i <= end){res[j] = src[i];++j;++i;}return res;
}/* 右移 */
void mRight(char* src)
{int len = length(src);int i = len - 1;/* 获取小数部分的起始位置 */int num = 0;char* p = src;while (*p != '.'){++num;++p;}++num;/* 将小数后的第一位空出,其余全部后移 */for (; i >= num; --i){src[i + 1] = src[i];}++i;/* 根据正负进行添1 或 添0 */if (src[0] == '1'){src[i] = '1';}else{src[i] = '0';}}/* 浮点数加法 */
void Add(char* lhsstr, char *rhsstr, char *result)
{int lhsLen = length(lhsstr);int rhsLen = length(rhsstr);/* 对长度较小的数字,在其后补0,目的是为了使两数长度相同 */if (lhsLen < rhsLen){int diff = rhsLen - lhsLen;int i = lhsLen;while (diff > 0){lhsstr[i] = '0';--diff;++i;}}else if (lhsLen > rhsLen){int diff = lhsLen - rhsLen;int i = rhsLen;while (diff > 0){rhsstr[i] = '0';--diff;++i;}}/* 拿到最大的长度 */int i = lhsLen <= rhsLen ? rhsLen - 1 : lhsLen - 1;int j = i;/* 进位标志 */int flag = 0;while (i >= 0){/* 小数点跳过 */if (lhsstr[i] == '.'){result[i] = '.';--i;continue;}/* 小数点跳过 */if (rhsstr[j] == '.'){result[j] = '.';--j;continue;}int lhs = lhsstr[i] - '0';int rhs = rhsstr[j] - '0';int sum = lhs + rhs;if (flag == 1){sum += 1;flag = 0;}/* 和为2,则需要进位,存储0,更新进位标志 */if (sum == 2) {flag = 1;sum = 0;}/* 和为3,即之前有进位,且现在和为2也有进位,即11,存储1,更新进位标志 */else if (sum == 3){flag = 1;sum = 1;}result[i] = sum + '0';--i;--j;}
}/* 原码转补码 */
void calComplement(char *origin, char *recv)
{/* 负数标志 */int isMinus = 0;if (origin[0] == '-'){isMinus = 1;}char* result = origin;/* 原码为负,补码--> 原码变反加一 */if (isMinus){/* -0.1101 -> 11.xxxx */*origin++ = '1';*origin++ = '1';/* 小数位全部变反 */while (*origin != NULL){if (*origin == '1'){*origin = '0';}else if (*origin == '0'){*origin = '1';}++origin;}/* 加一操作:构造和操作数长度相同的加数,即 11.xxxx + 00.0001 */int len = length(result);char rhs[20] = { 0 };rhs[0] = '0';rhs[1] = '0';rhs[2] = '.';rhs[len - 1] = '1';for (int i = len - 2; i > 2; --i){rhs[i] = '0';}Add(result, rhs, recv); return;}/* 原码为正,补码不改变,但在这里给补码前补0,即 0.1011 --> 00.1011 */int len = length(origin);for (int i = len - 1; i >= 0; --i){origin[i + 1] = origin[i];}origin[0] = '0';copy(recv, origin);
}/* 补码转原码:最后的结果转换 */
void calOri(char* origin, char* recv)
{/* 负数标志 */int isMinus = 0;if (origin[0] == '1'){isMinus = 1;}char* result = origin;/* 补码的符号位为负 */if (isMinus){/*multiRes : 11.01110001X * Y COM : 1.01110001X * Y : -0.10001111*//*** 11.XXXXX --> -0.XXXXX(通过multiRes补码** 转换,因为11恰好可用-0,都是两位,直接替换) */*origin++ = '-';*origin++ = '0';/* 按位取反 */while (*origin != NULL){if (*origin == '1'){*origin = '0';}else if (*origin == '0'){*origin = '1';}++origin;}/* 加一操作 */int len = length(result);char rhs[20] = { 0 };rhs[0] = '0';rhs[1] = '0';rhs[2] = '.';rhs[len - 1] = '1';for (int i = len - 2; i > 2; --i){rhs[i] = '0';}Add(result, rhs, recv);return;}/* 补码符号位为正,即原码和补码相同 */copy(recv, origin);
}/* booth算法核心实现 */
void Calculate()
{int i = 0;char index[20] = { 0 };/* 拿到末尾添0的乘数副本 */copy(index, multiNum);/* 计算小数部分起始位置 */int num = 0;while (index[i] != '.'){++num;++i;}/* 去掉index的小数点,便于之后进行移位分割 */char res[20] = { 0 };int len = length(index);for (i = num; i < len - 1; ++i){index[i] = index[i + 1];}index[i] = '\0';i = length(index) - 1;/* 首次计算标志,因为首次计算是与部分积初值的计算 */int first = 1;/* 存储部分积 */char multiRes[20] = { 0 };while (i - 1 >= 0){/* 移位分割,从低位向高位分割,分割首末位置每次同时向高位移动一位 */intercept(index, res, i - 1, i); /* 首次是与初值的运算 */if (first){first = 0;if (compare(res, "00") == 0){/* 00 --> 初值右移一位 */mRight(multiSrc);}else if (compare(res, "01") == 0){/* 01 --> 初值加[x]补,并右移一位 */Add(multiSrc, xCom, multiRes);mRight(multiRes);}else if (compare(res, "10") == 0){/* 10 --> 初值加[-x]补,并右移一位 */Add(multiSrc, mxCom, multiRes);mRight(multiRes);}else if (compare(res, "11") == 0){/* 初值右移一位 */mRight(multiSrc);}}/* 非首次都是与部分积的运算 */else{/* 00 --> 部分积右移一位 */if (compare(res, "00") == 0){if (i - 1 != 0)mRight(multiRes);}else if (compare(res, "01") == 0){/* 01 --> 部分积加[x]补,并右移一位 */Add(multiRes, xCom, multiRes);if (i - 1 != 0) mRight(multiRes);}else if (compare(res, "10") == 0){/* 10 --> 部分积加[-x]补,并右移一位 */Add(multiRes, mxCom, multiRes);if (i - 1 != 0)mRight(multiRes);}else if (compare(res, "11") == 0){/* 部分积右移一位 */if (i - 1 != 0)mRight(multiRes);}}--i;}/* 部分积运算结果 */printf("部分积运算结果 : %s\n", multiRes);/* 拷贝运算结果,因为它会被下面计算补码时更改,但是计算原码时要用到它 */char Ori[20] = { 0 };copy(Ori, multiRes);/* 通过部分积得到补码 */if (multiRes[0] == '1'){int mlen = length(multiRes);i = 0;for (; i < mlen - 1; i++){multiRes[i] = multiRes[i + 1];}multiRes[i] = '\0';}printf("[X * Y]补 : %s\n", multiRes);/* 通过部分积得到原码 */char finalRes[20] = { 0 };calOri(Ori, finalRes);printf("X * Y : %s\n", finalRes);}int main()
{char inputx[20] = { 0 };char inputy[20] = { 0 };printf("input x : ");scanf("%s", inputx);printf("input y : ");scanf("%s", inputy);char origin[20] = { 0 };// x补码copy(origin, inputx);calComplement(origin, xCom);printf("[x]补 : %s\n", xCom);// -x补码copy(origin, inputx);int lenx = length(origin);/* 如果x本身就为负,那么直接将负号去掉即可得到-x */if (inputx[0] == '-'){int i = 0;for (; i < lenx - 1; ++i){origin[i] = origin[i + 1];}origin[i] = '\0';}/* 如果x本身为正,那么添加负号得到-x */else{for (int i = lenx - 1; i >= 0; --i){origin[i + 1] = origin[i];}origin[0] = '-';}calComplement(origin, mxCom);printf("[-x]补 : %s\n", mxCom);// y补码copy(origin, inputy);calComplement(origin, yCom);printf("[y]补 : %s\n", yCom);// 乘数y的末尾补0copy(origin, inputy);int leny = length(origin);origin[leny] = '0';origin[leny + 1] = '\0';copy(multiNum, origin);printf("乘数y的末尾补0 : %s\n", multiNum);// 计算部分积初值multiSrc[0] = '0';multiSrc[1] = '0';multiSrc[2] = '.';int len = length(xCom) - 3;int i = 0;int j = 3;for (; i < len; ++i, ++j){multiSrc[j] = '0';}printf("部分积初值: %s\n\n", multiSrc);/* Booth算法 */Calculate();
}
我们输入之前的示例进行验证:
由结果可知,我们的计算是正确的。
定点数乘法运算:Booth算法(补码一位乘法)C 实现相关推荐
- [计算机组成原理] Booth算法 —— 补码一位乘法
x * y = z 运算规则: 1.和原码一位乘法不同,补码一位乘法的符号位是参加运算的,且运算结果和所有参加运算的数都是补码形式. 2.乘数 x 取双符号位参与运算,部分积的初始值为0: 乘数 y ...
- 【计算机组成原理】定点乘法运算之补码一位乘法(Booth算法)
x * y = z 讨论已知x和y的情况下,怎么通过补码一位乘法方法得出z- 首先说下运算规则- 和原码一位乘法不同的是,补码一位乘法的符号位是参加运算的~运算的所有的数包括得到的结果z都是补码的形式 ...
- (计算机组成原理)第二章数据的表示和运算-第二节5:定点数乘法运算(原码/补码一位乘法)
文章目录 一:乘法运算基本思想 二:原码一位乘法 (1)实现原理 (2)手算模拟 三:补码一位乘法( B o o t h Booth Booth
- 补码一位乘法(Booth算法)
在补码一位乘法的求解过程中我们需要的东西:[X]补,[Y]补以及被乘数的相反数的补码[-X]补 一.运算规则 1.符号位参与计算 2.采用补码进行计算 3.被乘数X 一般取双符号位参与计算,并且让部分 ...
- 补码一位乘法-一般乘法与Booth的证明与原理
补码一位乘法 为什么要使用补码乘法? 在计算机中,使用一般乘法的话,对符号位还要重新进行异或操作,这样会大大降低运算速度,而使用补码乘法运算,就可以找到一种通用的解法来解决符号位的重复计算,而将符号位 ...
- 一位原码的乘法规则_原码一位乘法与补码一位乘法
原码1位乘法 在定点计算机中,两个原码表示的数相乘的运算规则是:乘积的符号位由两数的符号按异或运算得到.而乘积的数值部分则是两个正数相乘之积.设n位被乘数和乘数用定点小数表示(定点整数也相同适用) 被 ...
- 补码一位乘法——布斯(Booth)算法
布斯Booth算法 "乘积"均改为"部分积".
- 2.2.2 .6定点数的乘法运算-1原码一位乘法
XYXZNB哈哈哈哈(๑•̀ㅂ•́)و✧买!୧(﹒︠ᴗ﹒︡)୨ 加法移位运算如何实现,那这小节中我们要学习定点数的源码乘法如何实现,那由于今天窗外的雨下的很大,所以可能会有一些雨声的录入好的,那这个小 ...
- 定点乘法运算之原码一位乘法
x * y = z 讨论已知x和y的情况下,怎么通过原码一位乘法方法得出z~~ 首先说下运算规则~ 1. z的符号位通过x和y的符号位进行异或运算得到~(这个很好理解哒,负负得正,正正得正,正负得负嘛 ...
- 【计算机组成原理】定点乘法运算之原码两位乘法
讨论x * y = z 采用原码两位乘法,已知x和y,如何求得z 原码两位乘法和原码一位乘法一样,符号位不参加运算 部分积和被乘数x均采用三位符号,乘数y末位每次要加一个c,c一开始是0 根据如下法则 ...
最新文章
- centos下忘记mysql密码_CentOS下忘记mysql密码的解决办法
- php网页加查询框,Twentytwelve头部添加搜索框及网站名称与描述同行显示的简单方法 | 科研动力...
- 《Java 核心技术卷1 第10版》学习笔记------异常
- linux 股票指南针,IOS开发入门之ios指南针
- 学习opencv3中文版_给视觉组新生的一点学习建议
- python 数学基础_Python3数学基础 - 随笔分类 - 既生喻何生亮 - 博客园
- 一人编程累,加班何人陪?1024 最好的礼物给最牛掰的你
- system进程总是100%
- abaqus对应python版本_Abaqus里应用Python的一些技巧
- 最好的网盘--主流网盘大比拼
- 3D打印——从solidworks到打印机(含打印机常见问题及解决方法)
- 宗教信仰与孟加拉国女性社交网络的更大规模、亲属​​密度和地理分布有关
- 屏蔽不讲robots规则的国外垃圾蜘蛛
- 红蜘蛛显示器测试软件,红蜘蛛5校色仪怎么用?显示器校色及测试色域和色彩精准度详细教程...
- 数据库连接超时和go away、如何检测数据库的最大连接数
- Effective C++ 读书笔记之Part5.Implementations
- 让你的闲置iPad/安卓平板成为你电脑屏幕的扩展显示器!
- 如何使用Google云端硬盘备份和还原WhatsApp消息
- Linux下安装Oracle18c
- Unity游戏开发中ECS思想介绍