如何用汇编语言实现进制转换

关键字:

二进制 十进制 16进制 进制 汇编语言 8086汇编

 

摘要:"二进制",这个术语是计算机专业中一个相当重要的概念。它是整个现代计算机的基础。普通的计算机用户往往很难弄懂二进制运算的来龙去脉。不过即使弄不懂它,也不影响使用计算机。但是对于计算机专业的学生或者程序设计人员,一定要弄懂它,而且能够编写2-10进制转化的程序。本文试图教会程序设计人员弄懂什么是2,10,16进制,以及2-10进制转化的原理和方法。

 

进制

什么是进制?自然界的数可能无穷大。而符号的个数是有限的。用有限的符号来表示无穷大的数必然涉及到进制。先说说10进制。

 

 10进制

 因为人有10个指头,而手指头是最容易计数的工具(我想很多人都见过小学生掰指头数数的场景),所有最自然的进制是10进制。10进制就需要10个符号,现在世界最通用符号是阿拉伯数字,0,1,2,3,4,5,6,7,8,9. 十以内的数用一个符号来表示,10^1(表示10的1次方,下同)以上且10^2以下的数用2个符号来表示,以此类推。十进制的特点是逢十进一。如果一个数用三个符号来表示,从高位到低位的3个符号依次是d2,d1,d0. 则d2,d1,d0这三个符号表示数:(d2 * 10^2) + (d1 * 10^1) + (d0 * 10^0)。

 

  2进制

  10进制,对人类来说,10进制是最直观表示法。但对计算机来说,2进制才是最方便的表示。2进制仅仅需要2个符号,故2进制的物理表示仅需要2种状态,这对电子器件来说十分方便的,如高电平和低电平,电路的通与断,电路也会大大简化。同时,在逻辑运算中(数学中有一个分支叫布尔代数)也仅仅需要2个值,即真和假,所以使用2进制作为计算机的基础,不仅可简化电路设计,也可以很好的处理逻辑运算。逻辑运算包含逻辑加(or),逻辑乘(and),逻辑反(not),异或(xor)等。 各种CPU一般都支持各种算术运算(如加,减,乘,除),逻辑运算以及移位运算。移位运算和逻辑运算一般通称位运算。2进制只需要2个符号0和1,大于2的数必须使用多个符号来表示,2进制的特点是逢二进一。一般说来,表示2^n以内的整数之多需要n位2进制数来表示。一位2进制数叫1个比特,英文名称为bit,8位2进制数组合起来叫做1个字节(byte),可表示0到2^8-1的数。16位2进制数做和起来叫做1个字(word),可表示0到2^16-1(65535)之间的数。32位2进制数叫做1个双字(dword),可表示0到2^32-1的数。64位2进制叫做一个qword。

 

   我们常常听到8位机,16位汇编,32位汇编,32位操作系统,32位CPU,64位操作系统这样的说法。这里的位就是2进制的位。16位汇编指供16位CPU使用的

汇编语言,对应的机器指令是16位机器指令。32位操作系统是指,在这样的操作系统上,可以运行32位机器指令, 相应的地址空间可达2^32. 由于操作系统和CPU都是向下兼容的,故32位操作系统(如windows 95,windows xp)下仍可运行16位的dos程序, 甚至于CPU是是64位的CPU.

   一点儿题外话. 现在主流的CPU,操作系统,编译器都是32bit的(实际上最新的CPU是64位的),窃以为,16位汇编语言是过时的,只学习32位汇编语言即可. 但教科书往往是过时的,初学者仍然不得不学已经趋于淘汰的16位汇编,编写16位DOS程序.

 

  数的内部表示和字符串表示.

  上文提到,1个0-65535之间的数需要2个字节来表示,1个0-4294967295的数需要4个字节来表示. 前者可以放入1个16bit的寄存器,如ax,bx,cx,dx,si,di,sp,bp,后者可以放入1个32bit的寄存器,如eax,ebx,ecx,edx,esp,ebp,esi,edi. 由于寄存器名称本身已经隐含了数的大小(这里指是16bit格式还是32bit格式),所有通常无需特别的修饰,但如果操作数是内存表示,必须使用修饰符,如1个名为n的32bit整数,想要比较它是否等于0,在x86汇编,你需要这样写"cmp dword ptr [n],0",这里"dword ptr"表示n是一个32bit的数. 数的内部表示是最经济的表示法,具有占用空间少且长度固定的优点. 如一个32bit的数总是占用4字节. 但这种表示法也有缺点,就是不能直接显示,必须把它转化为字符串来输入和输出. 如数1000000000,它的内部表示仅需4字节.如果表示为16进制串,它是"3B9ACA00", 这个字符串占用8字节,如果表示为10进制串,它是"1000000000",这个字符串占用10字节. 而对于数1,无论是16进制还是10进制,它的字符串表示形式总是相同的,仅仅占用1个字节.

 

 为什么需要进制转化

 因为数的内部表示总是紧凑的2进制表示,但在输入输出中使用的字符串形式是16进制或者10进制的,所有在输入和输出时必须作进制转化. 同时,输入和输出必须使用操作系统提供的系统调用或者高级语言提供库函数来实现. 高级语言提供了各种各样的库函数,相当比重的库函数是和输入输出相关的.同时,我们也可以在汇编语言和高级语言中使用操作系统提供的系统调用来实现. 例如,如果输出一个字符串在标准输出设备. 你可以使用下列方法来做.

  1. 在C语言中,调用标准库函数printf

  2. 在16位dos环境, 可使用Dos调用9号功能

  3. 在32位windows环境,可使用Windows API 函数MessageBox,输出字符串在新窗口

  4. 在32位windows环境,可使用Windows API 函数WriteFile, 第一个参数为标准输出设备,输出字符串到控制台.

  printf函数除了可输出字符串外,甚至可将一个整数或者浮点数转化为字符串输出.如printf("%d",n),可将整数转化为10进制字符串并输出. 所以对于C语言,你可能不需要进制转化,printf可以代劳。当然,如故你觉得printf的性能不能令你满意,你也可以编写一个转化函数。 但是,对于汇编语言,就没有这么方便了,许多汇编初学者遇到第一个困难的问题就是如果将整数转化为字符串输出. 为此,你得首先知道计算机中的字符是怎样表示的. 目前,计算机的西文符号最普通的表示法是ASCII,这套符号体系可表示128个符号,每个符号为1字节,其编码为0-127,最高bit为0. 这128个符号可表示26个大小写字符,0-9这10个数字,以及一些符号和不可显示的控制字符,如回车(16进制形式为0d). 响铃(16进制形式为07). 数0需要显示为符号'0'. 如在8086汇编语言中,一个0-9之间的数,存放在al寄存器,你需要将其转化为字符串,存在以di开始的内存空间,你需要类似这样的2条汇编指令, "add al, 48"和 "mov byte ptr [di],al".为什么要加上48呢?因为符号'0'的ASICII码是48,当然,上面的指令也可以写为 "add al, '0'" 或"add al, 30h".它们是完全等价的.

 

 转化1个数为R进制字符串

 如果一个数小于n,转化为n进制非常后只有1个字符,非常简单。上文就是1个例子。本节讲述普通的数如何转化为1个R进制的字符串. 1个数n转化为R进制的

字符串表示,采用除r取余法,重复的计算n % r的余数和n/r的商,依次得到R进制的各个字符,值得注意的是,得到的R进制的各个字符顺序是从低位到高位,

这和我们平时书写的顺序是相反的,为了和书写顺序保持一致,在最后阶段需要将字符串首尾交换。

 步骤1:  将缓冲区首地址p和head

步骤2: 

    c取n除以r的余数,即c=n % r

    将c存入p处

    p前进一个位置, 即p=p+1;   

    n取n除以r的商, 即n=n/r;   

 步骤3:    如果n大于0,继续重复执行步骤2

 步骤4:

    将字符串倒置,即末字符和第1个字符交换,倒数第2个字符和第2个字符交换,依次类推。如"12345"经过倒置后变成"54321"

   到此为止,以head开头长度为p-head的字符串即为n的R进制表示。   

 

下面是该算法的c语言代码,以16进制为例

char hextab[]=
{
       '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'
};

// 将数n转化为16进制字符串,存入buff
void toHex(int n,char buff[])
{
       char *p=buff;
       char *head;
       do 
       {
              int c;
              c=n % 16;
              *p=hextab[c];
              p++;
              n=n / 16;
       } while (n>0);

     

     *p=0;   //c语言字符串结束标志
       p--;  //p指向最后一个字符
       head=buff;
      while (head

 这里再给出转换为10进制字符串的c语言代码

// 将数n转化为10进制字符串,存入buff
void toDec(int n,char buff[])
{
       char *p=buff;
       char *head;
       do 
       {
              int c;
              c=n % 10;
              *p=c+'0';
              p++;
              n=n / 10;
       } while (n>0);
       

     *p=0;   //c语言字符串结束标志
       p--;  //p指向最后一个字符
       head=buff;
      while (head


 下面是该算法的16位汇编语言代码例子,以10进制为例  

TITLE toDec
        .model small

stack segment
       db 100 dup(?)
stack ends  


data segment
       n         dw    ?
       out_buff  db      16 dup (?)
data ends
 

code segment
assume cs:code,ds:data,ss:stack

 start:
       mov ax,stack
       mov ss,ax
       mov ax,data
       mov ds,ax

       mov  ax,9     ;here you can change 9 to any value
       mov  word ptr n,  ax
       
       mov   di, offset out_buff
       call  to_DecString
       mov   dx, offset out_buff

       mov   ah, 9h
       int   21h

       mov  ax,4c00h                ; terminate program
       int   21h
 

;conver a number result to output_buffer and fill 0x24 after number string
;function:
;    conver 16 bit integer result to string
;input parameter:
;     none, always convert varible n

;output parameter
;     di: The address of output buffer


to_DecString   proc
       push di        ;di is the address of out_buff   
       mov  bx,10    

convert_loop:
       xor  dx,dx     ;0 -> dx
       mov  ax,word ptr [n]
       div  bx
       mov  word ptr [n],ax

       add  dl,'0'        ;save (result % 10)+'0' to out_buffer
       mov  byte ptr [di],dl
       inc  di            ;di point next position   
cmp_r:
       cmp  word ptr [n],0
       jnz  convert_loop

inv_string:         ;swap string head and string tail
       dec  di     ;the di point to the last char
       mov  byte ptr [di+1], 24h    ; For dos ah=09, string output, the terminal char must be 0x24
       pop  si    ;now si is the address of out_buff

 inv_loop:
       mov  al,[si]    ;swap char
       mov  ah,[di]
       mov  [di],al
       mov  [si],ah
       inc  si
       dec  di

cmp_head_tail:
       cmp  si,di
       jb   inv_loop
       ret
to_DecString   endp

code    ends
       end     start


  转化R进制字符串为1个数

 1个R进制字符串转化为1个数,采用乘r加“字符”法,重复的计算n乘以R的积并加上当前字符,直到当前字符不是一个有效的R进制字符。

 步骤1:   将字符串首地址送p,  n=0

 步骤2:    如果 p指向的字符不是R进制字符,转步骤4

 步骤3:  

   n和r相乘,结果仍然存入n

   n和p指向的字符对应的数相加,结果仍然存入n

   p前进一个位置, 即p=p+1;   

   转步骤2    

 步骤4:  现在n已经是转化好的数

  

这里是10进制字符串转换整数的c语言代码 

//转换decString到一个整数并返回
int decString2Number(char *decString)
{

       int n=0;
       char *p=decString;
       while ( *p>='0' && *p<='9')
       {
              n*=10;
              n += (*p-'0');
              p++;
       }
       return n;
}
  

下面给出一个16位dos汇编代码,过程input_num从标准输入设备输入一个字符串,然后转换这个字符串到16bit整数,并存储在ax寄存器

; 传入参数: si, 输入缓冲区地址, [si]存储随后缓冲区的大小
; 传出参数: ax, 转化后的数
input_num   proc
       mov    dx,si
       mov    ah,0ah
       int    21h

       add    si,2    ;skip the first 2 char and point to the first input char
       xor    ax,ax   ;ax return value
       xor    cx,cx   ;clean cx
       mov    bx,10

loop_read_char:
       mov    cl,byte ptr [si] ; get a char from in_buff
       sub    cl,'0'
       cmp    cl,9
       ja     input_exit    ;if the char <'0' or >'9', then jump out loop 

       mul    bx
       add    ax,cx

       inc    si
       jmp    loop_read_char

input_exit:
       ret
input_num   endp 


说明,上面的代码稍稍使用了一些技巧,判断字符是否在'0'到'9'之间只用了一条比较命令,下面解释一下。

字符大于'9'则跳出循环,应该容易理解。但如果字符小于'0',是否也会跳出循环呢。答案是Yes. 如果字符cl小于'0',在执行完"sub cl,'0'"后,cl是一个负数,8bit的负数看作无符号数时,它一定比9大,所以同样会跳出循环。

   结束语,就到此为止吧。如果你想要一个包含输入字符串转化为数,做某种计算,再转化为字符串输出的16位汇编程序的例子,请在帖子《我编的十进制乘法运算程序 可以4乘4 不知道哪有错误 请指教~~》查看完整的代码. 

你可能感兴趣的:(汇编语言)