2016-06-28 9 views
2

Итак, я пытаюсь перенести некоторый очень старый и почтенный инженерный анализ QBasic 4.5 на C. Я пытаюсь точно совместить результаты, и я обнаружил, что не могу понять, как QB выполняет свою математику.Почему эти идентификационные вычисления 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 (точка 0x38e2532d плавающей)

е2 становится 1.0792013E-4 (точка 0x38e2532e плавающей)

, который когда-либо так немного отличается. Может ли кто-нибудь помочь мне понять, почему? Огромное спасибо.

ответ

2

Какую версию QB вы используете? И как вы печатаете или выдаете свои переменные d2 и e2?

Когда я пытаюсь выполнить вашу программу в QuickBASIC 4.5 в DOSBox 0.74, я не получаю разные значения, d2 и e2 одинаковы, когда я их PRINT. Оператор знак

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

exlamation будет типаж его SINGLE (с одинарной точностью, 4 байта), так что это так же, как AS SINGLE. Может быть, значение 32.174 в вашей строке d2 = 1! /.. похоже на DOUBLE как есть?

+0

Похоже, когда вы печатаете их, происходит округление ... различие прошло мимо «201». Я сравниваю их, фактически используя «PUT #», чтобы записать их в файл (я все равно делал это), а затем смотрел на шестнадцатеричный. Кроме того, когда я помещаю суффикс '!' на этом 32.174, IDE удаляет его. Исходя из этого, я предполагаю, что SINGLE неявно, и поэтому '!' является излишним. – Chris

+0

Извините ... используя QB4.5 на 32-битной машине. У меня DOSBOX на 64-битном, но я этого не пробовал. – Chris

+0

Я не знаком с 'PUT #' для вывода и проверки SINGLE переменных. Но в любом случае моя QuickBasic IDE также автоматически удаляет '!' После редактирования строки, поэтому переменная уже неявно SINGLE, как вы говорите. Btw, когда вы добавляете '#' за ним, чтобы указать, что это DOUBLE, который не удаляется. – BdR

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 

EDIT

Как пояснил @BlackJack в комментариях, эффекты вы заметили, по всей видимости происходит, когда файл компилируется , Так что был дан ключ, я использовал отладчик CODEVIEW в DOSBox, и вот сокращенная результат:

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 Compiler (BC.EXE) уменьшил задание d2 к простому заданию с плавающей точкой constant (т.е. он сам оценивал само выражение и оптимизировал ваш код для этого единственного назначения, а не выполнял все указанные вами операции). Однако назначение e2 не так просто, так как оно содержит непостоянное выражение a!.

Для борьбы с этой проблемой и попытаться сохранить как можно больше точности, насколько это возможно, он изменил 1/(2 * a * 144) к математически эквивалентны (1/288)/a и приближенное значение 1/288 хранился при смещении 0x0050, поэтому FLD кончались нагрузки, что смещение. После загрузки этого значения SINGLE он разделил его на значение a (смещение 0x0036) и сохранил результат в e2 (смещение 0x003E). Вы можете сделать присвоение e2 таким же, как d2, используя CONST a = 32.174, но вы не можете изменить его значение.

Кто-то, вероятно, сейчас задается вопросом, почему это происходит только при компиляции, а не в среде IDE, и я честно не знаю. Лучше всего предположить, что IDE сохраняет столько плавающих в стеке FP столько, сколько может, чтобы сохранить точность, поэтому вместо того, чтобы использовать 32-битное округленное значение a, оно использует существующее 80-битное значение, хранящееся в стеке FP уже , если он все еще хранится там. Таким образом, потери точности меньше, так как сохранение 80-битного значения вне стека FP требует округления до ближайшего 32-битного или 64-битного значения, в зависимости от того, где вы указываете, чтобы сохранить значение. Конечно, если по какой-то причине требуется больше 8 значений в стеке FP, нужно будет поменять местами, чтобы освободить место для другого, и потеря точности будет в конечном итоге проявляться.

@BlackJack также указал, что IDE интерпретирует код, а не компилирует его с оптимизацией, что может быть причиной того, что представления байтов одинаковы, когда код запускается в среде IDE, но отличается в скомпилированной версии. То есть оба вычисления d2 и e2 выполняются точно так же, а не оптимизируют вычисление d2 до единственного значения, как это делает BC.EXE.

В любом случае, вы, вероятно, не заметите его в своем коде на C, потому что ваш современный компилятор намного умнее и имеет больше памяти для работы, когда дело доходит до оптимизации, чем BC.EXE, даже без помощи современных технологии с плавающей запятой, такие как SSE2.

+2

Ницца, это какое-то обстоятельное расследование и объяснение. Мой единственный комментарий заключается в том, что повторяющаяся часть 'MID $ + PRINT' может быть помещена в' SUB'. – BdR

+0

Ничего себе, это хорошая работа. Вы, очевидно, один из оставшихся в мире экспертов в QB. :) Я предполагаю, что выгода заключается в том, что я испытываю проблему, а вы не ... – Chris

+0

@ Крис Не очень эксперт. Просто конвертируйте некоторые знания о C и DOS в QB. –

 Смежные вопросы

  • Нет связанных вопросов^_^