[算法实战002]高精度正负浮点数四则运算
几年之前,论坛正搞笔记征集活动,才写了几篇,这个区就突然没了{:9_218:} ,就没怎么上论坛了今天偶然看到汇编区回来了{:9_232:} ,发个帖子,
账号可能被注销了{:9_219:} ,emmm只能新搞一个了
https://attach.52pojie.cn/forum/201902/19/203337tv4hy08yom3oovmo.jpg
https://attach.52pojie.cn/forum/201902/19/203342tuxja8hjq1q8aaf8.jpg
;高精度正负浮点数四则运算 Algo
;Code By 老刘
;编译环境:MASM32 SDK
;编译指令:ml /coff 高精度正负浮点数四则运算.ASM /link /subsystem:console
;调用方法:
; 传参,参数1为数1对数2进行的运算(+、-、*、/),
; 参数2、3为分别为数1,数2参数。
;合规数字:1 01 1.0 00.100 -01.1234 (不和规数字:.1 3.)
;其他:
; 两个数字的最大长度为124位(包含负号、小数点、0)。
; 受MASM32 SDK中老旧的ArgClC函数限制,两个数字的总长度和应不超过188。
; 应尽量避免多余0的出现(如0100中开头的0、0.0010中最后的0),会削减除法运算的精度。
;参考:MHL批处理标准教程之高精度系列代码。
Include masm32rt.inc
Main Proto ;主函数
_Addition Proto ;加(减)法函数
_ArraySubtraction Proto lpMinuend:dword,lpSubtrahend:dword,bArrayLength:byte ;数组减法函数
_ArrayCompare Proto lpArray1:dword,lpArray2:dword,bArrayLength:byte ;数组(表示的数字的)大小比较函数
_Multiplication Proto ;乘法函数
_Division Proto ;除法函数
.Data?
szNum1 DB 128 Dup (?)
szNum2 DB 128 Dup (?)
bNum1Symbol DB ?
bNum2Symbol DB ?
bNum1Length DB ?
bNum2Length DB ?
bNum1IntegralPartLength DB ?
bNum1DecimalPartLength DB ?
bNum2IntegralPartLength DB ?
bNum2DecimalPartLength DB ?
bNum1LengthPlusNum2Length DB ?
bNum1IntegralPartLengthPlusNum2IntegralPartLength DB ?
bNum1DecimalPartLengthPlusNum2DecimalPartLength DB ?
bNum1LengthPlusNum2DecimalPartLength DB ?
bNum2LengthPlusNum1DecimalPartLength DB ?
lpNum1IntegralPart DD ?
lpNum1DecimalPart DD ?
lpNum2IntegralPart DD ?
lpNum2DecimalPart DD ?
;End Data?
.Code
Main Proc
Local @szOperation:word ;运算符+null的长度
;获得参数并赋值变量
Invoke ArgClC,1,Addr @szOperation
Invoke ArgClC,2,Addr szNum1
Invoke ArgClC,3,Addr szNum2
Lea Eax,szNum2
Mov lpNum2IntegralPart,Eax ;初步确定指针
Lea Esi,szNum1
Mov lpNum1IntegralPart,Esi
;正负号判断(0正1负)
LodSB ;Esi=lpNum1IntegralPart
.If Al == '-'
Mov bNum1Symbol,1
Inc lpNum1IntegralPart ;指针修正,使其指向数字
.Else
Mov bNum1Symbol,0
.EndIf
Mov Ax,@szOperation
Mov Ah,Al
Mov Esi,lpNum2IntegralPart
LodSB
.If Ah == '-' ;负负得正的处理
.If Al == '-'
Mov bNum2Symbol,0
Inc lpNum2IntegralPart
.Else
Mov bNum2Symbol,1
.EndIf
.Else
.If Al == '-'
Mov bNum2Symbol,1
Inc lpNum2IntegralPart
.Else
Mov bNum2Symbol,0
.EndIf
.EndIf
;获得两数的小数、整数部分长度;
;获得两数的小数部分的内存地址;
;去掉小数点;将数字从Ascii转为Byte。
Mov bNum1IntegralPartLength,0
Mov bNum1DecimalPartLength,0
Mov bNum2IntegralPartLength,0
Mov bNum2DecimalPartLength,0
Mov Esi,lpNum1IntegralPart
Mov Edi,Esi ;同时操作一个字符串
Lea Ebx,bNum1IntegralPartLength
Lea Ecx,bNum1DecimalPartLength
Xor Edx,Edx ;作为循环flag
@@:
LodSB
.While Al != '.' && Al != NULL
Inc Byte Ptr
Sub Al,30H ;将数字从Ascii转为Byte
StoSB ;重新写回去
LodSB
.EndW
Push Esi
.If Al != NULL
LodSB
.While Al != NULL
Inc Byte Ptr
Sub Al,30H
StoSB
LodSB
.EndW
.EndIf
.If Edx == 0
Pop lpNum1DecimalPart
Mov Esi,lpNum2IntegralPart
Mov Edi,Esi
Lea Ebx,bNum2IntegralPartLength
Lea Ecx,bNum2DecimalPartLength
Inc Edx
Jmp @B
.EndIf
Pop lpNum2DecimalPart
Dec lpNum1DecimalPart ;去掉了小数点后的指针修正
Dec lpNum2DecimalPart
;计算各种长度。
Mov Al,bNum1IntegralPartLength
Add Al,bNum1DecimalPartLength
Mov bNum1Length,Al
Add Al,bNum2DecimalPartLength
Mov bNum1LengthPlusNum2DecimalPartLength,Al
Add Al,bNum2IntegralPartLength
Mov bNum1LengthPlusNum2Length,Al
Sub Al,bNum1IntegralPartLength
Mov bNum2LengthPlusNum1DecimalPartLength,Al
Sub Al,bNum1DecimalPartLength
Mov bNum2Length,Al
Sub Al,bNum2DecimalPartLength
Add Al,bNum1IntegralPartLength
Mov bNum1IntegralPartLengthPlusNum2IntegralPartLength,Al
Sub Al,bNum1LengthPlusNum2Length
Mov Al,bNum1LengthPlusNum2Length
Sub Al,bNum1IntegralPartLengthPlusNum2IntegralPartLength
Mov bNum1DecimalPartLengthPlusNum2DecimalPartLength,Al
;调用相应的计算函数。
Mov Ax,@szOperation
.If Al == '+' || Al == '-'
Call _Addition
.ElseIf Al == '*'
Call _Multiplication
.ElseIf Al == '/'
Call _Division
.EndIf
Ret
Main EndP
_Addition Proc
Local @bNum1Array:byte,@bNum2Array:byte ;其中之一同时作为结果数组
Local @bDestSymbol:byte,@lpDestArray
Local @bDestEqualZeroSign:byte,@bDestDecimalPartEqualZeroSign:byte
Local @bDestDecimalPartLength:byte,@szDest:byte
Mov Al,bNum1DecimalPartLengthPlusNum2DecimalPartLength ;初步确定结果小数部分长度
Mov @bDestDecimalPartLength,Al
;数组清0。
Xor Eax,Eax
Mov Ecx,SizeOf @bNum1Array
Lea Edi,@bNum1Array
Rep StoSB
Mov Ecx,SizeOf @bNum2Array
Lea Edi,@bNum2Array
Rep StoSB
;数1存入数组。
Mov Esi,lpNum1IntegralPart
Lea Edi,@bNum1Array
Movzx Eax,bNum1LengthPlusNum2DecimalPartLength
Add Edi,Eax
Dec Edi ;数组从0开始
Movzx Ecx,bNum1Length
.Repeat
MovSB
Dec Edi ;倒序装入数组
Dec Edi
.UntilCxZ
;数2存入数组。
Mov Esi,lpNum2IntegralPart
Lea Edi,@bNum2Array
Movzx Eax,bNum2LengthPlusNum1DecimalPartLength
Add Edi,Eax
Dec Edi
Movzx Ecx,bNum2Length
.Repeat
MovSB
Dec Edi
Dec Edi
.UntilCxZ
;判断同异号并计算结果。
Mov @bDestEqualZeroSign,0FFH
Mov Al,bNum1Symbol
Xor Al,bNum2Symbol
.If Al == 0 ;同号
;获取结果符号。
Mov Al,bNum1Symbol
Mov @bDestSymbol,Al
;计算结果。
Xor Eax,Eax ;Ah进位清0
Lea Esi,@bNum2Array
Lea Edi,@bNum1Array ;作为结果数组
Mov @lpDestArray,Edi
Movzx Ecx,bNum1LengthPlusNum2Length ;计数器
Mov Bl,10 ;十进制
.Repeat
LodSB
Add Al,
Add Al,Ah
Cbw
Div Bl
Xchg Al,Ah
StoSB
.UntilCxZ
.Else ;异号
;比较两数大小并获取结果符号。
Invoke _ArrayCompare,Addr @bNum1Array,Addr @bNum2Array,bNum1LengthPlusNum2Length
.If Eax == 1 ;(绝对值)数1>数2
Mov Al,bNum1Symbol
Mov @bDestSymbol,Al
Lea Edi,@bNum1Array ;作为结果数组
Mov @lpDestArray,Edi
Invoke _ArraySubtraction,Edi,Addr @bNum2Array,bNum1LengthPlusNum2Length
.ElseIf Eax == 2
Mov Al,bNum2Symbol
Mov @bDestSymbol,Al
Lea Edi,@bNum2Array
Mov @lpDestArray,Edi
Invoke _ArraySubtraction,Edi,Addr @bNum1Array,bNum1LengthPlusNum2Length
.Else ;两数相等
Inc @bDestEqualZeroSign ;=0
.EndIf
.EndIf
;验证加法结果是否为0。
;(其实就是应对0+0或-0-0这样的情况)
Mov Al,@bDestEqualZeroSign
.If Al == 0FFH
Mov Esi,@lpDestArray
Movzx Ecx,bNum1LengthPlusNum2Length
.Repeat
LodSB
.UntilCxZ Al != 0
.If Al == 0 ;全部跑完,且值为0
Inc @bDestEqualZeroSign
.EndIf
.EndIf
;结果转为字符串。
Mov Al,@bDestEqualZeroSign
.If Al == 0FFH ;值为FF在此表示值不为0
Mov Al,@bDestDecimalPartLength
.If Al != 0 ;两整数相加时小数部分肯定为0
;修正结果小数部分长度。
Mov Esi,@lpDestArray
Movzx Ecx,@bDestDecimalPartLength
.Repeat
LodSB
.UntilCxZ Al != 0
.If Al != 0 ;小数部分不为0
Inc Cl ;长度从1开始
Mov @bDestDecimalPartLength,Cl
.Else
Mov @bDestDecimalPartLength,Al ;=0
.EndIf
.EndIf
;结果字符串写入。
Lea Edi,@szDest
;结果符号判断&写入。
Mov Al,@bDestSymbol
.If Al == 1 ;结果为负
Mov Al,'-'
StoSB
.EndIf
;结果的整数部分转为字符串。
Movzx Esi,bNum1LengthPlusNum2Length
Add Esi,@lpDestArray
Dec Esi
Movzx Ecx,bNum1IntegralPartLengthPlusNum2IntegralPartLength
.Repeat ;去除先导0
LodSB
Dec Esi
Dec Esi
.UntilCxZ Al !=0
Add Al,30H ;Byte转Ascii
StoSB ;整数部分的最高位或0写入
.While Ecx != 0 ;剩余整数部分写入
LodSB
Dec Esi
Dec Esi
Add Al,30H
StoSB
Dec Ecx
.EndW
;结果小数点及小数部分转为字符串。
Movzx Ecx,@bDestDecimalPartLength ;计数器
.If Cl != 0
Mov Al,'.'
StoSB ;小数点写入
.Repeat
LodSB
Dec Esi
Dec Esi
Add Al,30H
StoSB
.UntilCxZ
.EndIf
Mov DWord Ptr ,000A0DH ;字符串结尾
.Else
;结果字符串写入
Lea Edi,@szDest
Mov DWord Ptr ,000A0D30H ;"0\r\n"+null
.EndIf
;输出结果。
Invoke StdOut,Addr @szDest
Xor Eax,Eax
Ret
_Addition EndP
_ArraySubtraction Proc Uses Eax Ebx Ecx Esi Edi lpMinuend:dword,lpSubtrahend:dword,bArrayLength:byte
;参数:被减数数组指针、减数数组指针、两个数组的共同长度。
;要求:被减数大于减数。
Mov Edi,lpMinuend
Mov Esi,lpSubtrahend
Movzx Ecx,bArrayLength
Xor Ebx,Ebx ;Bl借位清0
.Repeat
Mov Al,
Sub Al,Bl
Sub Al,
.If Sign? ;结果为负
Add Al,10
Mov Bl,1
.Else
Xor Ebx,Ebx
.EndIf
StoSB
Inc Esi
.UntilCxZ
Ret
_ArraySubtraction EndP
_ArrayCompare Proc Uses Edi Esi Ecx lpArray1:dword,lpArray2:dword,bArrayLength:byte
;参数:数组1、数组2、两个数组的共同长度。
;返回值(存于Eax中):0-两数(组)相等;1-数1大于数2;2-数2大于数1。
Mov Edi,lpArray1
Mov Esi,lpArray2
Movzx Ecx,bArrayLength ;计数器
Add Esi,Ecx
Dec Esi ;指向数组最后一个元素
Add Edi,Ecx
Dec Edi
.Repeat
Xchg Esi,Edi
LodSB ;数1
Dec Esi
Dec Esi
Mov Ah,Al
Xchg Esi,Edi
LodSB ;数2
Dec Esi
Dec Esi
.If Ah > Al ;(绝对值)数1>数2
Mov Eax,1
Ret
.ElseIf Ah < Al
Mov Eax,2
Ret
.EndIf
.UntilCxZ
Xor Eax,Eax ;能运行到这里,说明两数相等。
Ret
_ArrayCompare EndP
_Multiplication Proc
Local @bDestArray:byte,@bDestDecimalPartLength:byte,@szDest:byte
Local @bDestEqualZeroSign:byte,@bDestSymbol:byte
;结果数组清0。
Xor Eax,Eax
Mov Ecx,SizeOf @bDestArray
Lea Edi,@bDestArray
Rep StoSB
;数1、数2从低到高按位相乘并放入结果数组。
Lea Edi,@bDestArray
Mov Ebx,lpNum1IntegralPart
Movzx Edx,bNum1Length
Add Ebx,Edx
Dec Ebx ;指针修正
Xchg Dh,Dl ;Dh做计数器
;使用数1的每一位乘数2的全部位。
.While Dh != 0
Mov Dl, ;Dl做因数
Push Edi
;数2初始化。
Mov Esi,lpNum2IntegralPart
Movzx Ecx,bNum2Length ;Ecx做计数器
Add Esi,Ecx
Dec Esi
.Repeat
LodSB
Dec Esi ;逆读取指针修正;数2换高一位
Dec Esi
Mul Dl
Add ,Al
Inc Edi
.UntilCxZ
;进位处理,避免溢出。
Movzx Ecx,bNum2Length ;要处理进位的长度=数2的长度
Mov Edi, ;使Edi指向需进位处理的首个数字
Mov Dl,10
.Repeat
Movzx Ax,Byte Ptr
Div Dl
Mov ,Ah
Add ,Al
Inc Edi
.UntilCxZ
;根据乘法法则,数1换高一位,结果也换高一位。
Pop Edi
Dec Ebx
Inc Edi
Dec Dh
.EndW
;判断结果是否为0;
;获得结果符号。
Movzx Ecx,bNum1DecimalPartLength
Add Cl,bNum2DecimalPartLength
Mov @bDestEqualZeroSign,0
Lea Esi,@bDestArray
.If Cl > 0
.Repeat
LodSB
.UntilCxZ Al != 0
.If Al != 0 ;小数部分非0
;小数部分非0说明乘积不为0,获得结果符号。
Mov Al,bNum1Symbol
Xor Al,bNum2Symbol
Mov @bDestSymbol,Al
Inc Ecx
Mov @bDestDecimalPartLength,Cl
.Else
Mov @bDestDecimalPartLength,0
;判断整数部分是否为0。
Movzx Ecx,bNum1IntegralPartLength
Add Cl,bNum2IntegralPartLength
.Repeat
LodSB
.UntilCxZ Al != 0
.If Al == 0 ;乘积为0,不管正负,使结果为正(避免输出-0)
Mov @bDestSymbol,0
Inc @bDestEqualZeroSign ;=1
.EndIf
.EndIf
.Else ;结果不存在小数部分
Mov @bDestDecimalPartLength,0
.EndIf
;生成结果字符串。
Lea Esi,@bDestArray
Movzx Ecx,bNum1Length
Add Cl,bNum2Length
Add Esi,Ecx ;使得Esi指向数组末尾
Lea Edi,@szDest
.If @bDestEqualZeroSign == 1
Mov DWord Ptr ,000A0D30H ;"0\r\n"+null
.Else
;获得整数部分长度。
Movzx Ecx,bNum1IntegralPartLength
Add Cl,bNum2IntegralPartLength
;写入符号。
.If @bDestSymbol == 1
Mov Al,'-'
StoSB
.EndIf
;略过整数部分开头的0。
.Repeat
Dec Esi
LodSB
Dec Esi
.UntilCxZ Al != 0
Inc Ecx
;写入整数部分。
.Repeat
LodSB
Dec Esi
Dec Esi
Add Al,30H ;Byte转Ascii
StoSB
.UntilCxZ
;写入小数部分(如果有)。
Movzx Ecx,@bDestDecimalPartLength
.If Cl > 0
;写入小数点。
Mov Al,'.'
StoSB
;写入小数部分。
.Repeat
LodSB
Dec Esi
Dec Esi
Add Al,30H
StoSB
.UntilCxZ
.EndIf
;字符串结尾。
Mov DWord Ptr ,000A0DH
.EndIf
;输出结果。
Invoke StdOut,Addr @szDest
Xor Eax,Eax
Ret
_Multiplication EndP
_Division Proc
Local @szDest:byte,@bDividendArray:byte,@bDivisorArray:byte,@lpDest
Local @bDestDecimalPointPos:byte,@bDivisorArrayLength:byte
Local @bDestEqualZeroSign:byte,@bDestIntegralPartEqualZeroSign:byte,@bDecimalPointWriteInSign:byte
;将除数的有效数字存入除数数组。
Mov Esi,lpNum2IntegralPart
Movzx Ecx,bNum2IntegralPartLength
.Repeat ;去除整数部分多余的0
LodSB
.UntilCxZ Al != 0
.If Al != 0
Inc Ecx
Mov bNum2IntegralPartLength,Cl
Dec Esi ;指向有效数字
Add Cl,bNum2DecimalPartLength
.Else ;整数部分均为0,继续去0直到遇到有效数字
Mov bNum2IntegralPartLength,0
Movzx Ecx,bNum2DecimalPartLength
.If Ecx != 0 ;除数存在小数部分
.Repeat
LodSB
.UntilCxZ Al != 0
.If Al != 0
Inc Ecx
Dec Esi ;指向有效数字
.Else ;除数为0
Mov Eax,1
Ret
.EndIf
.Else ;不存在小数部分,除数为0
Mov Eax,1
Ret
.EndIf
.EndIf
;按位(倒序)存入除数数组。
Mov @bDivisorArrayLength,Cl ;有效数字到除数末尾的长度
Lea Edi,@bDivisorArray
Add Edi,Ecx
Dec Edi ;指向数组最后一个元素
.Repeat
LodSB
StoSB
Dec Edi
Dec Edi
.UntilCxZ
;为配合运算,将除数数组增加一个元素;
;不影响除数数组代表的除数的大小。
Movzx Ecx,@bDivisorArrayLength
Inc @bDivisorArrayLength
Lea Eax,@bDivisorArray ;此时Eax+Ecx指向新增的元素
Mov Byte Ptr ,0 ;清0
;计算结果小数点的位置。
Mov Al,bNum1IntegralPartLength
Add Al,bNum2DecimalPartLength
Mov @bDestDecimalPointPos,Al
;判断被除数是否为0;
;判断结果符号并写入。
Mov Esi,lpNum1IntegralPart
Movzx Ecx,bNum1Length
.Repeat
LodSB
.UntilCxZ Al != 0
.If Al == 0
Mov @bDestEqualZeroSign,1
Lea Eax,@szDest
Mov DWord Ptr ,000A0D30H ;"0\r\n"+null
.Else
Mov @bDestEqualZeroSign,0
Mov Al,bNum1Symbol
Xor Al,bNum2Symbol
.If Al == 0 ;结果为正
Lea Eax,@szDest
Mov @lpDest,Eax
.Else ;结果为负
Lea Eax,@szDest
Mov Byte Ptr ,'-'
Inc Eax
Mov @lpDest,Eax
.EndIf
.EndIf
;被除数数组清空。
Xor Eax,Eax
Mov Ecx,SizeOf @bDividendArray
Lea Edi,@bDividendArray
Rep StoSB
;进行除法运算并生成最终字符串。
.If @bDestEqualZeroSign != 1
;获得被除数数组起始元素地址;
Lea Esi,@bDividendArray
Mov Ebx,SizeOf @bDividendArray
Dec Ebx ;此时Esi+Ebx指向被除数数组末尾
Sub Bl,@bDivisorArrayLength ;作为计算终止的标识之一
Add Esi,Ebx ;此时Esi指向(当前的)被除数数组起始元素地址
Mov Bh,bNum1Length
Mov Ecx,lpNum1IntegralPart ;作为被除数的读取指针
Sub Bl,4 ;减小精度,为字符串中数字以外的其它字符保留空间
Mov Edi,@lpDest ;Edi作为字符串指针
Mov Dh,@bDestDecimalPointPos ;Dh作为小数点写入的标识
Mov @bDestIntegralPartEqualZeroSign,0
;开始除法(减法模拟)运算。
.While Bl > 0
Invoke _ArrayCompare,Esi,Addr @bDivisorArray,@bDivisorArrayLength
Dec Bl
.If Eax <= 1 ;被除数数组>=除数数组,做减法
Xor Dl,Dl ;Dl作为商
.While Eax != 2
Invoke _ArraySubtraction,Esi,Addr @bDivisorArray,@bDivisorArrayLength
Invoke _ArrayCompare,Esi,Addr @bDivisorArray,@bDivisorArrayLength
Inc Dl
.EndW
Mov Al,Dl
Add Al,30H
StoSB
Mov @bDestIntegralPartEqualZeroSign,1
.Else ;被除数数组已经(被减到)小于除数数组
;用被除数将被除数数组添加一位。
Dec Esi
.If Bh > 0 ;被除数未全部放入数组
Mov Al,Byte Ptr
Mov Byte Ptr ,Al
Inc Ecx
Dec Bh
.EndIf
;若被除数全部放入数组,则只需添0,由于数组已经清空,无需处理。
;判断是否需要向字符串中写入小数点。
.If Dh == 0 && @bDecimalPointWriteInSign == 0
Mov @bDecimalPointWriteInSign,1
.If @bDestIntegralPartEqualZeroSign == 0
Mov @bDestIntegralPartEqualZeroSign,1
Mov Al,'0'
StoSB
.EndIf
Mov Al,'.'
StoSB
.EndIf
Dec Dh
;判断是否处理完成;
;写入0。
Invoke _ArrayCompare,Esi,Addr @bDivisorArray,@bDivisorArrayLength
.If Eax == 2
.If Bh == 0 && @bDecimalPointWriteInSign == 1 ;被除数非零数全部放入数组且被除数整数部分除完
Movzx Ecx,@bDivisorArrayLength ;判断被除数数组是否除完(全空)
.Repeat
Lea Eax,
Mov Al,Byte Ptr
.UntilCxZ Al != 0
.If Al == 0
.If Dh == 0FFH ;小数点刚写入,已经计算完成
Dec Edi ;去掉小数点
.EndIf
.Break
.EndIf
.EndIf
.If @bDestIntegralPartEqualZeroSign == 1
Mov Al,'0'
StoSB
.EndIf
.EndIf
.EndIf
.EndW
.EndIf
Mov DWord Ptr ,00000A0DH ;字符串结尾
;输出结果。
Invoke StdOut,Addr @szDest
Xor Eax,Eax
Ret
_Division EndP
Start:
Call Main
Invoke ExitProcess,Eax
End Start
End
页:
[1]