您的位置:首页 > 编程语言 > C语言/C++

用C语言和汇编语言实现将1个整数分解成几个素数的乘积

2012-06-13 23:08 471 查看
任何1个大于2的整数都可以分解成几个素数的乘积。将1个整数分解成几个素数的乘积是个热门话题。

经常有人问到。本文试图用C语言和32位X86汇编语言给出一个比较好的实现。希望对c语言学习者和

汇编语言学习者带来帮助。

问题的提出:将一个整数分解素因数

根据算术基本定理,任何大于2的正整数都可以表示为素数的乘积,如果不考虑这些素数出现的次序,其

表示方式为唯一的。本题目的要求是输入一个正整数,将这个正整数显示为几个素数乘积的形式,如



1=1

2=2

3=3

4=2*2

5=5

6=2*3

7=7

...

60=2*2*3*5

算法:

对于合数n,它总可表示为两个数的乘积,如n=a*b,令a<=b,容易证明a <= sqrt(n),这里sqrt(n)表示n的

平方根,下同。这说明了,用i =2 到 sqrt(n)之间的数逐一试除,如果所有的i都不是n的因数,则n一定

是一个素数。如果在试除过程中,某个i能够整数n,则将i存入因子表,n/i => n,直到 i*i大于n为止。

在试除过程中,若p和q都是n的因子,p是素数,q大于p,q是p的倍数,如果p能够整除n,则n = n / p,

这样,最终n就不再是p的倍数,故n也不会是q的倍数,故在计算n = n / p这一过程,p一定是一个素数,

这种方法得到的所有因子都是素因子。

在所有的试除方法中,最少的试除步骤是用2到sqrt(n)之间的所有素数去试除。但这个算法需要预先算出

sqrt(n)之间的所有素数。这是最快的算法,但我们这里不打算搞得这么复杂,我们这里的算法是用2和所有2

以上的奇数去试出。因为如果一个数是4,6,8的倍数,那么他一定是2的倍数,当被2除后,就不需要试再试

除4,6,8了。



下面给出一个C语言的实现

#include <stdio.h>
#include <stdlib.h>
typedef unsigned long DWORD;

//函数decomp_integer对n分解素因数,分解后的素数存入facArr数组,并返回因子的个数
int decomp_integer( DWORD n, DWORD facArr[])
{
    DWORD fac;      //n的可能的一个因子
    int count;

    if (n<4)        //4以内的数,不需要分解
    {   
        facArr[0]=n; return 1;  
    }

    count=0;
    //下面的while循环为2试出n,直到2不是n的因子为止
    while ( (n & 1)==0) // 这里判断偶数用 (n &1)==0,这比(n % 2)==0更快
    {
        facArr[count++]=2;  n/=2;
    }

    fac=3;  //用3到sqrt(n)之间的奇数试除
    while (fac*fac<=n) // fac*fac <= n
    {
        while (n % fac==0)
        {
            facArr[count++]=fac;
            n /= fac;
        }
        fac+=2;
    }
    if (n==1)
        return count;

    facArr[count++]=n;
    return count;
}

int main(int argc, char* argv[])
{
    DWORD n,facArray[32];
    int i,count;

    printf("Please input unsigned integer:");
    scanf("%u",&n);
    count=decomp_integer(n,facArray);
    printf("%u=%u",n,facArray[0]);
    for (i=1;i<count;i++)
        printf("*%d",facArray[i]);
    printf("\n");
    return 0;
}

汇编语言可能是最麻烦的语言,在实践中,除非必要,好少有人用汇编语言写程序。这主要是因为

1.同样的题目,用汇编语言编程需要的代码量较大,费时费力

2.汇编语言的可读性很差,日后维护的成本也大。

许多有经验的工程师对汇编语言写程序都很头疼,对许多初学这说,更是一件难以完成的任务。

初学者常犯的错误是,一上来就有汇编写程序,不管有没有理清头绪,这样造成的结果就是,程序改

了又改,但总是不对,好容易改对了,但程序的风格很差,代码冗长,逻辑混乱,可读性很差。其实,

用任何语言写成都需要设计,程序应该具有模块化,条理清晰,风格一致。汇编语言是最接近机器的

语言,编写汇编程序,非常有利用理解计算机的工作原理。

个人觉得,对于一个逻辑比较复杂的汇编程序的设计,不妨采用高级语言为原型,用高级语言编译运

行通过了,说明你也就对这个题目理解了,然后,看看你的程序,哪里可以简化,哪里可以重构使其

更优雅。最后可将你用高级语言写的程序翻译成汇编语言,这里的翻译不是简单的翻译,汇编语言有

汇编语言的特点,他具有更大的灵活性,开发者应该总是用最适当的方式来实现它。

下面是这个题目的汇编版本,使用masm32编写。关于输入输出部分,使用的几个Windows API函数,

这里说明如下:

程序使用ReadFile从控制台输入数据,使用WriteConsole输出运算结果到控制台。字符串到数的转化

子程序是自己写的。见myAtoi。而数到字符串的转化调用的则是Windows API函数wsprintf。此外,程

序还用到GetStdHandle和ExitProcess,感兴趣的同学,可以查MSDN来得到更多的信息。

在C语言版,外循环的判断语句"while (fac*fac<=n)"使用了乘法,而汇编语言班则使用了性能更好的lea

指令,见下

mov edx,s_fac

mov eax,fac

lea edx,[edx+eax*4+4] ; (fac+2)*(fac+2)=s_fac * 4*fac+4

mov s_fac,edx

add fac,2 ; fac +=2



其基本原理为,若s_fac是fac的平方,则在求(fac+2)^2时,使用了数学公式(fac+2)^2= fac^2 + 4*fac +4,

这里的^表示乘方,程序中,fac和s_fac分别表示当前因子和当前因子的平方,然后当前因子加2,同时需要

更新s_fac,在上面的代码中,先计算(fac+2)^2,(fac+2)=fac^2 + 4*fac +4=s_fac+ 4*fac +4,然后计算fac+2.

lea指令支持*4,*8这样的形式,故这里使用lea指令而没有时候性能很慢的乘法指令。

下面给出汇编语言版的代码,可借助c版的程序来理解汇编版,同时请体会汇编语言和c语言在处理逻辑方面的

的不同,特别是分支和循环语句。

; 如何编译链接该程序
; 本程序需使用masm32来编译,若你的masm32安装在c盘,请设置如下环境变量
; set include=c:\masm32\include
; set lib=c:\masm32\lib
; set PATH=c:\masm32\bin;%PATH%

; 若本程序名为decomp.asm,则运行如下命令编译和链接和运行
; ml /c /coff decomp.asm
; link /subsystem:console decomp.obj
; decomp.exe

.386
.model flat,stdcall ; 32 bit memory model
option casemap :none  ; case sensitive

include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib

.data
        facTab   dd  32 dup(?)     ;number的所有素因子
        facCount dd  ?             ;number的所有素因子的个数
        
        number   dd   ?            ;被分解的数
        fac   	 dd   ?         ;number的当前因子
        s_fac    dd   ?            ;number的平方
        
        tLen     dd   ?            ;临时变量,仅在调用Windows API函数是用
        hInput   dd   ?            ;标准输入设备句柄
        hOutput  dd   ?            ;标准输出设备句柄
        
        inBuffer  db    32 dup(?)  ;输入缓冲区
        outBuffer db    128 dup(?) ;输出缓冲区
        
.const
       szFmtIn    db    '%u',0
       szFmtOut1  db    'Please input an unsigned integer:'
       szFmtOut2  db    '%u=%u',0
       szFmtOut4  db    '*%u',0
      
.code

;use global varible, don't pass any in parameter in stack
dcomp proc far uses esi
    mov  esi,number     ;esi is copy of n
    cmp  esi,4
    jae  case_02
 
case_01:
    mov  facTab[0],eax
    mov  ecx,1
    jmp  this_exit
 
case_02:
    xor ecx,ecx        ;ecx: copy of count
    jmp loop_02_cmp
    
loop_02:
    mov facTab[ecx*4], 2
    inc ecx            ;count++
    shr esi,1          ;esi is copy of n,n /=2

loop_02_cmp:
    mov edx,esi     
    and  edx, 1
    jz  loop_02        ; n % 2==0
 
    mov fac,3
    mov s_fac,  9
    jmp loop03_outer_cmp

loop03_outer:
    jmp loop03_inner_cmp
    
loop03_inner:
    mov edx, fac
    mov facTab[ecx*4],edx   ;fac[count++]=fac
    inc ecx                 ;count++
    xor edx,edx
    mov eax,esi
    div fac
    mov esi,eax
    
loop03_inner_cmp:
    xor edx,edx
    mov eax,esi
    div fac
    or  edx,edx
    jz loop03_inner
    
    mov edx,s_fac
    mov eax,fac
    lea edx,[edx+eax*4+4] ; (fac+2)*(fac+2)=s_fac * 4*fac+4
    mov s_fac,edx
    add fac,2           	; fac +=2
    
loop03_outer_cmp:
    mov eax,s_fac
    cmp eax,esi
    jbe loop03_outer
    
    cmp esi,1               ;
    jz  this_exit           ; n ==1
    
    mov facTab[ecx*4],esi
    inc ecx
    mov eax,ecx
    
this_exit:
    mov  eax,ecx       		;返回素因子数
    ret
dcomp endp

; convert from a string to a integer
myAtoi proc far C uses ebx esi, var_String
    mov    esi,var_String  ;in parameter => esi
    mov    ebx,10
    xor    eax,eax
    xor    ecx,ecx
    
loop_read_char:  
    mov    cl,byte ptr [esi] ; get a char from in_buff  
    inc    esi
    sub    cl,'0'  
    cmp    cl,9  
    ja     input_exit    ;if the char <'0' or >'9', then jump out loop   
  
    mul    ebx  
    add    eax,ecx  
  
    jmp    loop_read_char  

input_exit:
    ret 
myAtoi endp

; use global varible, don't pass parameter in stack
; number: in parameter
; facTab: in paramter
; facCount: factor count
; hInstance: input device handle

output_fac proc far C uses ebx edi
    lea    edi, outBuffer
    mov    eax,facTab[0]
    invoke wsprintf, edi, addr szFmtOut2,number,eax
    add    edi, eax
    
    mov    ebx,1
    jmp    next_cmp
    
convert_loop:   
    mov    eax,facTab[ebx*4]     
    invoke wsprintf,edi,addr szFmtOut4,eax
    add    edi, eax     ;edi the write pointer in next ; 下次输出时的地址送edi
    inc    ebx
next_cmp:
    cmp    ebx,facCount
    jb     convert_loop

print:  
    lea   eax, outBuffer
    sub   edi, eax  ;计算输出的字符串的长度,存入edi
    invoke WriteConsole,hOutput,addr outBuffer,edi,addr tLen,0    ;在控制台输出
    
    ret 
output_fac endp

main proc far
    invoke GetStdHandle,STD_OUTPUT_HANDLE   ; 获取控制台输出设备的句柄
    mov    hOutput,eax
    invoke WriteConsole,hOutput,addr szFmtOut1,sizeof szFmtOut1,addr tLen,0
    
    invoke GetStdHandle,STD_INPUT_HANDLE      ; 获取控制台输入设备的句柄
    mov    hInput, eax
    invoke ReadFile,hInput,addr inBuffer,sizeof inBuffer,ADDR tLen,NULL

    lea    eax, inBuffer        
    invoke myAtoi,eax       ;转化字符串为一个数
    mov    number, eax      ;将这个数存入number
    
    invoke dcomp            ;调用因子分解子程序,,各个因子存入facTab
    mov    facCount,eax     ;分解后的因子个数存入facCount
    
    invoke output_fac       ;调用output_fac打印number的分解式
    
    xor    eax, eax
    invoke ExitProcess,eax  ;退出进程
    ret 
main endp
end  main



liangbch@263.net, 转载请注明出处。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐