为什么这些idneitcal QB计算会产生略微不同的值?

时间:2016-06-28 12:27:02

标签: basic qbasic quickbasic

所以,我正在尝试将一些非常古老而值得尊敬的工程分析QBasic 4.5代码移植到C.我试图准确地匹配结果,并且我发现我无法理解QB如何进行数学运算。 / p>

例如,这两行

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!)

d2变为1.07920125E-4(浮点0x38e2532d)

e2变为1.0792013E-4(浮点0x38e2532e)

有点不同。谁能帮我理解为什么?非常感谢。

2 个答案:

答案 0 :(得分:5)

我为d2e2获取相同的输出,即使在值的原始字节表示方面也是如此。这是一些带注释的输出:

# 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

代码:

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

修改

正如@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

BASIC编译器(BC.EXE)将对d2的赋值简化为浮点常量的简单赋值(即它评估表达式本身并将代码优化为单个赋值而不是执行所有您指定的操作)。但是,对e2的分配并不那么简单,因为它包含非常量表达式a!

为了解决问题并尝试尽可能保持精确度,它将1 / (2 * a * 144)更改为数学上等效的(1 / 288) / a,并将1 / 288的近似值存储在偏移量{{ 1}},这就是0x0050最终加载该偏移量的原因。加载FLD值后,将其除以SINGLE(偏移a)的值,并将结果存储在0x0036(偏移e2)中。您可以使用0x003Ee2的作业与d2相同,但您无法更改其值。

有人可能想知道为什么这只会在编译时发生,而不是在IDE中发生,老实说我不知道​​。我最好的猜测是IDE在FP堆栈上保留尽可能多的浮点数以保持精度,因此它不使用CONST a = 32.174的32位舍入值,而是使用存储在其上的现有80位值。 FP堆栈已经存在,如果它仍然存储在那里。这样,由于将80位值存储在FP堆栈之外,因此精度损失较小,因此需要舍入到最接近的32位或64位值,具体取决于指定存储值的位置。当然,如果由于某种原因需要在FP堆栈上超过8个值,则需要换出一个以便为另一个腾出空间,最终会出现精度损失。

@BlackJack还指出IDE正在解释代码而不是使用优化来编译代码,这可能是代码在IDE中运行但在编译版本中不同时字节表示相同的原因。也就是说,ad2的计算都以完全相同的方式执行,而不是像BC.EXE那样将e2的计算优化为单个值。

在任何情况下,你可能都没有在你的C代码中注意到它,因为你的现代编译器更聪明,并且在优化时比BC.EXE有更多内存可用,即使没有帮助像SSE2这样的现代浮点技术。

答案 1 :(得分:2)

您使用的是哪个QB版本?您如何打印或输出变量d2e2

当我在DOSBox 0.74中的QuickBASIC 4.5中尝试你的程序时,我没有得到不同的值,当我PRINT时,d2和e2是相同的。

a =  32.174
d2 =  1.079201E-04 
e2 =  1.079201E-04 

exlamation mark运算符会将其类型化为SINGLE(单精度,4个字节),因此它与AS SINGLE相同。也许你的行32.174中的值d2 = 1! /..被某种方式强制转换为双倍?