注册 登录
编程论坛 汇编论坛

大家来写写汇编小玩意(三)

Valenciax 发布于 2016-06-26 08:30, 10230 次点击
之前提了几个小题目,考的是写最短代码,这次来点别的,玩玩解题能力。

题目一:
先来百度:
考拉兹猜想,又称为3n+1猜想、角谷猜想、哈塞猜想、乌拉姆猜想或叙拉古猜想,是由日本数学家角谷静夫发现,是指对于每一个正整数,如果它是奇数,则对它乘3再加1,如果它是偶数,则对它除以2,如此循环,最终都能够得到1。
取一个数字。
如n = 6,根据上述公式,得出 6→3→10→5→16→8→4→2→1 。(步骤中最大的数是16,共有8个步骤)。
如n = 11,根据上述公式,得出 11→34→17→52→26→13→40→20→10→5→16→8→4→2→1。(步骤中最大的数是52,共有14个步骤)
考拉兹猜想称,任何正整数,经过上述计算步骤后,最终都会得到 1 。

题目来了。
输入:
输入需要验证角谷猜想的数n,0<n<1000000000(10亿),直到输入0或回车为止。

输出:
从数字n开始,依次输出经过变换后得到的每一个数字,数与数之间用“->”连接,直到输出1为止步。统计步骤中最大的数字m以及所需的步骤数x,然后在下一行输出如下引号中的信息:“Max is m and we need x steps.”。
对于步骤数的规定:我们约定,经过一次运算为1步,比如,在下面的输入样例中,从11到34就是第1步,如此循环,直到最后数字变成1为止。

比如:
11
输出:
11->34->17->52->26->13->40->20->10->5->16->8->4->2->1
Max is 52 and we need 14 steps.

其实逻辑挺简单的,这是我草草写的一个。
只有本站会员才能查看附件,请 登录



题目二:
1. 键盘输入的两个日期(日期为8位数的年月日),求出之间相差几天。
2. 在输入日期前要有提示信息(如:Input First Date / Input Second Date)

这题也很简单,重点是计算闰年,设定一个参考点....验证可用excel
一如既往,我也写了一个。

只有本站会员才能查看附件,请 登录


有趣兴可以写写,贴出代码,或者说说想法也可以。



25 回复
#2
wmf20142016-06-26 20:36
感谢Valenciax为活跃本版所做的努力!
这两题要汇编做,已经很复杂了。估计没什么人接题,抽空我做下吧,不过第一题我只能用16位做,数据范围就是1-65536/3的范围(没有、也不知道怎么写32位汇编
#3
Valenciax2016-06-26 23:02
回复 2楼 wmf2014
谢谢班竹参与,国内汇编论坛一向冷清,反而国外比较热闹些,仍然有一群热心人写,他们许多人由dos写到win32和win64,x86写到sse4………

题一不会很复杂,64k内自然可以,至于如何读入大数,如何显示大数(甚至几百几千位),我会在接下的贴子跟大家分享,当然若有人写出来,那就更好了。

题二较难些,我会随后贴出代码和一些心得。



#4
hu9jj2016-06-27 06:25
围观,暂时没空参与讨论,先看看热闹。
#5
Valenciax2016-06-27 07:53
回复 2楼 wmf2014
班竹常提到的32BIT,其实之前写的一直是DOS的16BIT代码,只是偶然用到32BIT暂存器而已,题一写的也是DOS程式。一时起意,刚才用WIN32的控制台写了一个,用的是MASM32安装包里的MASM32 Editor编写,因为利用宏,输入输出借用了函数库,反而比DOS简单的多,更且可以用中文。
程序代码:

NLimit equ 999999999
include \masm32\include\masm32rt.inc
.stack 500h
.data
     Result dd 0,0
     myStep dd 0,0
     MaxValue dd 0,0
     output db "%I64i", 0 ;64bit format
     crtf db 10,13,0
.code
start:
     xor eax,eax
     lea edi,Result
     mov ecx,6
     rep stosd
     invoke atol, input(10,13,"角谷猜想 (1-999,999,999):") ;get input
     cmp eax,1  ;is it 1
     jb quit ; less then , quit
     cmp eax,NLimit ; is it input limit ?
     ja start ;yes
     mov Result,eax ;save
next:  
     lea esi,Result  ;get last value address
     invoke crt_printf, offset output, qword ptr [esi] ;print it
     cmp Result,1  ;is it 1 ?
     jz next9  ;yes
     print ""  
     mov eax,Result ;get last value
     test eax,1  is it Even
     jz next2 ;yes , EVEN !
     ;-- ODD --
     add eax,Result
     add eax,Result
     inc eax  ; x 3 + 1
     cmp eax,MaxValue ;最大值 ?
     jbe next4 ;no
     mov MaxValue,eax ;save it 最大值
     jmp short next4
next2: ; -- EVEN
     shr eax,1  ; /2
next4:
     mov Result,eax  ;save it
     inc myStep ;加步
     jmp short next ; looping....
next9:
     invoke crt_printf,addr crtf  ;other method to print 回车
     print "最大值 "
     lea si,MaxValue
     invoke crt_printf, offset output, qword ptr [esi] ;print最大值
     print ""
     lea si,myStep
     invoke crt_printf, offset output, qword ptr [esi] ;print 步
     print ""
     jmp start
quit:
     exit  ;結束
end start

只有本站会员才能查看附件,请 登录


[此贴子已经被作者于2016-6-27 10:06编辑过]

#6
zhulei19782016-06-27 11:42
#7
wmf20142016-06-27 11:48
我知道:所谓暂时没空参与=永远没空参与但有空围观!
好多人已过了有动力追求结果的年纪了,我也早过了。
作为写程序相当业余的我就处在这种状态:看到别人写得好的程序,会在心里酸酸地认为,这有什么了不起的,只要我下番功夫,肯定写得比他好。然后就永远不会下番功夫了。
5楼的代码这么简练?不需要考虑输入、输出,也不需要十进制-十六进制转换?我还在想怎么将键盘输入转变为16进制数呢。
#8
Valenciax2016-06-27 13:32
回复 7楼 wmf2014
对的,不用考虑太烦琐输入输出...
Masm32并不是微软官方出品,是一群有心人在维护,它们整合了masm32/64版,加了许多有用的宏,借用许多c标准函数库,比如上面5楼的atol、input和print等就是宏,atol是字符转long的意思,传回EAX的32bit值,一句宏相当于十数条汇编指令(我的dos版,光输入32值连容错代码就要三十多行),赶得上高阶语言的方便!

[此贴子已经被作者于2016-6-27 13:33编辑过]

#9
Valenciax2016-06-27 22:00
先发个子程序,若用int21h,ah=0AH读入字符串,这子程序能把它
转换成32bit数值传回,换言之,最多读到FFFFFFFFh,即4294967295

分两个版本
版本一只用16bit暂存器,由dx:ax传回
版本二用32bit暂存器,由EAX传回
使用方法,si指向输入缓冲,呼叫子程式即可
程序代码:


 lea dx,InputBuffer  ;指向输出缓冲
  mov ax,0c0ah  ;输入函数,先清空键盘缓冲
  int 21h

 ..

 ..

 ..

 lea si,InputBuffer + 2

 Call GetValue32 或 Call GetValue32x



16bit暂存器版
程序代码:

;--------------------------------------------------
;
转值子程序,把输入的10进制文字转成16进制
;
输入:ds:si数字字符串起点,以0dh或0结束
;
输出:ds:ax=转换后的32进制值,cf=1表示有非数字字符
;
若输入值少于65535,则dx=0
;
--- 16bit暂存器版---------------------------------
GetValue32:

 push bx

 push cx

 push si

 push di

 push bp

 xor cx,cx

 xor di,di

 xor dx,dx

 mov bx,10
d10:

 lodsb

 cmp al,0dh ;若以0d为结束符,启用这句,关闭下句
;cmp al,0  ;若以0为结束符,启用这句,关闭上句
jz d20

 sub al,'0'

 cmp al,9

 jbe d15
d12:

 stc

 jmp short d99
d15:

 cbw

 mov bp,ax ;store to bp
mov ax,cx ;get cx
mul bx ;x 10
mov cx,ax ;store to key hi
mov ax,bp ;restore from bp
xchg ax,di ;get key lo
mul bx ;x 10
xchg ax,di ;store to key lo
add di,ax ;add low byte to key lo
adc cx,dx ;add to key hi
jnc d10

 jmp short d12
d20:

 mov dx,cx

 mov ax,di

 clc
d99:

 pop bp

 pop di

 pop si

 pop cx

 pop bx

 ret


32bit暂存器版
程序代码:

;--- 32bit暂存器版---------------------------------
;
转值子程序,把输入的10进制文字转成16进制
;
输入:ds:si数字字符串起点,以0dh或0结束
;
输出:eax=转换后的32进制值,cf=1表示有非数字字符
;
若希望由ds:ax传回,可加三句于ret之前
;
mov edx,eax
;
shr edx,16
;
and eax,0000FFFFh
;
--------------------------------------------------
.386
GetValue32X:

 push ebx

 push edi

 xor ebx,ebx

 mov edi,10
GetV10:

 lodsb     ;指向起点
and eax,000000ffh ;清除高bit,保留AL
cmp al,0dh ;若以0d为结束符,启用这句,关闭下句
;cmp al,0  ;若以0为结束符,启用这句,关闭上句
jz Getvx

 sub al,'0'

 cmp al,9

 ja Getvy

 xchg ebx,eax  ;交换
mul edi

 add ebx,eax  ;累加
jmp short GetV10
Getvx:   

 mov eax,ebx ;ascii转值后由eax转回
clc ;成功cf=0
jmp short Getvz
Getvy:

 stc  ;错误 cf=1
Getvz:

 pop edi

 pop ebx
; mov edx,eax
;
shr edx,16
;
and eax,0000FFFFh
ret



[此贴子已经被作者于2016-6-27 22:06编辑过]

#10
Valenciax2016-06-28 09:49
再发一个印出32位的代码

使用方法
 mov dx, yyyy
 mov ax, xxxx
 call  OutDec  ;印出dx:axh的 32值,若只输ax, dx必先清0
程序代码:

;-----以十进制输出DX:AX的32BIT值------
;
输入 :dx=32bit高位,ax=32bit低位
;
输出 :标准输出设备(屏幕)
;
破坏暂存器:除dx,ax外,无
;
注:若只输ax,dx先清0
;
-------------------------------------
OutDec:     
    push bx
    push cx
    push si
    push bp
    xor cx,cx
    xchg bp,dx
    mov si,10      ;div by 10
    mov bx,30h  ;'0'
OutDec1:   
    or bp,bp    ;是否已除完高位?
    jz OutDec3  ;yes
    xchg bp,ax
    xor dx,dx
    div si   ; /10 ,利用除10取余法,逐步push入个位
    xchg bp,ax
    div si
    or dl,bl  ;转ascii
    push dx
    inc cx
    jmp short OutDec1
OutDec3:   
    xor dx,dx
    div si
    or dl,bl
    push dx
    inc cx
    or ax,ax  ; 是否已除完低位?
    jnz OutDec3  ;no
OutDec4:    ;到此,高低已除完! cx=有效个数
    pop ax   ;后入先出,分别pop回最前位,然后一直到个位
    int 29h  ;可改用int10h,ah=0eh,印出al, 或pop dx,用int21h,ah=2印出dl
    loop OutDec4
    pop bp
    pop si
    pop cx
    pop bx
    ret
;--------------------------------------


[此贴子已经被作者于2016-6-28 09:54编辑过]

#11
wmf20142016-06-28 13:01
回复 9楼 Valenciax
咦?其实就是atoi函数,这就简单了。
我想复杂了,还在考虑将输入转换为bcd码,还是不是要用什么aam、aad进行asc码调整,想到这些指令从未用过(其实以前写汇编算术运算最多无符号加减),就迟迟没动手了
最近真的事多,体制内的单位总是忙着搞半年度材料,很不幸地我就是那个写材料的最底层的那个。无暇写这些作业,容后再说了~~~~
#12
zhulei19782016-06-28 15:24
这是作业吗,没人给你布置作业啊
#13
Valenciax2016-06-29 07:37
题一有了9/10楼的输入/输出子程序,写代码就不是难事,再不明白可参考5楼的写法。

接下来说说题二
重点是解决闰年的判断,先来一段百度
西元年分除以400可整除,为闰年。
西元年分除以4可整除但除以100不可整除,为闰年。
西元年分除以4不可整除,为平年。
西元年分除以100可整除但除以400不可整除,为平年

好了,有了定义,下面我写了一段判断闰年的子程序。

用法:
MOV AX,YEAR (待查的年份)
CALL LeapYear

传回AX=1(闰年)
传回AX=0(非闰年)

程序代码:

LeapYear:  ;輸入AX=年份,輸出AX=1(閏年),AX=0(非閏年)
push bx

 push dx

 push si

 mov si,ax

 mov bx,400 ;年份除以400餘數等于零,為閏年
mov dx,0

 div bx

 or dx,dx ;是否0
jz last ;餘數為零,直接輸出閏年
mov dx,0

 mov bx,4 ;除以400餘數不等于0,進行除以4餘數是否等0的判斷
mov ax,si

 div bx

 or dx,dx  ;是否0
jz ok ;除以四餘數等于0,跳往除以100餘數是否等0的判斷
over:
;除以四餘數不等于0,則不為閏年,結束
mov ax,0

 jmp nil

ok: mov bx,100 ;除以100餘數為零,則不為閏年,餘數不為零,則為閏年
mov dx,0

 mov ax,si

 div bx

 or dx,dx ;是否0
jz over
last:

 mov ax,1
nil:

 pop si

 pop dx

 pop bx

 ret


然后是2月的28/29问题
我们先建立一个月份的天数阵列
monthday dw 31,28,31,30,31,30,31,31,30,31,30,31 ;月份天数

这其中,除了二月都是恒定的,但可以根据使用者输入的年份去加或不加二月。
若是闰年,二月的28加1,否则不加。
拿这个表格,还可以判断使用者输入的日期是否合法,若输入2015/2/29,查知该年2月是28天,不符!
若输入2016/2/29,查知该年2月是29天,通行!

接下来,我们定义一个参考点,就1000年吧,若输入2016/2/29。
计算方法是:
1.
定义一个双字的总年份 TOTALYEAR DD 0
由年份1000起,AX=年份,呼叫LeapYear子程序得知该年是否闰年,
是则TOTALYEAR + 366, 否则 + 365
年份+1,回到1再加,直到输入年份的前一年,2015。
2.
计算该年的总天数。
由输入月份的前一月加起,根据表格monthday的该月天数加,直加到1月,再加上输入日期,
就是该年的总天数。
3.
该年的总天数加上1项的天数就是[使用者输入年月日]和1000/1/1相差的天数。
4.
将使用者输入的两个日期相减,就是两个日期的[相差值]。
5.
印出



原理说完了,有了LeapYear子程序,还怕写不出来?

#14
hu9jj2016-06-29 07:38
以下是引用zhulei1978在2016-6-28 15:24:14的发言:

这是作业吗,没人给你布置作业啊

这是自己给自己布置的作业,一则活跃论坛,二则练练手,开拓思路。
#15
Valenciax2016-06-29 07:40
回复 14楼 hu9jj
刚贴了点东西就看见班竹回话了,早上好
#16
zhulei19782016-06-29 11:09
Valanciax好积极啊,希望大家都能提高汇编水平
#17
hu9jj2016-06-29 21:17
以下是引用Valenciax在2016-6-29 07:40:58的发言:

刚贴了点东西就看见班竹回话了,早上好

我是蜻蜓点水,早上浏览一下就退出了,晚上再浏览一次,每次的时间不超过半小时,所以没及时回复。现在只好回复:晚安!
#18
wmf20142016-06-30 11:47
总算有点空了,交第一题作业。
未做全面测试,数据范围只能为1-21845,做32位的或多位的烧脑!另关于第二题:我曾经在c里用一行算术代码完成获取指定月份天数,在汇编里可能就复杂多了,空下来再弄吧!
程序代码:
    code segment
    assume cs:code,ds:code,es:code,ss:code
    org 100h
start:    mov dx,offset buf1
    mov si,offset buf1+2
    mov ax,0c0ah
slp1:    int 21h
    mov ah,2
    mov dl,10
    int 21h
    call atoi
    or ax,ax
    jz slp2
    cmp ax,21845
    ja slp2              ;数据合法性判断
slp5:    cmp ax,max
    jb slp6
    mov max,ax           ;得到最大数
slp6:    mov bx,ax            ;空出ax寄存器调int21用
    call hexdec          ;显示数据
    cmp bx,1
    jz slp3              ;最终数据是1,完成
    inc word ptr step    ;操作步数增1
    mov dx,offset disp5
    mov ah,9
    int 21h              ;显示->2361545863 余金平
    mov cx,1
    mov ax,bx
    and cx,bx
    jz slp4              ;转到对偶数的处理
    mov cx,3
    mul cx
    inc ax
    jmp slp5             ;*3+1
slp4:    shr ax,1
    jmp slp5             ;/2
slp3:    mov dx,offset disp6
    mov ah,9
    int 21h              ;回车换行
    mov dx,offset disp2
    int 21h              ;Max is
    mov ax,max
    call hexdec          ;显示最大数
    mov ah,9
    mov dx,offset disp3
    int 21h              ;and we need
    mov ax,step
    call hexdec          ;显示总步数
    mov ah,9
    mov dx,offset disp4
    int 21h              ;steps.
    jmp slp7
slp2:    mov ah,9
    mov dx,offset disp1  ;显示数据错误
    int 21h
slp7:    mov ax,4c00h
    int 21h
atoi:    ;本函数将asc串转换为16位int数,ds:si为asc串首,非数字转换结束,结果在ax中返回
    push si
    push bx
    push cx
    push dx
    xor cx,cx
    mov bx,10
ailp2:    mov ax,cx
    mul bx
    jc ailp1
    mov dx,ax
    xor ax,ax
    lodsb
    sub al,'0'
    cmp al,9
    ja ailp1
    mov cx,dx
    add cx,ax
    jmp ailp2
ailp1:    mov ax,cx
    pop dx
    pop cx
    pop bx
    pop si
    ret   
hexdec:    ;本函数将16进制数用十进制数形式显示出来,待显示的数在ax中
    push bx
    push cx
    push dx
    xor cx,cx
    mov bx,10
hdlp1:    xor dx,dx
    div bx
    add dl,'0'
    push dx
    inc cx
    or ax,ax
    jnz hdlp1
    mov ah,2
hdlp2:    pop dx
    int 21h
    loop hdlp2
    pop dx
    pop cx
    pop bx
    ret
    buf1 db 20,0,20 dup(0)
    max dw 0
    step dw 0
    disp1 db " Input Error! $"
    disp2 db " Max is $"
    disp3 db " and we need $"
    disp4 db " steps.",13,10,'$'
    disp5 db "->$"
    disp6 db 13,10,'$'
    code ends
    end start

只有本站会员才能查看附件,请 登录
#19
Valenciax2016-06-30 12:56
回复 18楼 wmf2014
很好,指令也很精简了

我也发一个dos版的,用的是32位暂存器(1-999999999),结果就是1楼的图,和5楼的不同,5楼是windows的控制台程式,只能在windows下运行,这个dos版在任何环境都可运行(win7/8 64bit需要dosbox或模拟器)
和楼上班竹的一样,这是com格式
Masm5.x要exe->com
Exe2bin xxx.exe
Masm6.1
ML /AT xxx.asm
程序代码:

    NLimit equ 999999999
    code segment
    assume cs:code,ds:code,es:code,ss:code
    org 100h
start:jmp short begin
     InputBuffer db 10,0,10 dup (0)
     DispStr db 10,13,'Input a number (1-999,999,999):$'
     CurrentValue dd 0
     Step dd 0
     MaxValue dd 0
     Dispstr1 db 10,13,'Max is ','$'
     Dispstr2 db ' and we need ','$'
     Dispstr3 db ' steps.','$'
     CtrF db 10,13,'$'
begin:cld   ;正向
    Call GetUserInput  ;输入子程序
    ;则对它乘3再加1,如果它是偶数,则对它除以2,如此循环,最终都能够得到1。  
.386
    cmp EAX,0
    jz QuitX
    mov  CurrentValue,EAX
    lea dx,CtrF
    mov ah,9
     int 21h
next:mov eax,CurrentValue
    xor edx,edx
    call print_dec
    cmp eax,1
    jz quit
    push eax
    mov al,26     ;->
    int 29h
    pop eax
    test eax,1
    jz next3 ;偶数
    ;奇数
next2:add CurrentValue,eax
    add CurrentValue,eax
    inc CurrentValue
    mov eax,CurrentValue
    cmp eax,MaxValue
    jbe next4
    mov MaxValue,eax
    jmp short next4
next3:   ;偶数
    shr eax,1  ;/2
    mov CurrentValue,eax
next4:inc Step
    jmp short next
quit:lea dx,Dispstr1
    call DispProc
     mov eax,MaxValue
    xor edx,edx
    call print_dec
    lea dx,Dispstr2
    call DispProc
     mov eax,Step
    xor edx,edx
    call print_dec
    lea dx,Dispstr3
    call DispProc
    mov ah,7 ;暂停
    int 21h
quitx:mov ah,4ch ;离开
    int 21h
;--------------------------------------------------------------------------
;
output EDX:EAX 输出64bit子程序 (0-18446744073709551615)
print_dec:pushad
     xor ecx,ecx
     xchg ebp,edx
     mov esi,10 ;div by 10
     mov ebx,30h
print_dec1:or ebp,ebp
     jz print_dec3
     xchg ebp,eax
     xor edx,edx
     div esi
     xchg ebp,eax
     div esi
     or dl,bl
     push dx
     inc ecx
     jmp short print_dec1
print_dec3:xor edx,edx
     div esi   
     or dl,bl
     push dx
     inc ecx
     or eax,eax
     jnz print_dec3
print_dec4:pop ax
     int 29h
     loop print_dec4
     popad
     ret
;--------------------------------------------------------------------------
;
转值子程序,把输入的10进制文字转成16进制
;
输入:ds:si数字字符串起点,以0dh结束
;
输出:eax=转换后的16进制值(32bit),cf=1表示有非数字字符
GetValue:push ebx
     push edi
     xor ebx,ebx
     mov edi,10
GetV10:lodsb     ;指向起点
     and eax,000000ffh ;清除高bit,保留AL
     cmp al,0dh ;回车?
     ;cmp al,0  ;-----zero
     jz Getvx
     sub al,'0'
     cmp al,9
     ja Getvy
     xchg ebx,eax  ;交换
     mul edi
     add ebx,eax  ;累加
     jmp short GetV10
Getvx:mov eax,ebx ;ascii转值后由eax转回
     clc ;成功cf=0
     jmp short Getvz
Getvy:stc  ;错误 cf=1
Getvz:pop edi
     pop ebx
     ret
;-------------------------------------------------------------------
GetUserInput: mov ah,9
     lea dx,DispStr ;提示
     int 21h
     lea dx,InputBuffer  ;指向输出缓冲
     mov ax,0c0ah  ;输入函数,先清空键盘缓冲
     int 21h
     lea si,InputBuffer + 2
     mov cx,0  
     mov cl,[si-1] ;取实际输入数
     jcxz GetUx  ;无输入
     Call GetValue ;取值子程序,ax传回该值,cf=1表示错误,可能输入非数字
     jc GetUserInput  ;输入错误,非数字
     cmp eax,NLimit   ;999999999 ?
     ja GetUserInput
     ret
GetUx:xor eax,eax
     ret
;-------------------------------------------------------------------
DispProc:mov ah,9
    int 21h
    ret
;-------------------------------------------------------------------
CODE ENDS
END START


[此贴子已经被作者于2016-6-30 13:06编辑过]

#20
wmf20142016-06-30 16:59
我的获取某月天数的函数,利用int 21h返回错误的特性,无需计算闰年,无需建立月份天数表(早期我用vb写数据库应用时用过这方法,也是利用错误捕获获得某月天数的):
程序代码:
redate:    ;本函数返回某年某月份天数,结果在ax中,调用bx:年,al:月
    push bx
    push cx
    push dx
    push ax
    mov ah,2ah
    int 21h
    pop ax
    push cx
    push dx           ;保存当前日期
    mov ah,2bh
    mov cx,bx
    mov dh,al
    mov dl,32
ymdlp1:    dec dl
    int 21h
    or al,al
    jnz ymdlp1
    mov bl,dl
    pop dx
    pop cx
    int 21h           ;恢复当前日期
    xor ax,ax
    mov al,bl
    pop dx
    pop cx
    pop bx
    ret
#21
Valenciax2016-06-30 17:36
回复 20楼 wmf2014
这个我也实作过了,但有限制的,好像是1970-2xxx,这个xxx记不起是什么,
但不会是999这么大,我改用计算就是不爽这个限制,基本上计算并无限制,
公元到几万年都没问题。

另有一点, int 2ah/2ch这些日期函数,在dosbox或模拟器或freedos等系统不一定有作用,结果是让人气结,不如直接计算。
#22
八画小子2016-07-01 15:25
回复 21楼 Valenciax
你写好程序,放在bochs上用啊。基本你用到的BIOS调用,它都有。
#23
wmf20142016-07-01 20:49
回复 21楼 Valenciax
在winxp里调整时间试了下,年份到2099年就回到1980了,不知道在int21h里会这样不。非要计算的话,用下面的代码即可,已验证:
程序代码:
redate:    ;本函数返回指定年、月份的天数,输入参数cx:年 dh:月,按int21h的2ah习惯给参数,天数从ax中返回
    push bx
    push dx
    push si
    push di
    xor ax,ax
    mov al,dh
    mov di,ax
    mov bx,30
    cmp di,2
    jnz relp1              ;不是2月,按普通月份处理
    mov bx,28              ;如是2月,则做闰年判断
    xor dx,dx
    mov ax,cx
    mov si,400
    div si
    or dx,dx
    jz relp2               ;能被400整除,是闰年
    xor dx,dx
    mov ax,cx
    mov si,4
    div si
    or dx,dx
    jnz relp1              ;不是闰年,按普通月份处理
    mov ax,cx
    mov si,100
    div si
    or dx,dx
    jnz relp2              ;不能被100整除,是闰年
relp1:    cmp di,8
    jb relp3
relp2:    inc di                 ;闰年和大于7月份的月份处理
relp3:    and di,1
    mov ax,di
    add ax,bx
    pop di
    pop si
    pop dx
    pop bx
    ret
#24
Valenciax2016-07-02 07:16
回复 22楼 八画小子
能不能调用是次要,主要是BIOS/DOS有年份的设定限制,任何高阶语言甚至EXCEL的日期函数都是这个限制,倒不如自己计算。

[此贴子已经被作者于2016-7-2 16:53编辑过]

#25
Valenciax2016-07-02 15:34
题二代码:

下面的版本和1楼写的不一样,为了代码的重用,主要几个功能都封装成子程式,
利用参数传递就能使用。
也因为要封装,所以用了invoke和local区域变量,须用masm6.x以后版本编译
编译方法:  ml  xxxx.asm

几个有点用的子程序,可以在其他程式使用
1.
invoke GetmonthDay, month
输入月份得出该月份最大天数,month可以是暂存器,变量或立即数,可混用。
比如:
invoke GetmonthDay,ax    ;ax=输入月份,传回该月最大天数
invoke GetmonthDay,12    ;输入月份=12(立即数),传回12月最大天数=31

2.
GetTotalDay year,month,day
输入年月日,得出该日期和1000/1/1相差的总天数由dx:ax传回
year,month和day可以是暂存器,变量或立即数,可混用
比如:
invoke GetTotalDay,ax,bx.cx ;ax=年,bx=月,cx=日
invoke GetmonthDay,2016,12,29   ;传回该日期1000/1/1相差天数,由dx:ax传回

大家或者有疑问,这子程序只有这条题目才适用吧,比如只想计算某年某月某日的总天数,岂不得物无所用?
换一个想法,把该年该月该日当参数求得A值,该年1月1日作参数求得B值,A-B+1,就是该年该月该日的总天数了。
虽然是绕了一个圈子,但高阶语言/WINDOW API等,呼叫一个函数难道绕的圈子少了,这是封装程序无可避免的代价。

3.
invoke GetDate,address
address缓冲地址,传回ax=年,bx= 月,cx=日,dx= 0 (非闰年), =1(闰年)
用法:
先在资料区定义一个缓冲 
InputBuffer db 11,0,11 dup (0)
...
...
lea mov si,InputBuffer
invoke GetDate,si
若使用者若输入31/11/2016
则ax=2016,bx=11,cx=31,dx=1(闰年)
若使用者输入29/2/2015或31/11/2018等错误日期,则传回cf=1

4.
LeapYear子程序
使用方法
mov ax,年份
call LeapYear ;传回AX=1(闰年),AX=0(非闰年)

程序代码:

;程式功能:输入两个日期,计算两个日期相差天数
;
因用了invoke和local区域变量,须用masm6.x以后版本编译
;
编译方法:  ml  xxxx.asm
;
.MODEL  small,stdcall
.data

 InputBuffer db 11,0,11 dup (0)  

 FirstStr db 10,13,'Input First Date(dd/mm/yyyy):','$'

 SecondStr db 10,13,'Input Second Date(dd/mm/yyyy):','$'

 DayDiffStr db 10,13,'Difference:'

 DayDiffNum db 10 dup (20h)

 DaysStr db ' day(s)$'

 DaysStr_len equ $ - offset DaysStr

 UserTotaldate dd 0,0

 InputCount dw 0
.stack 100h
.code
;---------------------------------------------------------------------
;
------ 主程式在后段,请由start: 开始阅读------------------
;
---------------------------------------------------------------------
;
输入月份,传回ax=该月天数,若输入不符(1-12之外),cf=1
GetmonthDay proc near GiveMonth:word     ;(1-12)
     push bx
     mov bx,GiveMonth
     dec bx
     cmp bx,11
     ja GetMdxErr
     mov ax,28
     cmp  bx,1  ;是否2月
     jz GetMDok ;
     cmp bx,6   ;少于等于7月
     jbe GetMD10
     inc bx  ;大于7月加1
GetMD10:mov ax,30
     test bx,1
     jnz GetMDok  ;奇数不加ax=30
     inc ax ;偶数加1,ax=31
GetMDok:clc  ;ax=月份天数
     jmp short GetMDxErrx
GetMDxErr:
     stc
GetMDxErrx:     
     pop bx
     ret
GetmonthDay  endp
;-------------------------------------------------------------------
GetTotalDay proc near xyear:word,xmonth:word,xday:word
  local TotalHi,TotalLo,LLyear:word ;区域变量(32bit总天数),闰月调整(0或1)

     xor ax,ax
     mov word ptr TotalLo,ax     ;初始化
     mov word ptr TotalHi,ax ;初始化
     mov word ptr LLyear,ax ;初始化
     mov ax,xyear ;取年份
     call LeapYear  ;输入AX=年份,输出AX=1(闰年),AX=0(非闰年)         
     or ax,ax
     jz  GetTDA
     mov word ptr LLyear,1
GetTDA:mov di,xyear ;取年份

GetTD0:cmp di,1000 ;和基准1000比较
     ja GetTD3 ;大于
     jz GetTD4 ;等于,则不处理输入年份和1000的相差
     ; di = less then 1000  
     mov si,+1 ;预设加
     mov ax,di ;取得输入年份
GetTD1:call LeapYear ;是否闰年
     mov bx,365 ;一年多少天
     or ax,ax  ;是否闰年?
     jz  GetTD2 ;不是
     inc bx  ;是闰年,365+1
GetTD2:
     add word ptr TotalLo,bx ;加总日期
     adc word ptr TotalHi,0 ;带进位加
     add di,si    ;输入年份 加或减1 , 用以接近1000
     jmp short GetTD0
GetTD3:     ;大于1000到此
     lea ax,[di-1] ;本年不计,取前一年值,若di=2017,lea ax,[di-1]后, ax=2016
     mov si,-1; ;预设减,, 用以接近1000
     jmp short GetTD1 ;回去计算闰年
GetTD4:      ;得该年和1000/1/1相差年日数
     ;取得第一输入年月日,且已计算 和1000/1/1相差年日数
     mov dx,1  ;以下计算该年总天数
     xor ax,ax
     xor si,si
     mov dx,1  ;由1月起
     cmp xmonth,dx ;输入月份是否1月
     jz GetTD7 ;
GetTD5:cmp dx,xmonth
     jae GetTD7
     invoke GetmonthDay, dx  ;以月份(dx)查找该月天数
     cmp dx,2 ;是否2月
     jnz GetTD6 ;不是
     add ax,LLyear ;若闰月调整 28->29
GetTD6:add si,ax ;累加
     inc dx ;下一月
     jmp short GetTD5
GetTD7:mov ax,si ;取加总
     add ax,xday ;加上输入天数
     ;得该年总天数
     add word ptr TotalLo,ax ;加总日期
     adc word ptr TotalHi,0 ;带进位加
     mov dx,TotalHi
     mov ax,TotalLo   ;总天数由dx:ax传回
     ret
GetTotalDay endp
;--------------------------------------------------------------------------
;
转值子程序,把输入的10进制文字转成16进制
;
输入:ds:si数字字符串起点,以0dh结束
;
输出:ax=转换后的16进制值,cf=1表示有非数字字符
GetValue:
     push bx
     push di
     xor bx,bx
     mov di,10
GetV10:lodsb     ;指向起点
     mov ah,0 ;清除
     ;cmp al,0 ;0dh ;回车?
     cmp al,0  ;-----zero
     jz Getvx
     cmp al,'0' ;以下比较0-9,否则cf=1离开
     jb Getvy
     cmp al,'9'
     ja Getvy
     sub al,'0'  ;ascii -> 值
     xchg bx,ax  ;交换
     mul di
     add bx,ax  ;累加
     jmp short GetV10
Getvx:
     mov ax,bx ;ascii转值后由ax转回
     clc ;成功cf=0
     jmp short Getvz
Getvy:
     stc  ;错误 cf=1
Getvz:
     pop di
     pop bx
     ret
;------------------------------------
;
以下程序送出dx:ax的32bit结果到di,也可改成印出
;
output DX:AX,dword (0-FFFFFFFFh / 0-4294967295)
print_dec:      xor cx,cx
     xchg bp,dx
     mov si,10 ;div by 10
     mov bx,30h
print_dec1: or bp,bp
     jz print_dec3
     xchg bp,ax
     xor dx,dx
     div si
     xchg bp,ax
     div si
     or dl,bl
     push dx
     inc cx
     jmp short print_dec1
print_dec3:  xor dx,dx
     div si
     or dl,bl
     push dx
     inc cx
     or ax,ax
     jnz print_dec3
print_dec4: pop ax
     stosb
     loop print_dec4
     ret
;--------------------------------------------------------------
GetDate proc near BufferAdd:word   

 local Uyear,Umonth,Uday,LimitDay,splitCount,LYear:word

 local UserInputAddress[4]:word

     mov word ptr splitCount,0  ; [/]数目初始化
     mov word ptr LYear,0  ;闰年初始化
     mov dx,BufferAdd
     mov di,dx
     mov ax,0C0Ah
     int 21h
     add di,2 ;指向字串符位置
     xor cx,cx
     mov cl,[di-1] ;取实际输入数
     mov bx,cx
     or cx,cx
     jnz  okinput
     mov ax,0    ;没有输入
     jmp short GetdataXX
GetdataX:
     mov ax,-1   ;错误输入
GetdataXX:
     stc
     ret
okinput:
     mov byte ptr [di+bx],0   ;0dH ->0
     mov al,'/'  ;分隔符
     lea bx,UserInputAddress
retry:
     repnz scasb
     jnz NotFound
     mov ss:[bx],di
     mov byte ptr [di-1],0   ;清除分隔符
     inc splitCount ;加分隔符数目
     add bx,2 ;下一地址
     or cx,cx  ;完了?
     jnz retry ;
NotFound:
     cmp splitCount,2  ;只容许2个[/]分隔符
     jnz GetdataX  ;不符
     mov si, UserInputAddress[2] ;取年
     call GetValue ;取值
     cmp ax,4000 ;以下比较限制1000-4000
     ja GetdataX
     cmp ax,1000
     jb GetdataX
     mov Uyear,ax  ;
     mov si,UserInputAddress[0]  ;取月
     call GetValue  ;传回AX(月)
     mov Umonth,ax  ;
     invoke GetmonthDay,ax  ;取该月天数限制
     jc GetdataX   ;不符(1-12)
     mov LimitDay,ax  ;存天数限制
     mov si,BufferAdd
     add si,2  ;指向日期
     call GetValue ;取值
     mov Uday,ax  ;存输入日期
     mov ax,Uyear  ;取输入年份
     Call LeapYear ;计算是否闰年子程序,传回1表示闰年,0表示非闰年
     or ax,ax ;是闰年不?
     jz GetTD  ;不是闰年
     mov word ptr LYear,1  ;闰年
     cmp Umonth,2  ;二月?
     jnz GetTD  ; 不是二月
     inc LimitDay ;闰年,2月份的限制+1
GetTD:
     mov ax,Uday  ;使用者输入日期
     cmp ax,LimitDay  ;比较输入日期和该月份天数限制
     ja GetdataX ;输入日期大于月份限制, 再输入
     clc    ;所有日期输入正常
     mov ax,Uyear  ;传回年
     mov bx,Umonth ;传回月
     mov cx,Uday ;传回日
     mov dx,LYear ;闰年与否...0=不是,1=是
     ret
GetDate  endp
;-------------------------------------------------------------------
LeapYear:  ;输入AX=年份,输出AX=1(闰年),AX=0(非闰年)
     push bx
     push dx
     push si
     mov si,ax
     mov bx,400 ;年份除以400余数等于零,为闰年
     mov dx,0
     div bx
     or dx,dx ;是否0
     jz last ;余数为零,直接输出闰年
     mov dx,0
     mov bx,4 ;除以400余数不等于0,进行除以4余数是否等0的判断
     mov ax,si
     div bx
     or dx,dx  ;是否0
     jz ok ;除以四余数等于0,跳往除以100余数是否等0的判断
over:    ;除以四余数不等于0,则不为闰年,结束
     mov ax,0  ;不是闰年
     jmp nil
ok:
     mov bx,100 ;除以100余数为零,则不为闰年,余数不为零,则为闰年
     mov dx,0
     mov ax,si
     div bx
     or dx,dx ;是否0
     jz over
last:
     mov ax,1 ;闰年
nil:
     pop si
     pop dx
     pop bx
     ret
;----------------------------------------------------------------------------
;
--------------------------主程式-----------------------------------------
;
----------------------------------------------------------------------------
start:
     MOV AX,@DATA
     MOV DS,AX
     MOV ES,AX
     cld   ;正向
;
-------------输入/测试开始------------------------------------------------
     mov inputCount,0  ;初始化
TryInput:
     cmp inputCount,0  ;根据inputCount决定显示第1/第2字符串
     lea dx,FirstStr     
     jz Whoinput
     lea dx,SecondStr   
Whoinput:
     mov ah,9
     int 21h
     lea si,InputBuffer  ;输入缓冲,要求输入格式(dd/mm/yyyy)
     invoke GetDate,si  ;读取输入子程序,传回ax(年),bx(月),cx(日),dx(闰年)
     jnc Nextin ;正常
     or ax,ax   ;没有输入
     jz quit  ;离开
     jmp short TryInput  ;输入错误
Nextin:
     invoke GetTotalDay,ax,bx,cx ;输入年,月,日....传回dx:ax=总天数 (由输入日期至1000/1/1)
     mov bx,inputCount
     shl bx,1              ; x 2
     shl bx,1              ; x 4
     mov word ptr UserTotaldate[bx],ax   ;根据inputCount存入总天数
     mov word ptr UserTotaldate[bx+2],dx
     inc inputCount  ;next input ?
     cmp inputCount,2  
     jb TryInput  ;second输入
;
-------------输入/测试完成------------------------------------------------
    mov ax,word ptr UserTotaldate
    mov dx,word ptr UserTotaldate+2   ;取第1输入值入:dx:ax
    mov bx,word ptr UserTotaldate+4
    mov cx,word ptr UserTotaldate+6    ;取第2输入值入:cx:bx
    cmp dx,cx  ;比较高位
    ja noChg  ;大于
    jb xxchg  ;少于
      ;-- 32高位相等
    cmp ax,bx  ;比较低位
    jae noChg  ;大于等于不交换
xxchg:
     xchg ax,bx ;少于则交换
    xchg dx,cx
noChg:
     sub ax,bx
     sbb dx,cx
     mov di,offset DayDiffNum
     Call print_dec  ; 输出结果至字符串
     mov si,offset  DaysStr
     mov cx,DaysStr_len
     rep movsb
     mov dx,offset DayDiffStr ;印结果
     mov ah,9
     int 21h
     mov ah,7 ;暂停
     int 21h
quit:
     mov ax,4c00h   ;离开
     int 21h
;--------------------------------------------------------------------------
end start


[此贴子已经被作者于2016-7-3 11:25编辑过]

#26
zhulei19782016-07-02 16:49
这个跟bochs有什么关系,又不写操作系统
1