为何fprem1指令未输出预期结果?技术求助
fprem1指令大指数差时余数异常问题分析与解决
《汇编语言的艺术》中提到:fprem1指令用于计算部分余数,计算st0/st1的部分余数。若ST0与ST1的指数差小于64,fprem1可通过一次运算得到精确余数。
ST0和ST1是FPU数据寄存器,我针对该指令做了测试,编写的代码如下:
program exercise; #include( "stdlib.hhf" ); static x: real80:=156.5e18;//first number y: real80:=14.36; //second number z:real80; // partial remainder begin exercise; fld(y); //y goes into st0 fld(x); //now x goes into st0 but previous content of st0 //(that is st1) will move into st1, so x is into st0 and y is into st1 fprem1(); //calculates the remainder of st0/st1 (here x/y )but // for getting the remainder, the quotient will round to the nearest value and then // remainder = st0 - rounded quotient*st1==>>that is // equivalent by remainder= x-rounded quotient*y // ===>> then the remainder can be positive or negative. fstp(z); //this will store the partial remainder in the z real80 variable. stdout.put("z: ",z); end exercise;
运行程序后发现,指数差较小时结果符合预期,但指数差较大时结果与预期相差甚远,整理的测试结果如下:
| 序号 | 被除数/除数 | 预期余数 | 程序输出余数 |
|---|---|---|---|
| 1 | 156.5/14.36 | -1.46 | -1.4599..9e000 |
| 2 | 156.5e1/14.36 | -0.24 | -2.39e-1 |
| 3 | 156.5e2/14.36 | -2.4 | -2.39 |
| 4 | 156.5e3/14.36 | 4.72 | 4.7200...619 |
| 5 | 156.5e4/14.36 | 4.12 | 4.1200...6195 |
| 6 | 156.5e5/14.36 | -1.88 | -1.8799...805 |
| 7 | 156.5e6/14.36 | -4.44 | -4.4399..3805 |
| 8 | 156.5e7/14.36 | -1.32 | -1.3199...5017 |
| 9 | 156.5e8/14.36 | 1.16 | 1.16006..826 |
| 10 | 156.5e9/14.36 | -2.76 | -2.7599..737 |
| 11 | 156.5e10/14.36 | 1.12 | 1.1200619..627 |
| 12 | 156.5e11/14.36 | -3.16 | -3.159380..739 |
| 13 | 156.5e12/14.36 | -2.88 | -2.873805..346 |
| 14 | 156.5e13/14.36 | -0.08 | -7.99..e-2 ~= -0.079 |
| 15 | 156.5e14/14.36 | -0.8 | -7.99..e-1 ~= -0.79 |
| 16 | 156.5e15/14.36 | 6.36 | 6.36340...924 |
| 17 | 156.5e16/14.36 | 6.16 | 6.1940300..120 |
| 18 | 156.5e17/14.36 | 4.16 | 4.500300559 |
| 19 | 156.5e18/14.36 | 1.48 | 5.13274..e10 |
| 20 | 156.5e19/14.36 | 0.44 | 5.13274..e10 |
第19、20项结果明显不符合预期,余数远大于除数,这是错误的。请问是我的操作有误,还是fprem1指令存在问题?能否解释此问题并给出解决办法?
问题原因
你没有操作错误,问题出在fprem1指令的设计限制:
- fprem1单次最多只能处理指数差小于64的情况,此时能直接得到符合数学定义的余数(绝对值小于除数)。
- 当ST0与ST1的指数差≥64时,单次fprem1只能完成部分迭代计算,输出的是中间结果(也就是你看到的超大值),同时会设置FPU状态字中的C2标志位(位10),提示需要重复执行指令直到C2清零,才能得到最终正确的余数。
解决办法
必须循环执行fprem1,直到FPU状态字的C2标志位为0。具体做法是每次执行fprem1后,读取FPU状态字并检查C2位,若为1则继续执行fprem1,直到C2为0。
修改后的代码如下:
program exercise; #include( "stdlib.hhf" ); static x: real80:=156.5e18;//first number y: real80:=14.36; //second number z:real80; // partial remainder begin exercise; fld(y); //y存入st0 fld(x); //x存入st0,原st0的y移到st1,此时st0=x,st1=y fprem1(); //首次执行部分余数计算 fstsw ax; //将FPU状态字加载到AX寄存器 test ax, 0x400; //检查C2标志位(对应掩码0x400) jnz $-6; //如果C2为1,跳回fprem1重新执行($-6是回退到fprem1指令的位置) fstp(z); //将最终余数存入z stdout.put("z: ",z); end exercise;
代码说明
fstsw ax:把FPU的16位状态字读取到AX寄存器,状态字的第10位(二进制位10)就是C2标志。test ax, 0x400:通过位掩码检查C2标志是否被设置(0x400是2^10的十六进制表示)。jnz $-6:如果C2标志为1(结果不为0),就跳转到当前指令往前6字节的位置,也就是fprem1指令,重复执行直到C2标志清零。
这样修改后,无论指数差多大,都能得到绝对值小于除数的正确余数。
内容的提问来源于stack exchange,提问作者ahs
相关产品推荐
相关产品推荐

