0

I'm writing a bunch of macros to deal with big numbers in MASM, and I have found myself needing to convert from a series of digits to a number. Basically, to get around MASM size restrictions, I've been passing bignums as strings. So, a bignum call would look something like:

MOV_BIG_NUM [eax], <1234567891011121314151617181920212223>

I've got an implementation that can handle (as far as I can tell) strings passed in bases that are powers of 2. That is, calls like:

MOV_BIG_NUM [eax], <1001101111010101101011110101000011101b> ;Base 2
MOV_BIG_NUM [eax], <0123456710121314151617202122232425262o> ;Base 8
MOV_BIG_NUM [eax], <123456789ABCDEF101112131415161718191Ah> ;Base 16

Will be handled fine. However, the method I'm using for those doesn't translate well (or indeed at all) to bases that are not powers of 2. The most important of these, of course, is decimal, but I'd love to get a method that functions with an arbitrary base. The method I've been using for powers of 2 has been to access bytes sequentially, shift the digits if necessary, and bitwise or them with the existing digits. So my method for hexadecimal numbers looks something like this (This is a highly simplified version):

;foreach digit in list of digits
  ;eax contains the start of memory for the bignum
  IF bit_offset EQ 0 ;Only move a new value in when the offset has just changed
    mov ebx, dword ptr [eax + byte_offset] ;Get the current value at the dword's offset
  ENDIF

  digit = digit SHL bit_offset ;Shift the digit by a certain offset, so that we can store multiple digits in one byte

  or ebx, digit

  bit_offset = bit_offset + 4

  IF bit_offset EQ ( 32 ) ;Number of bits in a dword
    mov dword ptr [eax + byte_offset], ebx ;Move the dword back
    byte_offset = byte_offset + 4 ;Number of bytes in a dword
    bit_offset = 0;
  ENDIF
;end foreach

However, this kind of approach obviously won't work for an arbitrary base. How would I go about converting a sequential list of decimal digits into a hex number?

Rose Kunkel
  • 3,102
  • 2
  • 27
  • 53

1 Answers1

1

You could perform arithmetic multiplication by 10 and addition of a digit with hex strings. From that you can compose decimal to hexadecimal string conversion.

Illustration in C:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef unsigned uint;

int chhex2val(char ch)
{
  if (ch >= '0' && ch <= '9')
    return ch - '0';
  if (ch >= 'A' && ch <= 'F')
    return ch - 'A' + 10;
  if (ch >= 'a' && ch <= 'f')
    return ch - 'a' + 10;
  abort();
  return 0;
}

char val2chhex(int v)
{
  if (v >= 0 && v < 16)
    return "0123456789ABCDEF"[v];
  abort();
  return '0';
}

// Multiplies a hex string like "17F" by 10 and
// returns a string with the product (e.g. "0EF6").
// The original string isn't modified.
char* mulh10(char* h)
{
  size_t l = strlen(h);
  char* p = malloc(l + 1 + 1);
  size_t i;
  uint c = 0;

  if (p == NULL)
    abort();

  p[l + 1] = '\0';
  for (i = 0; i < l; i++)
  {
    c += chhex2val(h[l - 1 - i]) * 10;
    p[l - i] = val2chhex(c % 16);
    c /= 16;
  }
  p[0] = val2chhex(c);

  return p;
}

// Adds (arithmetically) to a hex string like "17F" a hex/dec digit, e.g. '9'.
// Returns the modified original string (e.g. "188").
char* addhd(char* h, char d)
{
  size_t l = strlen(h);
  size_t i;
  uint c = chhex2val(d);

  for (i = 0; c && i < l; i++)
  {
    c += chhex2val(h[l - 1 - i]);
    h[l - 1 - i] = val2chhex(c % 16);
    c /= 16;
  }

  return h;
}

int main(void)
{
  char num[] = "17F";
  printf("\"17F\" (hex) * 10 = \"%s\" (hex)\n", mulh10(num));
  printf("\"17F\" (hex) + '9' = \"%s\" (hex)\n", addhd(num, '9'));
  printf("\"65535\" (dec) = \"%s\" (hex)\n",
         addhd(mulh10(addhd(mulh10(addhd(mulh10(addhd(mulh10(addhd(mulh10(
         "0"), '6')), '5')), '5')), '3')), '5'));
  return 0;
}

Output (ideone):

"17F" (hex) * 10 = "0EF6" (hex)
"17F" (hex) + '9' = "188" (hex)
"65535" (dec) = "00FFFF" (hex)
Alexey Frunze
  • 61,140
  • 12
  • 83
  • 180