2

そこで、非常に古く由緒あるエンジニアリング分析 QBasic 4.5 コードを C に移植しようとしています。結果を正確に一致させようとしていますが、QB がどのように計算を行っているのかよくわかりません。

たとえば、次の 2 行

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) になります。

これまでとは少し異なります。誰かが理由を理解するのを手伝ってくれますか? どうもありがとう。

4

2 に答える 2

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

コード:

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 / 288offset に格納されました。これが、そのオフセットをロードすることになった0x0050理由です。FLDその値を読み込んだ後、それを(offset )SINGLEの値で割り、結果を(offset )に格納しました。を使用した場合と同じように に代入できますが、その値を変更することはできません。a0x0036e20x003Ee2d2CONST a = 32.174

IDE ではなくコンパイル時にのみ発生する理由を疑問に思っている人もいるでしょうが、私には正直わかりません。私の推測では、IDE は精度を維持するために FP スタックにできるだけ多くの浮動小数点数を保持するため、 の 32 ビットの丸められた値を使用する代わりにa、FP スタックに既に格納されている既存の 80 ビット値を使用します。まだそこに保管されている場合。このようにすると、80 ビット値を FP スタックの外部に格納するには、値を格納する場所を指定して、最も近い 32 ビットまたは 64 ビット値に丸める必要があるため、精度の低下が少なくなります。もちろん、何らかの理由で FP スタックに 8 つ以上の値が必要な場合は、1 つを交換して別のスペースを確保する必要があり、最終的には精度の低下が明らかになります。

@BlackJack は、IDE がコードを最適化してコンパイルするのではなく、コードを解釈していることも指摘しました。これが、コードが IDE で実行されるときのバイト表現が同じであるが、コンパイルされたバージョンでは異なる理由である可能性があります。つまり、 と の両方の計算は、d2BC.EXEのように の計算を単一の値にe2最適化するのではなく、まったく同じ方法で実行されます。d2

いずれにせよ、最新の浮動小数点テクノロジの助けがなくても、最新のコンパイラは BC.EXE よりも最適化に関してはるかにスマートであり、より多くのメモリを使用できるため、C コードでこれに気付かない可能性があります。 SSE2のように。

于 2016-06-29T01:50:27.047 に答える