Why do these idneitcal QB calculations produce slightly different values? - basic

So, I'm trying to port some very old and venerable engineering analysis QBasic 4.5 code into C. I'm trying to match results exactly, and I find that I can't quite understand how QB does its math.
For example, these two lines
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 becomes 1.07920125E-4 (floating point 0x38e2532d)
e2 becomes 1.0792013E-4 (floating point 0x38e2532e)
which are ever so slightly different. Can anyone help me understand why? Thanks very much.

I'm getting the same output for both d2 and e2, even in terms of the raw byte representation of the values. Here's some annotated output:
# 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
Code:
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
As #BlackJack explained in the comments, the effects you're noticing appear to occur when the file is compiled. Since that was the clue given, I used the CodeView debugger in DOSBox, and here's the abridged result:
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
The BASIC Compiler (BC.EXE) reduced the assignment to d2 to a simple assignment of a floating-point constant (i.e. it evaluated the expression itself and optimized your code to that single assignment rather than performing all of the operations you specified). However, the assignment to e2 isn't that simple since it contains a non-constant expression a!.
To combat the problem and attempt to keep as much precision as possible, it changed 1 / (2 * a * 144) to the mathematically equivalent (1 / 288) / a, and the approximate value of 1 / 288 was stored at offset 0x0050, which is why FLD ended up loading that offset. After loading that SINGLE value, it divided it by the value of a (offset 0x0036) and stored the result in e2 (offset 0x003E). You can make the assignment to e2 the same as d2 by using CONST a = 32.174, but you cannot change its value then.
Someone is probably wondering by now why this only happens when compiled and not in the IDE, and I honestly don't know. My best guess is the IDE keeps as many floats on the FP stack as much as it can to retain precision, so instead of using the 32-bit rounded value of a, it uses the existing 80-bit value stored on the FP stack already, if it is still stored there. This way, there's less precision loss since storing the 80-bit value outside of the FP stack requires rounding to the nearest 32-bit or 64-bit value, depending on where you specify to store the value. Of course, if more than 8 values are needed on the FP stack for some reason, one will need to be swapped out to make room for another, and the precision loss will manifest eventually.
#BlackJack also pointed out that the IDE is interpreting the code rather than compiling it with optimizations, which could be the reason that the byte representations are the same when the code is run in the IDE but different in a compiled version. That is, both the computations of d2 and e2 are executed in the exact same way rather than optimizing the calculation of d2 to a single value as BC.EXE does.
In any case, you likely don't notice it in your C code because your modern compiler is a lot smarter and has more memory to work with when it comes to optimizing than BC.EXE, even without the help of modern floating-point technologies like SSE2.

Which QB version are you using? And how are you printing or outputing your variables d2 and e2?
When I try your program in QuickBASIC 4.5 in DOSBox 0.74 I don't get different values, d2 and e2 are the same when I PRINT them.
a = 32.174
d2 = 1.079201E-04
e2 = 1.079201E-04
The exlamation mark operator will typecast it to SINGLE (single precision, 4 bytes), so it's same as AS SINGLE. Maybe the value 32.174 in your line d2 = 1! /.. is being typecast to DOUBLE somehow?

Related

Python struct not accepting 0xf031 as unsigned short

So I'm trying to implement a serial line data protocol. One of the commands has hex value 0xf031 (61489 in integer) .
When I try to enter it in as a unsigned short value in a struct I get this as an output:
d1= 61489
d2 = 24
pack('<HH', d1, d2)
>> b'1\xf0\x18\x00'
I expect
b'\x31\xf0\x18\x00'
With some trail and error I found out that the highest value it accepts is 61448
d1= 61448
d2 = 24
print(pack('<HH', d1, d2))
>> b'\x08\xf0\x18\x00'
Have anyone any tips or tricks. Even If i split the two bytes and enter them as single bytes using B (unsigned char) i get the same result.
d1= hex(61489)
d2 = 24
#print(d1)
d1_1 = d1[2:4]
d1_2 = d1[4:]
print(pack('<BBH', int(d1_1, base=16), int(d1_2,base = 16), d2))
>> b'\xf01\x18\x00'
And again the value 61448 gives correct bytes.
Just to show what 61449 gives me
d1= hex(61449)
d2 = 24
#print(d1)
d1_1 = d1[2:4]
d1_2 = d1[4:]
print(pack('<BBH', int(d1_1, base=16), int(d1_2,base = 16), d2))
>> b'\xf0\t\x18\x00'
Im using Jupyter-Lab in pipenv. python 3.8.7
You're seeing what you should be seeing. The 1 in the b-string isn't the value 1, it's the ASCII character "1" (0x31). b-strings will represent as printable characters when possible.
You can see if you convert it back that the original value is still there (in decimal):
>>> unpack('<H', pack('<H', 0xf031))
(61489,)

Xor using bitwise operations

Say I have a fn taking two ints A,B
Let binary rep of A be a0,a1,a2....an, and that of B be b0,b1,b2...bn .
I wish to return this ((a0 * b0) ^ (a1 * b1) ^ ..... ^ (an * bn)).
But the challenge is to achieve this without bit conversions i.e. using integers. How can I achieve this?
PS: I know A & B gives me a number. When this number is converted to binary and its elements are xorred amongst each other, I would get my answer. But I do not wish to convert the anded result to binary using bin() for faster computation.
def find( int A,int B):
multiply= A & B
list= bin(multiply)[2:] #(this step I wish to avoid cuz the product can be super large and the binary string is long)
list = [int(d) for d in list]
innerprod = (reduce(lambda i, j: int(i) ^ int(j), list))
return innerprod
First bitwise "and" (&) the numbers to get a number whose bits are the bits of the input numbers, multiplied respectively.
You can use Kernighan's algorithm to count the number of bits that are set to 1 (see link for description), in this number.
Then, mod 2 the result, because XOR is just flipping the result every time a bit set to 1 is encountered (so, an even number of 1's XOR'd together is 0, and an odd number will be 1).
Example:
7 is '111' and 5 is '101'
7 & 5 is '101' (bitwise "and" operation)
Two bits in '101' are set to 1 (so count_set_bits returns 2)
2 modulo 2 is 0.
(1 * 1) ^ (1 * 0) ^ (1 * 1) is 0
def count_set_bits(n):
count = 0
while n:
n &= (n-1)
count += 1
return count
def compute_answer(a, b):
return count_set_bits(a & b) % 2
print(compute_answer(7, 5)) # 0
print(compute_answer(37, 3)) # 1

Combining hex bytes into a string in VBA, without losing the leading Zero?

In VBA, I am trying to select 4 bytes out of a hex array, and convert them to decimal. However, if the byte is smaller than F, the first digit, which is 0, is lost in the compilation of the string, and the conversion is thus wrong.
I have tried various solution on this forum, without success.
The string I need to convert looks like this variable (called measHex):
AA 00 00 22 00 03 00 00 1F 07 00 BC 07
I am trying to convert bytes 7 to 10, to look like this:
00001F07
but what I get is 1F7
The following code is my function.
Private Function ToHexStringMeas(ByRef measHex As Variant) As String
ReDim bytes(LBound(measHex) + 6 To LBound(measHex) + 9)
Dim i As Long
For i = LBound(measHex) + 6 To LBound(measHex) + 9
bytes(i) = Hex(measHex(i))
Next
ToHexStringMeas = Strings.Join(bytes, "")
End Function
Any help would be highly appreciated.
After some more research, the solution was to add some code as follows:
Dim i As Long
For i = LBound(measHex) + 6 To LBound(measHex) + 9
bytes(i) = Hex(measHex(i))
Dim l As Integer
l = 2
h(i) = Replace(Space(l - Len(Hex(measHex(i)))), " ", "0") & Hex(measHex(i))
Next
ToHexStringMeas = Strings.Join(h, "")
You can also accomplish what I think is your goal using string functions.
VBA
Function ToHexStringShoot(ByRef measHex As String, Optional first As Long = 7, Optional last As Long = 10) As String
ToHexStringShoot = Replace(Mid(measHex, (first - 1) * 3, last * 3 - (first - 1) * 3), " ", "")
End Function
Worksheet Formula using the same logic
=SUBSTITUTE(MID(A1,6*3,10*3-6*3)," ","")

Join two bytes in BASCOM-AVR

How can I join two bytes to make an 16-bit int variable in BASCOM-AVR?
PARTIAL ANSWER:
Subquestion 1
If one byte is stored in the variable BYTE1 and the other is stored in the variable BYTE2, you can merge them into WORD1 in many BASICS with WORD1 = BYTE1: WORD1 = (WORD1 SHL 8) OR BYTE2. This makes BYTE1 into the high-order bits of WORD1, and BYTE2 into the low-order bits.
Subquestion 2
If you want to mask (or select) specific bits of a word, use the AND operator, summing up the bit values of the bits of interest - for example, if you want to select the first and third bits (counting the first bit as the LSB of the word) of the variable FLAGS, you would look at the value of FLAGS AND 5 - 5 is binary 0000000000000101, so you are guaranteeing that all bits in the result will be 0 except for the first and third, which will carry whatever value they are showing in FLAGS (this is 'bitwise AND').
Function to shift-left/right binary:
Byte1# = 255
PRINT HEX$(Byte1#)
Byte1# = SHL(Byte1#, 8) ' shift-left 8 bits
PRINT HEX$(Byte1#)
END
' function to shift-left binary bits
FUNCTION SHL (V#, X)
SHL = V# * 2 ^ X
END FUNCTION
' function to shift-right binary bits
FUNCTION SHR (V#, X)
SHR = V# / 2 ^ X
END FUNCTION
You can find this in BASCOM index:
varn = MAKEINT(LSB , MSB)
The equivalent code is:
varn = (256 * MSB) + LSB
Varn: Variable that will be assigned with the converted value.
LSB: Variable or constant with the LS Byte.
MSB: Variable or constant with the MS Byte.
For example:
varn = MAKEINT(&B00100010,&B11101101)
The result is &B1110110100100010.

How can you convert HEX to BIN, one character at a time in EXCEL 2010

I am trying to find a way to take a string of HEX values and convert them to BIN. I need to convert 1 HEX character at a time:
For example: HEX = 0CEC
BIN = 0000 1100 1110 1100
I need to do this in Excel. Any help would be great.
Thanks,
Larry
In a module:
Public Function HEX2BIN(strHex As String) As String
Dim c As Long, i As Long, b As String * 4, j As Long
For c = 1 To Len(strHex)
b = "0000"
j = 0
i = Val("&H" & Mid$(strHex, c, 1))
While i > 0
Mid$(b, 4 - j, 1) = i Mod 2
i = i \ 2
j = j + 1
Wend
HEX2BIN = HEX2BIN & b & " "
Next
HEX2BIN = RTrim$(HEX2BIN)
End Function
For:
=HEX2BIN("0CEC")
0000 1100 1110 1100
Yes, I had to do this recently. I'm late to the game, but other people will have to do this from time to time, so I'll leave the code where everyone can find it:
Option Explicit
Public Function HexToBinary(strHex As String, Optional PadLeftZeroes As Long = 5, Optional Prefix As String = "oX") As String
Application.Volatile False
' Convert a hexadecimal string into a binary
' As this is for Excel, the binary is returned as string: there's a risk that it will be treated as a number and reformatted
' Code by Nigel Heffernan, June 2013. Http://Excellerando.Blogspot.co.uk THIS CODE IS IN THE PUBLIC DOMAIN
' Sample Usage:
'
' =HexToBinary("8E")
' oX0010001110
'
' =HexToBinary("7")
' oX001111
'
' =HexToBinary("&HD")
' oX01101
Dim lngHex As Long
Dim lngExp As Long
Dim lngPad As Long
Dim strOut As String
Dim strRev As String
If Left(strHex, 2) = "&H" Then
lngHex = CLng(strHex)
Else
lngHex = CLng("&H" & strHex)
End If
lngExp = 1
Do Until lngExp > lngHex
' loop, bitwise comparisons with successive powers of 2
' Where bitwise comparison is true, append "1", otherwise append 0
strRev = strRev & CStr(CBool(lngHex And lngExp) * -1)
lngExp = lngExp * 2
Loop
' As we've done this in ascending powers of 2, the results are in reverse order:
If strRev = "" Then
HexToBinary = "0"
Else
HexToBinary = VBA.Strings.StrReverse(strRev)
End If
' The result is padded by leading zeroes: this is the expected formatting when displaying binary data
If PadLeftZeroes > 0 Then
lngPad = PadLeftZeroes * ((Len(HexToBinary) \ PadLeftZeroes) + 1)
HexToBinary = Right(String(lngPad, "0") & HexToBinary, lngPad)
End If
HexToBinary = Prefix & HexToBinary
End Function
You can use HEX2BIN(number, [places]).
The HEX2BIN function syntax has the following arguments:
Number Required. The hexadecimal number you want to convert. Number cannot contain more than 10 characters. The most significant bit of number is the sign bit (40th bit from the right). The remaining 9 bits are magnitude bits. Negative numbers are represented using two's-complement notation.
Places Optional. The number of characters to use. If places is omitted, HEX2BIN uses the minimum number of characters necessary. Places is useful for padding the return value with leading 0s (zeros).
I would use a simple formula as follows:
=HEX2BIN(MID(S23,1,2))&HEX2BIN(MID(S23,3,2))&HEX2BIN(MID(S23,5,2))&HEX2BIN(MID(S23,7,2)&HEX2BIN(MID(S23,9,2)&HEX2BIN(MID(S23,11,2)&HEX2BIN(MID(S23,13,2))
cell S23 = BFBEB991, Result = 10111111101111101011100110010001
This would allow it to be as long you need. Just add as many repetitions as you need incrementing the start position by 2 (eg 1, 3, 5, 7, 9, 11, 13, 15, ....). Note that the missing characters will be ignored.
For me, it gives this (sorry, in VBA, but has the advantage of not asking you the length of your string to convert). Be careful, I put a comment in the lower part for which you can add a space between each section of 4 bits. Some don't want the space and some will need it:
Length = Len(string_to_analyse)
For i = 1 To Length
Value_test_hexa = Left(Right(string_to_analyse, Length - (i - 1)), 1)
'get the asci value of each hexa character (actually can work as a decimal to binary as well)
Value_test = Asc(Value_test_hexa)
If Value_test > 47 And Value_test < 58 Then
Value_test = Value_test - 48
End If
' Convert A to F letters to numbers from 10 to 15
If Value_test > 64 And Value_test < 71 Then
Value_test = Value_test - 55
End If
'identify the values of the 4 bits for each character (need to round down)
a = WorksheetFunction.RoundDown(Value_test / 8, 0)
b = WorksheetFunction.RoundDown((Value_test - a * 8) / 4, 0)
c = WorksheetFunction.RoundDown((Value_test - a * 8 - b * 4) / 2, 0)
d = (Value_test - a * 8 - b * 4 - c * 2)
Value_converted = Value_converted & a & b & c & d ' can eventually add & " " in order to put a space every 4 bits
Next i
Tested OK so you can go with it.
Just leaving this here for anyone who needs it.
Instead of manually converting from hex to binary, I used Excel's built-in HEX2BIN function.
Function hexToBin(hexStr As String) As String
Dim i As Integer, b As String, binStr As String
For i = 1 To Len(hexStr)
b = Application.hex2bin(Mid(hexStr, i, 1), 4)
binStr = binStr & b
Next i
hexToBin = binStr
End Function

Resources