| 会员登录 立即注册

打印 上一主题 下一主题

如何在 16 位系统上进行 64 位数学运算

[复制链接]
跳转到指定楼层
楼主
3AAA 发表于 2022-12-11 19:34:37 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
楼主
3AAA
2022-12-11 19:34:37 4430 5 看楼主


只要对汇编有一点基本的了解,这些函数就能扩展到任意位长的整型数学运算。

几年前,我为 FreeDOS 写了一个叫做 VMATH 的命令行数学程序。它只能在很小的无符号整型上执行十分简单的数学运算。随着近来 FreeDOS 社区里对基础数学的兴趣,我改进了 VMATH 使其可以为有符号 64 位整型提供基本的数学支持。

仅使用 16 位 8086 兼容的汇编指令来操控大型数字的过程并不简单。我希望能够分享一些在 VMATH 中用到的技术例子。其中一些方法掌握起来相当容易。而另外一些方法则看起来有点奇怪。你甚至可能学到一种进行基本数学运算的全新方式。

接下来要讲的加、减、乘、除会用到的技术将不局限于并不局限于 64 位整型。只要对汇编有一点基本的了解,这些函数就能扩展到任意位长的整型数学运算。

在深入研究这些数学函数前,我想先从计算机的角度介绍一下数字的一些基本知识。

计算机是如何读取数字的


一个英特尔兼容的 CPU 以字节Byte的形式贮存数字,储存顺序为从最低有效字节到最高有效字节。每个字节由 8 个二进位Bit组成,两个字节组成一个字Word。

一个储存在内存里的 64 位整型占用了 8 个字节(即 4 个字)。例如,数字 74565(十六进制表示为0x12345)的值长得是这个样子的:
用字节表示:db 0x45, 0x23, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00用字表示:dw 0x2345, 0x0001, 0x0000, 0x0000
当读取或写入数据到内存时,CPU 会以正确的顺序处理这些字节。对于比 8086 更现代的处理器而言,数据分组可以再大些,比如一个四字组Quadword就可以表达整个 64 位整型 0x0000000000012345。

8086 CPU 不能理解这么大的数字。当为 FreeDOS 编程时,你想要写的是一个能在任意电脑上跑的程序,甚至是原始的 IBM PC 5150。你想要使用能够扩展到任意大小整型的技术。我们其实并不关心更现代 CPU 的能力。

为了能做整型运算,我们的数据需要表达两种不同类型的数字。

第一种是无符号unsigned整型,其使用了所有的位来表达一个正数。无符号整型的值域为从 0到2位长- 1。例如,8 位数可以是0到255之间的任意值,而 16 位数则在0到65535之间,以此类推。

有符号整型也很类似。不同之处在于数字的最高位代表了这个数是一个正数(0)还是一个负数 (1)。有符号整型的值域前半部分为正数,正数值域是从0到2位长 - 1- 1。整型值域的后半部分为负数,负数值域则从0 - 2位长 - 1到-1。

比如说,一个 8 位数代表着 0到127之间的任意正数,以及-128到-1之间的任意负数。为了能更好的理解这一点,想象字节为一列数组[0...127,-128...-1]。因为-128在数组内紧跟着127,127加1等于-128。当然这可能看起来有点奇怪甚至反常,但这其实让这个层级的基本数学运算变简单了。

为了能够对大型整型进行简单的加、减、乘、除,你应该摸索一些简单的公式来计算一个数的绝对值或负值。你在做有符号整型运算的时候会用上它们的。

绝对值与负值


计算一个有符号整型的绝对值并没有它看起来的那么糟糕。由于无符号和有符号数字在内存里的储存形式,我们其实有一个简单的方案。你只需要翻转一个负数的所有字位,得出的结果再加 1。

如果你从没接触过二进制的话,这可能听上去有点奇怪,但这就是这么工作的。让我们来举一个例子,取一个负数的 8 位表达,比如说 -5。因为-5靠近[0...127,-128...-1]字节组末端,它的十六进制值为0xfb,二进制值为11111011。如果你翻转了所有字位,你会得到0x04或二进制值00000100。结果加1你就得到了你的答案:你刚刚把-5的值变成了+5。

你可以用汇编写下这个程序用以返回任意 64 位数字的绝对值:
; 语法,NASM for DOSproc_ABS:  ; 启动时,SI 寄存器会指向数据段(DS)内的内存位置,那里存放着程序内包含着  ; 会被转为正数的 64 位数。  ; 结束时,如果结果数字不能被转正,CF 寄存器会被设置。这种情况只  ; 有在遇到最大负值时会发生。其余情况,CF 不会被设置。    ; 检查最高字节的最高位  test [si+7], byte 0x80  ; 如不为 1,值为正值  jz .done_ABS  ; 翻转所有位  not word [si+6]       ; 字 #4  not word [si+4]       ; 字 #3  not word [si+2]       ; 字 #2  not word [si]         ; 字 #1  ; 字 #1 加 1  inc word [si]  ; 如结果不为 0,结束  jnz .done_ABS  ; 字 #2 加 1  inc word [si+2]  ; 如结果为 0,进位下一个字  jnz .done_ABS  inc word [si+4]  jnz .done_ABS  ; 此处无法进位  inc word [si+6]  ; 再一次检查最高位  test [si+7], byte 0x80  ; 如不为 1,我们成功了,结束  jz .done_ABS  ; 溢出错误,它被转成了负数  stc  ; 设置 CF 并返回  ret.done_ABS:  ; 成功,清理 CF 并返回  clc  ret
你可能已经注意到了,这个函数有一个潜在问题。由于正数和负数的二进制值表达方式,最大负数无法被转成正数。以 8 位数为例,最大负数是 -128。如果你翻转了-128的所有位数(二进制1__0000000),你会得到 127(二进制0__1111111)这个最大正值。如果你对结果加1,它会因溢出回到同样的负数(-128)。

要将正数转成负数,你只需要重复计算绝对值的步骤就行。以下的程序十分相似,你唯一需要确认的就是一开始的数字不是已经负了。
; 语法, NASM for DOSproc_NEG:  ; 开始时,SI 会指向需要转负的数字在内存里的位置。  ; 结束时,CF 永远不会被设置。    ; 检查最高字节的最高位  test [si+7], byte 0x80  ; 如为 1,数已经是负数  jnz .done_NEG  not word [si+6]       ; 翻转字的所有位,字 #4  not word [si+4]       ; 字 #3  not word [si+2]       ; 字 #2  not word [si]         ; 字 #1  inc word [si]         ; 字 #1 加 1  ; 如结果不为 0,结束  jnz .done_NEG  ; 字 #2 加 1  inc word [si+2]  ; 如结果为 0,进位下一个字  jnz .done_NEG  inc word [si+4]  jnz .done_NEG  ; 此处无法进位或转化  inc word [si+6]  ; 正。.done_NEG:  clc                   ; 成功,清理 CF 并返回  ret
看着这些绝对值函数与负值函数间的通用代码,它们应该被合并起来节约一些字节。合并代码也会带来额外的好处。首先,合并代码能帮助防止简单的笔误。这样也可以减少测试的要求。进一步来讲,这样通常会让代码变得简单易懂。在阅读一长串的汇编指令时,忘记读到哪里是常有的事。现在,我们可以不管这些。

计算一个数的绝对值或负值并不难。但是,这些函数对于我们即将开始的有符号整型数学运算至关重要。

我已经介绍了整型数字在位这一层面的基本表示方法,也创造了可以改变这些数字的基本程序,现在我们可以做点有趣的了。

让我们来做些数学运算吧!

via: https://opensource.com/article/22/10/64-bit-math

作者:Jerome Shidel选题:lkxed译者:yzuowei校对:wxy

本文由 LCTT原创编译,Linux中国荣誉推出
回复

使用道具 举报

沙发
jjiangxh2012 发表于 2022-12-11 19:36:04 | 只看该作者
沙发
jjiangxh2012
2022-12-11 19:36:04 看楼主
甭说16位机,8位机也可以做64位运算。4位机也可以。计算器用的是4位MPU(过去是4004)。
回复 支持 反对

使用道具 举报

板凳
嘎嘎 发表于 2022-12-11 19:36:33 | 只看该作者
板凳
嘎嘎
2022-12-11 19:36:33 看楼主
用字节数组实现,每个字节负责8位,多少位也轻松实现。
回复 支持 反对

使用道具 举报

地板
我弟叫雷锋丶 发表于 2022-12-11 19:37:23 | 只看该作者
地板
我弟叫雷锋丶
2022-12-11 19:37:23 看楼主
计算器不就这样的么?
回复 支持 反对

使用道具 举报

5#
我是胖熊11 发表于 2022-12-11 19:37:54 | 只看该作者
5#
我是胖熊11
2022-12-11 19:37:54 看楼主
理论来说,八位就可以进行10进制呢加减乘除。因为最大9*9是81八位足够了。就和小学学习加减乘除一样。多余的可以放入内存。一位一位的进行计算
回复 支持 反对

使用道具 举报

6#
zhengtse 发表于 2022-12-11 19:38:49 | 只看该作者
6#
zhengtse
2022-12-11 19:38:49 看楼主
8位AVR上就实现过64位有符号加法、减法、乘法、除法、开方……等。全汇编,性能非常好。同时提醒你回忆下几十年代各种电子词典很多都是8位的6502处理器,可以做到各种科学计算,然而6502仅仅提供了基本8位ALU,更多还是软件扩展,比如64位除法,64次移位相减,汇编做性能还是不错的
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则 返回列表

3AAA 当前离线
白银会员

查看:4430 | 回复:5

关于我们  |   侵权投诉受理  |   联系我们  |   Archiver  |  
免责声明:邳州信息网所有言论只代表发表者个人观点,与本站无关
Copyright © 2009-2025 pzxxw.com 版权所有:邳州金银杏文化传媒有限公司  

苏公网安备 32038202000401号

快速回复 返回顶部 返回列表