Я получаю одинаковый вывод как для 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.
Похоже, когда вы печатаете их, происходит округление ... различие прошло мимо «201». Я сравниваю их, фактически используя «PUT #», чтобы записать их в файл (я все равно делал это), а затем смотрел на шестнадцатеричный. Кроме того, когда я помещаю суффикс '!' на этом 32.174, IDE удаляет его. Исходя из этого, я предполагаю, что SINGLE неявно, и поэтому '!' является излишним. – Chris
Извините ... используя QB4.5 на 32-битной машине. У меня DOSBOX на 64-битном, но я этого не пробовал. – Chris
Я не знаком с 'PUT #' для вывода и проверки SINGLE переменных. Но в любом случае моя QuickBasic IDE также автоматически удаляет '!' После редактирования строки, поэтому переменная уже неявно SINGLE, как вы говорите. Btw, когда вы добавляете '#' за ним, чтобы указать, что это DOUBLE, который не удаляется. – BdR