文章目录

  • 综述
  • 定点数的乘法运算
    • 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 实现相关推荐

  1. [计算机组成原理] Booth算法 —— 补码一位乘法

    x * y = z 运算规则: 1.和原码一位乘法不同,补码一位乘法的符号位是参加运算的,且运算结果和所有参加运算的数都是补码形式. 2.乘数 x 取双符号位参与运算,部分积的初始值为0: 乘数 y ...

  2. 【计算机组成原理】定点乘法运算之补码一位乘法(Booth算法)

    x * y = z 讨论已知x和y的情况下,怎么通过补码一位乘法方法得出z- 首先说下运算规则- 和原码一位乘法不同的是,补码一位乘法的符号位是参加运算的~运算的所有的数包括得到的结果z都是补码的形式 ...

  3. (计算机组成原理)第二章数据的表示和运算-第二节5:定点数乘法运算(原码/补码一位乘法)

    文章目录 一:乘法运算基本思想 二:原码一位乘法 (1)实现原理 (2)手算模拟 三:补码一位乘法( B o o t h Booth Booth

  4. 补码一位乘法(Booth算法)

    在补码一位乘法的求解过程中我们需要的东西:[X]补,[Y]补以及被乘数的相反数的补码[-X]补 一.运算规则 1.符号位参与计算 2.采用补码进行计算 3.被乘数X 一般取双符号位参与计算,并且让部分 ...

  5. 补码一位乘法-一般乘法与Booth的证明与原理

    补码一位乘法 为什么要使用补码乘法? 在计算机中,使用一般乘法的话,对符号位还要重新进行异或操作,这样会大大降低运算速度,而使用补码乘法运算,就可以找到一种通用的解法来解决符号位的重复计算,而将符号位 ...

  6. 一位原码的乘法规则_原码一位乘法与补码一位乘法

    原码1位乘法 在定点计算机中,两个原码表示的数相乘的运算规则是:乘积的符号位由两数的符号按异或运算得到.而乘积的数值部分则是两个正数相乘之积.设n位被乘数和乘数用定点小数表示(定点整数也相同适用) 被 ...

  7. 补码一位乘法——布斯(Booth)算法

    布斯Booth算法 "乘积"均改为"部分积".

  8. 2.2.2 .6定点数的乘法运算-1原码一位乘法

    XYXZNB哈哈哈哈(๑•̀ㅂ•́)و✧买!୧(﹒︠ᴗ﹒︡)୨ 加法移位运算如何实现,那这小节中我们要学习定点数的源码乘法如何实现,那由于今天窗外的雨下的很大,所以可能会有一些雨声的录入好的,那这个小 ...

  9. 定点乘法运算之原码一位乘法

    x * y = z 讨论已知x和y的情况下,怎么通过原码一位乘法方法得出z~~ 首先说下运算规则~ 1. z的符号位通过x和y的符号位进行异或运算得到~(这个很好理解哒,负负得正,正正得正,正负得负嘛 ...

  10. 【计算机组成原理】定点乘法运算之原码两位乘法

    讨论x * y = z 采用原码两位乘法,已知x和y,如何求得z 原码两位乘法和原码一位乘法一样,符号位不参加运算 部分积和被乘数x均采用三位符号,乘数y末位每次要加一个c,c一开始是0 根据如下法则 ...

最新文章

  1. centos下忘记mysql密码_CentOS下忘记mysql密码的解决办法
  2. php网页加查询框,Twentytwelve头部添加搜索框及网站名称与描述同行显示的简单方法 | 科研动力...
  3. 《Java 核心技术卷1 第10版》学习笔记------异常
  4. linux 股票指南针,IOS开发入门之ios指南针
  5. 学习opencv3中文版_给视觉组新生的一点学习建议
  6. python 数学基础_Python3数学基础 - 随笔分类 - 既生喻何生亮 - 博客园
  7. 一人编程累,加班何人陪?1024 最好的礼物给最牛掰的你
  8. system进程总是100%
  9. abaqus对应python版本_Abaqus里应用Python的一些技巧
  10. 最好的网盘--主流网盘大比拼
  11. 3D打印——从solidworks到打印机(含打印机常见问题及解决方法)
  12. 宗教信仰与孟加拉国女性社交网络的更大规模、亲属​​密度和地理分布有关
  13. 屏蔽不讲robots规则的国外垃圾蜘蛛
  14. 红蜘蛛显示器测试软件,红蜘蛛5校色仪怎么用?显示器校色及测试色域和色彩精准度详细教程...
  15. 数据库连接超时和go away、如何检测数据库的最大连接数
  16. Effective C++ 读书笔记之Part5.Implementations
  17. 让你的闲置iPad/安卓平板成为你电脑屏幕的扩展显示器!
  18. 如何使用Google云端硬盘备份和还原WhatsApp消息
  19. Linux下安装Oracle18c
  20. Unity游戏开发中ECS思想介绍

热门文章

  1. selenium 区域截图
  2. 最新官方版本Fliqlo 炫酷翻盖时钟屏保 多平台
  3. 酒店管理系统(功能结构图、流程图)
  4. ehcache使用java_Java分布式缓存框架Ehcache 使用(一)
  5. 树莓派链接USB摄像头
  6. 0基础跟着黑马程序员学微信小程序前端开发Day02(自学笔记)
  7. java将多个excel合并为一个
  8. 数据库 | Redis 缓存雪崩解决方案
  9. 《挑战不可能之加油中国》中越边境广西段扫雷队整装亮相
  10. 微信群接口(开发思路)