Convert a decimal number string to a Base36 number string
There are 2 ways to make this conversion…
WAY 1
If the encoding have to result in a string having only “printable” characters (#32..#126) then an easy packing method not requiring any complex calculation would be BCD: pack two digits into a byte, giving a 50% size reduction:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
function NumStringToBCD(const inStr: string): string; function Pack(ch1, ch2: Char): Char; begin Assert((ch1 >= '0') and (ch1 <= '9')); Assert((ch2 >= '0') and (ch2 <= '9')); Result := Chr((Ord(ch1) and $F) or ((Ord(ch2) and $F) shl 4)) end; var i: Integer; begin if Odd(Length(inStr)) then Result := NumStringToBCD('0' + inStr) else begin SetLength(Result, Length(inStr) div 2); for i := 1 to Length(Result) do Result[i] := Pack(inStr[2 * i - 1], inStr[2 * i]); end; end; function BCDToNumString(const inStr: string): string; procedure UnPack(ch: Char; var ch1, ch2: Char); begin ch1 := Chr((Ord(ch) and $F) + $30); ch2 := Chr(((Ord(ch) shr 4) and $F) + $30); Assert((ch1 >= '0') and (ch1 <= '9')); Assert((ch2 >= '0') and (ch2 <= '9')); end; var i: Integer; begin SetLength(Result, Length(inStr) * 2); for i := 1 to Length(inStr) do UnPack(inStr[i], Result[2 * i - 1], Result[2 * i]); end; procedure TForm1.Button1Click(Sender: TObject); var S1, S2: string; begin S1 := '15151515151515151515'; S2 := NumStringToBCD(S1); memo1.lines.add('S1: ' + S1); memo1.lines.add('Length(S2): ' + IntToStr(Length(S2))); memo1.lines.add('S2 unpacked again: ' + BCDToNumString(S2)); end; |
WAY 2
This DecimalStrToBase36Str seems to work on smaller inputs, but check output on the larger inputs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
{ ... } const Base36Digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; type tArrayElement = Byte; tDoubleElement = Word; const SizeOfAryElem = SizeOf(tArrayElement); BitsInBufElem = SizeOfAryElem * 8; function DecimalStrToBase36Str(const Value: string): string; var Man: array[0..19] of tArrayElement; NbrManElem, Cry, i, j, n, Tmp: integer; Tmp1, Tmp2: packed record case byte of 0: (Wd: tDoubleElement); 1: (Lo, Hi: tArrayElement); end; begin n := length(Value); if n <> 20 then raise Exception.CreateFmt('Input string must be 20 decimal digits, not %d digits', [n]); NbrManElem := 0; for i := 1 to n do begin Cry := ord(Value[i]) - ord('0'); if (Cry < 0) or (Cry > 9) then raise Exception.CreateFmt('Input string contains non-decimal digit (%s)', [Value[i]]); {Multiply accumulation by 10 and add k:} for j := 0 to NbrManElem - 1 do begin Tmp := Man[j] * 10 + Cry; Man[j] := Tmp and $FF; Cry := Tmp shr 8; end; if Cry <> 0 then begin Inc(NbrManElem); Man[NbrManElem - 1] := Cry; end; end; SetLength(Result, 14); for i := 14 downto 1 do begin Tmp1.Hi := 0; for j := NbrManElem - 1 downto 0 do begin Tmp1.Lo := Man[j]; Tmp2.Wd := Tmp1.Wd div 36; Assert(Tmp2.Hi = 0); Man[j] := Tmp2.Lo; Tmp1.Hi := Tmp1.Wd mod 36; end; Result[i] := Base36Digits[Tmp1.Hi + 1]; if (NbrManElem > 0) and (Man[NbrManElem - 1] = 0) then begin dec(NbrManElem); end; end; end; |