Chr*_*ris 2 basic qbasic quickbasic
因此,我正在尝试将一些非常古老且值得尊敬的工程分析QBasic 4.5代码移植到C.我试图准确地匹配结果,并且我发现我无法理解QB如何进行数学运算.
例如,这两行
DIM a AS SINGLE
DIM d2 AS SINGLE
DIM e2 AS SINGLE
a = 32.174
d2 = 1! / (2! * 32.174 * 144!)
e2 = 1! / (2! * a! * 144!)
Run Code Online (Sandbox Code Playgroud)
d2变为1.07920125E-4(浮点0x38e2532d)
e2变为1.0792013E-4(浮点0x38e2532e)
这有点不同.谁能帮我理解为什么?非常感谢.
小智 5
我得到了相同的输出两个d2和e2,甚至在价值观的原始字节表示的条款.这是一些带注释的输出:
# Calculation results
d2: 38 E2 53 2E
e2: 38 E2 53 2E
1.079201E-04 = 1.079201E-04
# Result of changing the last byte (some mantissa bits) to alter the value,
# proving they're not equal
d2: 38 E2 53 2F
e2: 38 E2 53 2E
1.079201E-04 <> 1.079201E-04
# Result above may just be luck. This result alters the first byte
# (some exponent bits) to prove that the intended bits were altered.
d2: 39 E2 53 2E
e2: 38 E2 53 2E
4.316805E-04 <> 1.079201E-04
Run Code Online (Sandbox Code Playgroud)
码:
DIM a AS SINGLE
DIM SHARED d2 AS SINGLE
DIM SHARED e2 AS SINGLE
a = 32.174
d2 = 1! / (2! * 32.174 * 144!)
e2 = 1! / (2! * a! * 144!)
' Print the hex representation of the bytes
' and show they're initially equal.
CALL printHex
PRINT
' Change the last byte of the mantissa by 1 bit.
' Show that doing this makes the two values unequal.
DEF SEG = VARSEG(d2)
POKE VARPTR(d2), PEEK(VARPTR(d2)) + 1
DEF SEG
CALL printHex
PRINT
' Show that the correct byte was poked by reverting mantissa change and
' altering exponent.
DEF SEG = VARSEG(d2)
POKE VARPTR(d2), PEEK(VARPTR(d2)) - 1
POKE VARPTR(d2) + 3, PEEK(VARPTR(d2) + 3) + 1
DEF SEG
CALL printHex
SUB printHex
'SHARED variables used:
' d2, e2
DIM d2h AS STRING * 8, e2h AS STRING * 8
' Get bytes of d2 and e2, storing them as hexadecimal values
' in d2h and e2h.
DEF SEG = VARSEG(d2)
MID$(d2h, 1) = hexByte$(PEEK(VARPTR(d2) + 3))
MID$(d2h, 3) = hexByte$(PEEK(VARPTR(d2) + 2))
MID$(d2h, 5) = hexByte$(PEEK(VARPTR(d2) + 1))
MID$(d2h, 7) = hexByte$(PEEK(VARPTR(d2)))
DEF SEG = VARSEG(e2)
MID$(e2h, 1) = hexByte$(PEEK(VARPTR(e2) + 3))
MID$(e2h, 3) = hexByte$(PEEK(VARPTR(e2) + 2))
MID$(e2h, 5) = hexByte$(PEEK(VARPTR(e2) + 1))
MID$(e2h, 7) = hexByte$(PEEK(VARPTR(e2)))
DEF SEG
' Print the bytes, separating them using spaces.
PRINT "d2: "; MID$(d2h, 1, 2); " "; MID$(d2h, 3, 2); " ";
PRINT MID$(d2h, 5, 2); " "; MID$(d2h, 7, 2)
PRINT "e2: "; MID$(e2h, 1, 2); " "; MID$(e2h, 3, 2); " ";
PRINT MID$(e2h, 5, 2); " "; MID$(e2h, 7, 2)
' Print whether d2 is equal to e2.
IF d2 = e2 THEN
PRINT d2; "= "; e2
ELSE
PRINT d2; "<>"; e2
END IF
END SUB
FUNCTION hexByte$ (b%)
' Error 5 is "Illegal function call".
' This can only happen if b% is outside the range 0..255.
IF b% < 0 OR b% > 255 THEN ERROR 5
' MID$("0" + HEX$(15), 2 + (-1)) => MID$("0F", 1) => "0F"
' MID$("0" + HEX$(16), 2 + ( 0)) => MID$("010", 2) => "10"
hexByte$ = MID$("0" + HEX$(b%), 2 + (b% < 16))
END FUNCTION
Run Code Online (Sandbox Code Playgroud)
编辑
正如@BlackJack在评论中解释的那样,在编译文件时,您注意到的效果似乎会发生.由于这是给出的线索,我在DOSBox中使用了CodeView调试器,这里是删节结果:
5: a = 32.174
057D:0030 C70636002DB2 MOV Word Ptr [0036],B22D
057D:0036 C70638000042 MOV Word Ptr [0038],4200
6: d2 = 1! / (2! * 32.174 * 144!)
057D:003C C7063A002D53 MOV Word Ptr [003A],532D
057D:0042 C7063C00E238 MOV Word Ptr [003C],38E2
7: e2 = 1! / (2! * a! * 144!)
057D:0048 CD35065000 FLD DWord Ptr [0050]; 00 CB 21 CD
057D:004D CD34363600 FDIV DWord Ptr [0036]; 42 00 B2 2D
057D:0052 CD351E3E00 FSTP DWord Ptr [003E]; e2 = result
057D:0057 CD3D FWAIT
Run Code Online (Sandbox Code Playgroud)
BASIC编译器(BC.EXE)将赋值d2简化为浮点常量的简单赋值(即,它评估表达式本身并优化代码到单个赋值,而不是执行您指定的所有操作).但是,赋值e2并不那么简单,因为它包含非常量表达式a!.
为了解决这个问题并尝试保持尽可能高的精度,它改为1 / (2 * a * 144)数学上等效(1 / 288) / a,并且近似值1 / 288存储在偏移量0x0050,这就是为什么FLD最终加载该偏移量.加载该SINGLE值后,将其除以a(偏移量0x0036)的值并将结果存储在e2(偏移量0x003E)中.您可以使用e2相同的方式进行分配,但不能更改其值.d2CONST a = 32.174
有人可能想知道为什么这只会在编译时发生,而不是在IDE中,我老实说也不知道.我最好的猜测是IDE在FP堆栈上保留尽可能多的浮点数以保持精度,因此它不使用32位舍入值a,而是使用已存储在FP堆栈上的现有80位值,如果它仍然存储在那里.这样,由于将80位值存储在FP堆栈之外,因此精度损失较少,因此需要舍入到最接近的32位或64位值,具体取决于指定存储值的位置.当然,如果由于某种原因需要在FP堆栈上超过8个值,则需要换出一个以便为另一个腾出空间,最终会出现精度损失.
@BlackJack还指出IDE正在解释代码而不是用优化编译它,这可能是代码在IDE中运行但在编译版本中不同时字节表示相同的原因.也就是说,计算d2和e2执行的方式完全相同,而不是d2像BC.EXE那样优化单个值的计算.
在任何情况下,您可能都没有在C代码中注意到它,因为即使没有现代浮点技术的帮助,您的现代编译器也更加智能,并且在优化方面有更多的内存可用于优化BC.EXE.像SSE2.