 Mirror

Convert a decimal number string to a Base36 number string (Views: 100)

 Problem/Question/Abstract:I have a 20 digit string, all numbers, and I would like to convert this to a Base36 to take up less space. I have tried the Borland Radix() routine but this would not work on such a large number. Does anyone have an idea on how to convert the decimal number string to aBase36 number string?Answer:Solve 1:Does the encoding have to result in a string having only "printable" characters (#32..#126) or is any byte value allowed? If so an easy packing method not requiring any complex calculation would be BCD: pack two digits into a byte, giving a 50% size reduction: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'));    {Ord('0') is \$30, so we can just use the low nybble of the character as value.}    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;Solve 2:This DecimalStrToBase36Str seems to work on smaller inputs, but I suggest that you check output on the larger inputs.{ ... }const  Base36Digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';type  tArrayElement = Byte;  tDoubleElement = Word;  {tArrayElement  = Word;}  {tDoubleElement = LongWord;}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    {Divide by 36 and save the remainder:}    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;

<< Back to main page