PB 3.5 8-to-7 Bit Text Compression
#1
Hello everyone, 
I developed a method to compress short texts by reducing 8-bit characters to 7-bit. While the compression ratio is low for large files, it efficiently reduces small strings. 

Key Steps
  1. Normalization: The input text is restricted to 7-bit ASCII (32–127).
  2. Offset Adjustment: Each character is decremented by 32 to fit within 0–95. 
  3. Compression
    For every 8 bytes, extract bits 0–6 (discarding bit 7).Pack these 7-bit segments into 7 bytes, using all 8 bits per byte. 
  4. Remaining Bytes: If the text length isn’t a multiple of 8, the last 1–7 bytes are copied uncompressed.
  5. Decompression
    Reverse the process: Expand 7-bit segments to 8 bits, then increment by 32. 

Here is the code, including some testing:

Code:
$Compile exe
$Dim All
$Lib All Off
$Error All Off
$String 2
$Com 0
$Float Procedure
$Optimize Size'Speed
$Cpu 80386
$Sound 1
$Stack 1536


'----------------------------------------------------------------------------
' Packe limitierten Text von 8 auf 7 Bit.
' Nutze dafür Zeiger zum Erhöhen der Geschwindigkeit
Declare _
FUNCTION PackTxt87 (sQuelle AS STRING) AS STRING
FUNCTION PackTxt87 (sQuelle AS STRING) AS STRING
  ' lokale Variablen
  DIM iQlen  AS LOCAL INTEGER          ' Länge von sQuelle
  DIM Q_ptr  AS LOCAL BYTE PTR         ' Zeichen-Pointer auf sQuelle
  Dim sZiel  As Local String           ' Komprimiertes sZiel
  DIM iZlen  AS LOCAL INTEGER          ' Länge von sZiel
  DIM Z_ptr  AS LOCAL BYTE PTR         ' Zeichen-Pointer auf sZiel
  DIM iZPos  AS LOCAL INTEGER          ' Position in sZiel
  DIM iQPos  AS LOCAL INTEGER          ' Position in sQuelle
  DIM iQLoop AS LOCAL INTEGER          ' Anzahl KompressionsSchleifen
  DIM iKompS AS LOCAL INTEGER          ' Anzahl komprimierbarer Zeichen
  Dim iRest  As Local Integer          ' Anzahl unkomprimierbarer Zeichen
  Dim bWert  As Local Byte             ' ASCII-32-Wert eines Zeichens.
  Dim iBitQ  As Local Integer          ' Aktuelle Bitbosition in sQuelle
  Dim iBitZ  As Local Integer          ' Aktuelle Bitbosition in sZiel
  Dim sTemp  As Local String

                                       '
  iQlen = LEN(sQuelle)                 ' Hole Länge der Quelle

  '--------------------------------------------------------------------------
  ' Fehler 1 abfangen
  If iQlen < 8 Then _                  ' String zu kurz?
    Function = sQuelle: _              ' Gib Original zurück
    Exit Function                      ' und raus

  iQLoop  = Fix(iQlen/8)               ' Hole Anzahl KompressionsSchleifen
  iRest  = iQlen Mod 8                 ' Hole Anzahl unkomprimierbarer Zeichen
  iKompS = iQLoop * 8                  ' Hole Anzahl komprimierbarer Zeichen
  iZlen  = iQLoop * 7 + iRest          ' Hole Länge komprimierten Strings
                                       '
  DECR iQlen                           ' Null-basiert für Pointer
  Q_ptr = STRPTR32( sQuelle )          ' Setze Pointer auf sQuelle

  '--------------------------------------------------------------------------
  ' Fehler 2 abfangen
  For iQPos = 0 To iQlen               ' Überprüfe auf ungültige Zeichen
    IF @Q_ptr[iQPos] < 32 _
      Or @Q_ptr[iQPos] > 159 _
      THEN
      sZiel = sZiel _                  ' sammle diese
            + Chr$(@Q_ptr[iQPos])
    End If
  Next iQPos

  If Len(sZiel) Then _
    Function = "Falsche Quelle: "+sZiel : _
    Exit Function                      ' und gib sie zurück

  sZiel = Repeat$(iZlen,Chr$(0))       ' Platz für Kompression in sZiel

  Z_ptr = STRPTR32( sZiel  )           ' Setze Pointer auf sZiel
  iZPos = 0                            ' Position im Pointer = 0
  iBitQ = 0                            ' Bitbosition in sQuelle auf 0
  iBitZ = 0                            ' Bitbosition in sZiel  auf 0

  DECR iKompS                          ' Null-basiert für Pointer

  '--------------------------------------------------------------------------
  ' Kompression:
  For iQPos = 0 To iKompS              ' Für komprimierbare Zeichen in sQuelle:
                                       ' 1. Schritt:
    bWert = @Q_ptr[iQPos] - 32         ' ASCII-Wert um 32 vermindern, dass
                                       ' ASCII zwischen 0-159 ist
    For iBitQ = 0 To 6                 ' Lies aus sQuelle Bit 0-6

      If Bit(bWert,iBitQ) = 1 Then     ' Schreibe Ziel-Bits 0-7
        Bit Set @Z_ptr[iZPos], iBitZ   '
      Else                             '
        Bit ReSet @Z_ptr[iZPos], iBitZ '
      End If

      If iBitZ < 7 Then                ' Solange in Ziel zwischen Bit 0 und 7
        Incr iBitZ                     ' Nächstes Bit
      Else                             ' sonst
        iBitZ = 0                      ' Bit 0 in
        Incr iZPos                     ' nächstem Zeichen
      End If
    Next iBitQ
  Next iQPos

  iZPos = iZlen-iRest                  ' Setze Zeiger auf erstes unpackbares
                                       ' Zeichen
  For iQPos = iQlen-iRest+1 To iQlen   ' Für unpackbare Zeichen in sQuelle
    @Z_ptr[iZPos] = @Q_ptr[iQPos]      ' Schreibe unpackbare Zeichen
    Incr iZPos
  Next iQPos


  Function = sZiel

END FUNCTION


'----------------------------------------------------------------------------
' EntPacke limitierten Text von 8 auf 7 Bit.
' Nutze dafür Zeiger zum Erhöhen der Geschwindigkeit
Declare _
FUNCTION DePackTxt87 (sQuelle AS STRING) AS STRING
FUNCTION DePackTxt87 (sQuelle AS STRING) AS STRING
  ' lokale Variablen
  DIM iQlen  AS LOCAL INTEGER          ' Länge von sQuelle
  DIM Q_ptr  AS LOCAL BYTE PTR         ' Zeichen-Pointer auf sQuelle
  Dim sZiel  As Local String           ' Komprimiertes sZiel
  DIM iZlen  AS LOCAL INTEGER          ' Länge von sZiel
  DIM Z_ptr  AS LOCAL BYTE PTR         ' Zeichen-Pointer auf sZiel
  DIM iZPos  AS LOCAL INTEGER          ' Position in sZiel
  DIM iQPos  AS LOCAL INTEGER          ' Position in sQuelle
  DIM iQLoop AS LOCAL INTEGER          ' Anzahl KompressionsSchleifen
  DIM iKompS AS LOCAL INTEGER          ' Anzahl komprimierbarer Zeichen
  Dim iRest  As Local Integer          ' Anzahl unkomprimierbarer Zeichen
  Dim bWert  As Local Byte             ' ASCII-32-Wert eines Zeichens.
  Dim iBitQ  As Local Integer          ' Aktuelle Bitbosition in sQuelle
  Dim iBitZ  As Local Integer          ' Aktuelle Bitbosition in sZiel
  Dim sTemp  As Local String
  Dim bPack  As Local Byte             ' Gepackte Bytes an sQuelleEnde

  iQlen = LEN(sQuelle)                 ' Hole Länge der Quelle

  '--------------------------------------------------------------------------
  ' Fehler 1 abfangen
  If iQlen < 7 Then _                  ' String zu kurz?
    Function = sQuelle: _              ' Gib Original zurück
    Exit Function                      ' und raus

  Q_ptr = STRPTR32( sQuelle )          ' Setze Pointer auf sQuelle

  iQLoop  = Fix(iQlen/7)               ' Hole Anzahl EntPacksSchleifen
  iRest  = iQlen Mod 7                 ' Anzahl ungepackte Zeichen?

  '--------------------------------------------------------------------------
  ' Fehler 2 abfangen

If iRest = 0 Then                      ' Scheint alles gepackt zu sein, dann
  bPack = 0                            ' GePacktes = 0
  For iQPos = iQlen - 7 To iQlen-1     ' Überprfe letzte 7 Zeichen auf
    IF @Q_ptr[iQPos] < 32 _            ' komprimierte Zeichen
      Or @Q_ptr[iQPos] > 127 _         ' Wenn welche da, dann
      THEN Incr bPack                  ' Erhöhe Gepacktes (bPack)
  Next iQPos
End If


  If bPack = 0? _                      ' Wenn keine gepackten Zeichen (bPack)
    And iRest = 0 _                    ' und keine ungepackten Zeichen (iRest)
    Then                               ' dann
    iRest = 7                          ' ungepackte Zeichen (iRest) = 7
    iKompS = iQlen - iRest             ' Setze Anzahl entpackbarer Zeichen
  Else                                 ' sonst
    iKompS = iQLoop * 7                ' Setze Anzahl entpackbarer Zeichen
  End If


  If IsFalse(bPack) _                  ' Sind die iRest-lichen Zeichen
    And iRest = 7 _                    ' ungepackt, dann reduziere
    Then Decr iQLoop                   ' die Anzahl der Entpackaufrufe


  iZlen  = iQLoop * 8 + iRest          ' Hole Länge komprimierten Strings
                                       '
  DECR iQlen                           ' Null-basiert für Pointer

  sZiel = Repeat$(iZlen,Chr$(0))       ' Platz für Kompression in sZiel

  Z_ptr = STRPTR32( sZiel  )           ' Setze Pointer auf sZiel
  iZPos = 0                            ' Position im Pointer = 0
  iBitQ = 0                            ' Bitbosition in sQuelle auf 0
  iBitZ = 0                            ' Bitbosition in sZiel  auf 0

  '--------------------------------------------------------------------------
  ' DeKompression: Rechne Zeichen in sQuelle auf ASCII 32-159 um
  For iQPos = 0 To iKompS              ' Fr entkomprimierbare Zeichen in sQuelle:
                                       ' 1. Schritt:
    For iBitQ = 0 To 7                 ' Lies aus sQuelle Bit 0-7

      If Bit(@Q_ptr[iQPos],iBitQ) = 1 Then ' Schreibe Ziel-Bits 0-7
        Bit Set @Z_ptr[iZPos], iBitZ   '
      Else                             '
        Bit ReSet @Z_ptr[iZPos], iBitZ '
      End If

      If iBitZ < 6 Then                ' Solange in Ziel zwischen Bit 0 und 6
        Incr iBitZ                     ' Nächstes Bit
      Else                             ' sonst
        iBitZ = 0                      ' Bit 0
        Incr @Z_ptr[iZPos], 32         ' Erstelle das richtige ZielZeichen
        Incr iZPos                     ' und nächstes ZielZeichen
      End If

    Next iBitQ

  Next iQPos

  iZPos = iZlen-iRest                  ' Setze Zeiger auf erstes unpackbares
                                       ' Zeichen
  For iQPos = iQlen-iRest+1 To iQlen   ' Für ungepackte Zeichen in sQuelle
    @Z_ptr[iZPos] = @Q_ptr[iQPos]      ' Schreibe ungepackte Zeichen
    Incr iZPos
  Next iQPos


  Function = sZiel

END FUNCTION


' Test-Code'

DIM originalText AS STRING, _
    compressedText AS STRING, _
    decompressedText AS STRING, _
    iIndex As Integer, _
    iLOrg  As Integer, _
    iLPck  As Integer, _
    iLEpk  As Integer

For iIndex = 07 To 19
  '?
  originalText = Left$("PowerBASIC 3.5 is a wonderful tool for writing fast and usefull programs.",iIndex)

  iLOrg = Len(originalText)
  compressedText = PackTxt87(originalText)
  iLPck = Len(compressedText)
  If Instr(compressedText, "Falsche Quelle: ") = 1 Then ?compressedText:end
  decompressedText = DePackTxt87(compressedText)
  iLEpk = Len(decompressedText)
  'PRINT "Original:      ";
  PRINT "Original: ";
  Print originalText;
  $If 0
  Print " ist =";iLOrg;" Byte lang"
  PRINT "Komprimiert:  ";
  Print compressedText;
  Print " ist =";iLPck;" Byte lang"
  PRINT "Dekomprimiert: ";
  Print decompressedText;
  Print " ist =";iLEpk;" Byte lang"
  $EndIf
  ? " --> Test verlief: [erfolg";
  If originalText = decompressedText Then
    Print "reich] :-)  :";iLOrg;iLPck;iLEpk
  Else
  Print "los] ;-(";iLOrg;iLPck;iLEpk
  ?"Original und wieder Entpacktes unterscheiden sich an folgenden Stellen:"
  For iIndex = 1 To iLOrg
    If ASCII(originalText,iIndex) <> ASCII(decompressedText,iIndex) Then
        ?"Position";iIndex;" - Zeichen Original: ";Chr$(ASCII(originalText,iIndex)); _
        " - Zeichen Entpackt: ";Chr$(ASCII(decompressedText,iIndex))
      End If
    Next iIndex
  End If

Next iIndex

Sleep

originalText = "PowerBASIC 3.5 is a wonderful tool for writing fast, small and usefull programs under DOS."
iLOrg = Len(originalText)
compressedText = PackTxt87(originalText)
iLPck = Len(compressedText)
If Instr(compressedText, "Falsche Quelle: ") = 1 Then ?compressedText:end
decompressedText = DePackTxt87(compressedText)
iLEpk = Len(decompressedText)
 
  PRINT "Original:"
  Print originalText;
  ? " --> Test verlief: [erfolg";
  If originalText = decompressedText Then
    Print "reich] :-)  :";iLOrg;iLPck;iLEpk
  Else
  Print "los] ;-(";iLOrg;iLPck;iLEpk
  ?"Original und wieder Entpacktes unterscheiden sich an folgenden Stellen:"
  For iIndex = 1 To iLOrg
    If ASCII(originalText,iIndex) <> ASCII(decompressedText,iIndex) Then
        ?"Position";iIndex;" - Zeichen Original: ";Chr$(ASCII(originalText,iIndex)); _
        " - Zeichen Entpackt: ";Chr$(ASCII(decompressedText,iIndex))
      End If
    Next iIndex
  End If
 
  PRINT "Komprimiert:"
  Print compressedText
  Print "ist =";iLPck;" Byte lang"
  PRINT "Dekomprimiert:"
  Print decompressedText
  Print "ist =";iLEpk;" Byte lang"



originalText = "PowerBASIC 3.5 ist ein wundervolles Werkzeug fr schnelle, kleine bis groáe und vor allen ¯ntzliche® Programme unter DOS."
?originalText
iLOrg = Len(originalText)
compressedText = PackTxt87(originalText)
iLPck = Len(compressedText)
If Instr(compressedText, "Falsche Quelle: ") = 1 Then ?compressedText:end
decompressedText = DePackTxt87(compressedText)
iLEpk = Len(decompressedText)
 
  PRINT "Original:"
  Print originalText;
  ? " --> Test verlief: [erfolg";
  If originalText = decompressedText Then
    Print "reich] :-)  :";iLOrg;iLPck;iLEpk
  Else
  Print "los] ;-(";iLOrg;iLPck;iLEpk
  ?"Original und wieder Entpacktes unterscheiden sich an folgenden Stellen:"
  For iIndex = 1 To iLOrg
    If ASCII(originalText,iIndex) <> ASCII(decompressedText,iIndex) Then
        ?"Position";iIndex;" - Zeichen Original: ";Chr$(ASCII(originalText,iIndex)); _
        " - Zeichen Entpackt: ";Chr$(ASCII(decompressedText,iIndex))
      End If
    Next iIndex
  End If
 
  PRINT "Komprimiert:"
  Print compressedText
  Print "ist =";iLPck;" Byte lang"
  PRINT "Dekomprimiert:"
  Print decompressedText
  Print "ist =";iLEpk;" Byte lang"


This is the DOS-output:
Code:
Original: PowerBA --> Test verlief: [erfolgreich] :-)  : 7  7  7
Original: PowerBAS --> Test verlief: [erfolgreich] :-)  : 8  7  8
Original: PowerBASI --> Test verlief: [erfolgreich] :-)  : 9  8  9
Original: PowerBASIC --> Test verlief: [erfolgreich] :-)  : 10  9  10
Original: PowerBASIC  --> Test verlief: [erfolgreich] :-)  : 11  10  11
Original: PowerBASIC 3 --> Test verlief: [erfolgreich] :-)  : 12  11  12
Original: PowerBASIC 3. --> Test verlief: [erfolgreich] :-)  : 13  12  13
Original: PowerBASIC 3.5 --> Test verlief: [erfolgreich] :-)  : 14  13  14
Original: PowerBASIC 3.5  --> Test verlief: [erfolgreich] :-)  : 15  14  15
Original: PowerBASIC 3.5 i --> Test verlief: [erfolgreich] :-)  : 16  14  16
Original: PowerBASIC 3.5 is --> Test verlief: [erfolgreich] :-)  : 17  15  17
Original: PowerBASIC 3.5 is  --> Test verlief: [erfolgreich] :-)  : 18  16  18
Original: PowerBASIC 3.5 is a --> Test verlief: [erfolgreich] :-)  : 19  17  19

Original:PowerBASIC 3.5 is a wonderful tool for writing fast, small and usefull
programs under DOS. --> Test verlief: [erfolgreich] :-)  : 90  79  90
Komprimiert:
░τ╡(§àf⌐◄`Γ¿ ÆS@►p}:ëE⌐▒╩♦PƒO&└°ö☻«╥$5Θ<☻î┴)ò☺ÿ6âL& Φ$☻¬╙ó▒╩d☻á╥τQ→lN☺U'▒(♣É^S.
ist = 79  Byte lang
Dekomprimiert:
PowerBASIC 3.5 is a wonderful tool for writing fast, small and usefull programs
under DOS.
ist = 90  Byte lang
PowerBASIC 3.5 ist ein wundervolles Werkzeug für schnelle, kleine bis große und
vor allen »nützliche« Programme unter DOS.
Falsche Quelle: ß»« 

Perhaps the code will also be of use to others...
Reply


Messages In This Thread
PB 3.5 8-to-7 Bit Text Compression - by Andy Dee - 04-03-2025, 10:55 PM

Forum Jump:


Users browsing this thread: