如何在 16 位系统上进行 64 位数学运算
创始人
2024-03-02 13:33:47
0

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

几年前,我为 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 整型,其使用了所有的位来表达一个正数。无符号整型的值域为从 02 位长 - 1。例如,8 位数可以是 0255 之间的任意值,而 16 位数则在 065535 之间,以此类推。

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

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

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

绝对值与负值

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

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

你可以用汇编写下这个程序用以返回任意 64 位数字的绝对值:

; 语法,NASM for DOS
proc_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 DOS
proc_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中国 荣誉推出

相关内容

不同对象内的矩阵数学运算
在这里是一个解决方法的示例,其中展示了两个不同对象内的矩阵数学运算...
2025-01-08 23:01:11
不使用VBA计算“n”个数...
使用Excel公式SUMIF,例如:假设有以下数学运算:2*3、4...
2024-12-29 12:32:22
不使用Java Math类...
要进行数学运算,但不使用Java的Math类,可以使用其他数学库或...
2024-12-28 21:01:13
不确定为什么这个函数不能执...
检查函数中的数学运算符和数值的正确性,并确保它们的类型相匹配。例如...
2024-12-27 23:00:47
不进行复制的通用数学运算符...
在C++中,我们可以通过重载通用数学运算符来实现不进行复制的操作。...
2024-12-25 22:02:07
编写一个函数,使其能够对给...
可以使用Python中的eval()函数来执行数学运算。首先,需要...
2024-12-06 18:31:28

热门资讯

Helix:高级 Linux ... 说到 基于终端的文本编辑器,通常 Vim、Emacs 和 Nano 受到了关注。这并不意味着没有其他...
使用 KRAWL 扫描 Kub... 用 KRAWL 脚本来识别 Kubernetes Pod 和容器中的错误。当你使用 Kubernet...
JStock:Linux 上不... 如果你在股票市场做投资,那么你可能非常清楚投资组合管理计划有多重要。管理投资组合的目标是依据你能承受...
通过 SaltStack 管理... 我在搜索Puppet的替代品时,偶然间碰到了Salt。我喜欢puppet,但是我又爱上Salt了:)...
Epic 游戏商店现在可在 S... 现在可以在 Steam Deck 上运行 Epic 游戏商店了,几乎无懈可击! 但是,它是非官方的。...
《Apex 英雄》正式可在 S... 《Apex 英雄》现已通过 Steam Deck 验证,这使其成为支持 Linux 的顶级多人游戏之...
如何在 Github 上创建一... 学习如何复刻一个仓库,进行更改,并要求维护人员审查并合并它。你知道如何使用 git 了,你有一个 G...
2024 开年,LLUG 和你... Hi,Linuxer,2024 新年伊始,不知道你是否已经准备好迎接新的一年~ 2024 年,Lin...
什么是 KDE Connect... 什么是 KDE Connect?它的主要特性是什么?它应该如何安装?本文提供了基本的使用指南。科技日...
Opera 浏览器内置的 VP... 昨天我们报道过 Opera 浏览器内置了 VPN 服务,用户打开它可以防止他们的在线活动被窥视。不过...