gpt4 book ai didi

basic - 为什么这些无意义的 QB 计算会产生略微不同的值?

转载 作者:行者123 更新时间:2023-12-01 08:11:04 33 4
gpt4 key购买 nike

因此,我正在尝试将一些非常古老且备受推崇的工程分析 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!)

d2 变为 1.07920125E-4( float 0x38e2532d)

e2 变为 1.0792013E-4( float 0x38e2532e)

它们之间的差异是如此之小。谁能帮我理解为什么?非常感谢。

最佳答案

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)/a1/288 的近似值存储在偏移量 0x0050 中,这就是 FLD 最终加载该偏移量的原因。加载该 SINGLE 值后,它将它除以 a 的值(偏移量 0x0036)并将结果存储在 e2(偏移 0x003E)。您可以使用 CONST a = 32.174 使 e2d2 的赋值相同,但您无法更改其值。

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

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

在任何情况下,您都可能不会在 C 代码中注意到它,因为您的现代编译器比 BC.EXE 更智能,并且在优化时有更多的内存可供使用,即使没有现代 float 的帮助-点技术,如 SSE2。

关于basic - 为什么这些无意义的 QB 计算会产生略微不同的值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38076274/

33 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com