VOGONS


First post, by Peter Swinkels

User metadata
Rank Oldbie
Rank
Oldbie

Curious as to how floating points exactly work internally (suprisingly despite all my years of programming I've never really gotten into that in depth) I wrote these two programs in QBasic:

This is a tiny experiment where the user can define the four hexadecimal bytes that make up a single precision floating point and then view the result:

DEFINT A-Z

RESTORE hexes
bin$ = ""
FOR byt = 0 TO 3
READ hx$
hxv = VAL("&H" + hx$)
bin$ = CHR$(hxv) + bin$
NEXT byt

CLS
PRINT CVS(bin$)

END

hexes:
DATA "3F": REM Sign (0x80) + scale.
DATA "80": REM Exponent mbs (0x80) + mantisse.
DATA "00": REM Mantisse.
DATA "00": REM Mantisse.

And this is a program which allows the user to specify a single precision floating point and view its inner structure in binary:

DEFINT A-Z
DECLARE SUB disect (flt AS SINGLE)

CLS

disect -1

SUB disect (flt AS SINGLE)
bin$ = MKS$(flt)

byte1 = ASC(MID$(bin$, 1, 1))
byte2 = ASC(MID$(bin$, 2, 1))
byte3 = ASC(MID$(bin$, 3, 1))
byte4 = ASC(MID$(bin$, 4, 1))

mant1 = byte1
mant2 = byte2
mant3 = (byte3 AND &H7F)
exponentmbs = ABS(((byte3 AND &H80) <> 0))
scale = (byte4 AND &H7F)
sign = ((byte4 AND &H80) <> 0)
totalmantisse& = mant1 + (mant2 * 256&) + (mant3 * 65536)
totalexponent = scale + (exponentmbs * 128)

PRINT "Sign: Scale: Exponent mbs: Mant 3: Mant 2: Mant 1:"
PRINT USING " ### ### ### ### ### ###"; sign; scale; exponentmbs; mant3; mant2; mant1
PRINT
PRINT totalmantisse&
PRINT totalexponent
END SUB

Please note that these programs are experimental and that I haven't fully tested everything yet.

If you notice a bug or mistake, you are of course welcome to let me know. 😀

Oh, and why QBasic? I felt like doing a bit of retro-programming at the same time and at this moment I am specifically curious about floating points in older BASIC dialects.

My GitHub:
https://github.com/peterswinkels

Reply 1 of 5, by wbahnassi

User metadata
Rank Oldbie
Rank
Oldbie

It would be interesting to try and run this on different CPUs and architectures where BASIC was available (I doubt BASICs made use of the math co-processor, so it's all software-emulated). Floating point IEEE 754 standards weren't there in the early 80s.. so every implementation probably did something different.

Turbo XT 12MHz, EGA, MFM HDD
Intel 386 DX-33, Speedstar 24X, SB 1.5, 1x CD
Intel 486 DX2-66, CL5428 VLB, SBPro 2, 2x CD
IBM BlueLightning 100MHz, CL5428, SB16, 4x CD
Intel Pentium 90, Matrox Millenium 2, SB16, 4x CD
HP Z400, Xeon 3.46GHz, YMF-744, RTX2060

Reply 2 of 5, by bakemono

User metadata
Rank Oldbie
Rank
Oldbie

QuickBASIC supported x87 math but QBASIC didn't. In the 8-bit days there were BASIC interpreters that used 3, 4, and 5-byte floating point formats internally. Somewhere I saw a short article comparing different platforms of the day in terms of speed and accuracy (maybe it was in DTACK Grounded)

Wreckage reimagined on itch: https://90soft90.itch.io/wreckage-dash

Reply 3 of 5, by gerry

User metadata
Rank l33t
Rank
l33t

hey, just a note of appreciation, interesting topic and nice to see a QB approach

Reply 4 of 5, by Peter Swinkels

User metadata
Rank Oldbie
Rank
Oldbie

Okay, I decided to move to vb6 which uses the same IEEE754 standard as QBasic for floating points:

'This module contains this program's core procedures.
Option Explicit

Private Const EXPONENT_MASK As Long = &H7F800000 'Defines the exponent's bitmask.
Private Const EXPONENT_SHIFT As Long = &H800000 'Defines the divisor used to right-shift the exponent's bits.
Private Const MANTISSA_MASK As Long = &H7FFFFF 'Defines the mantissa's bitmask.
Private Const SIGN_MASK As Long = &H80000000 'Defines the sign's bitmask.

'The Microsoft Windows API constants used by this program.
Private Declare Sub RtlMoveMemory Lib "kernel32" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)

'This procedure is executed when this program is started.
Public Sub Main()
Dim Bytes As Long
Dim Exponent As Long
Dim Float As Single
Dim Mantissa As Long
Dim Reconstruction As Single
Dim Sign As Long
Dim TotalMantissa As Double

Debug.Print String$(50, "=")

Float = 0 '<---Replace with your own number.
Debug.Print "Float: "; Float

RtlMoveMemory Bytes, Float, Length:=4
Debug.Print "Bytes: "; Hex$(Bytes); "h"

If (Bytes And SIGN_MASK) = SIGN_MASK Then Sign = -1 Else Sign = 1
Debug.Print "Sign: "; Sign

Exponent = ((Bytes And EXPONENT_MASK) \ EXPONENT_SHIFT)
Debug.Print "Exponent: "; Exponent; " (Real exponent: " & (Exponent - 127) & ")"

Mantissa = Bytes And MANTISSA_MASK
Debug.Print "Mantissa: "; Mantissa

TotalMantissa = 1# + (Mantissa / 8388608#)
Debug.Print "Total Mantissa: "; TotalMantissa

Reconstruction = Sign * TotalMantissa * (2 ^ (Exponent - 127))

Debug.Print "Reconstructed: "; Reconstruction
End Sub

This can probably be converted to QBasic relatively easily.

My GitHub:
https://github.com/peterswinkels

Reply 5 of 5, by Peter Swinkels

User metadata
Rank Oldbie
Rank
Oldbie
Attribute VB_Name = "Module1"
Option Explicit

Public Sub Main()
Debug.Print QBasicFloatToString(1.233)

End Sub

Public Function QBasicFloatToString(Float As Single) As String
Dim CommaPosition As Long
Dim Digit As Long
Dim Index As Long
Dim Log10 As Long
Dim Numbers As String
Dim Result As String
Dim Scaled As Double
Dim SignStr As String

If Float = 0# Then
QBasicFloatToString = "0"
Exit Function
End If

If Float < 0 Then
SignStr = "-"
Float = Abs(Float)
End If

Log10 = CInt(Log(Float) / Log(10#))

Scaled = Float / (10# ^ Log10)

Numbers = vbNullString
For Index = 1 To 7
Digit = Int(Scaled)
Numbers = Numbers & CStr(Digit)
Scaled = (Scaled - Digit) * 10#
Next Index

CommaPosition = Log10 + 1

If CommaPosition > 0 And CommaPosition <= 7 Then
Result = Left$(Numbers, CommaPosition) & "." & Mid$(Numbers, CommaPosition + 1)
Else
If CommaPosition > 7 Then
Result = Numbers & String$(CommaPosition - 7, "0")
Else
Result = "0." & String$(Abs(CommaPosition), "0") & Numbers
End If
End If

If InStr(Result, ".") > 0 Then
Do While Right$(Result, 1) = "0"
Result = Left$(Result, Len(Result) - 1)
Loop
If Right$(Result, 1) = "." Then Result = Left$(Result, Len(Result) - 1)
End If

QBasicFloatToString = SignStr & Result
End Function

This should mimick the way QBasic converts single precision floating points to strings.

My GitHub:
https://github.com/peterswinkels