Long multiply, 8 bits at a time
Perl, 137 characters
($x,$y)=<>;while($x=~s/.. *//s){$e=hex$&;$i=0;$s=$r[$i]+=$e*hex,$r[$i]&=255,$r[++$i]+=$s>>8 for$y=~/.. */gs;$y="00$y"}printf'%02x 'x@r,@r
Caveats
- Sometimes prints an extra
00
byte at the end of the result. Of course the result is still correct even with that extra byte. - Prints an extra space after the last hex byte in the result.
Explanation
The explanation is going to be a bit long, but I think most people here will find it interesting.
First of all, when I was 10 years old, I was taught the following little trick. You can multiply any two positive numbers with this. I will describe this using the example of 13 × 47. You start by writing the first number, 13, and dividing it by 2 (round down each time) until you reach 1:
13
6
3
1
Now, next to the 13 you write the other number, 47, and keep multiplying it by 2 the same number of times:
13 47
6 94
3 188
1 376
Now you cross out all the lines where the number on the left is even. In this case, this is only the 6. (I can’t do strike-through in code, so I’ll just remove it.) Finally, you add all the remaining numbers on the right:
13 47
3 188
1 376
----
611
And this is the right answer. 13 × 47 = 611.
Now, since you are all computer geeks, you will have realised that what we’re actually doing in the left and right columns is x >> 1
and y << 1
, respectively. Furthermore, we add the y
only if x & 1 == 1
. This translates directly into an algorithm, which I’ll write here in pseudocode:
input x, y
result = 0
while x > 0:
if x & 1 == 1:
result = result + y
x = x >> 1
y = y << 1
print result
We can re-write the if
to use a multiplication, and then we can easily change this so that it works on a byte-by-byte basis instead of bit-by-bit:
input x, y
result = 0
while x > 0:
result = result + (y * (x & 255))
x = x >> 8
y = y << 8
print result
This still contains a multiplication with y
, which is arbitrary-size, so we need to change that into a loop too. We’ll do that in Perl.
Now translate everything to Perl:
$x
and$y
are the inputs in hex format, so they have the least significant byte first.Thus, instead of
x >> 8
I do$x =~ s/.. *//s
. I need the space+star because the last byte might not have a space on it (could use space+?
too). This automatically puts the removed byte (x & 255
) into$&
.y << 8
is simply$y = "00$y"
.The
result
is actually a numerical array,@r
. At the end, each element of@r
contains one byte of the answer, but halfway through the calculation it may contain more than one byte. I’ll prove to you below that each value is never more than two bytes (16 bits) and that the result is always one byte at the end.
So here is the Perl code unravelled and commented:
# Input x and y
($x, $y) = <>;
# Do the equivalent of $& = x & 255, x = x >> 8
while ($x =~ s/.. *//s)
{
# Let e = x & 255
$e = hex $&;
# For every byte in y... (notice this sets $_ to each byte)
$i = 0;
for ($y =~ /.. */gs)
{
# Do the multiplication of two single-byte values.
$s = $r[$i] += $e*hex,
# Truncate the value in $r[$i] to one byte. The rest of it is still in $s
$r[$i] &= 255,
# Move to the next array item and add the carry there.
$r[++$i] += $s >> 8
}
# Do the equivalent of y = y << 8
$y = "00$y"
}
# Output the result in hex format.
printf '%02x ' x @r, @r
Now for the proof that this always outputs bytes, and that the calculation never generates values greater than two bytes. I’ll prove this by induction over the while
loop:
The empty
@r
at the beginning clearly has no values greater than 0xFF in it (because it has no values in it at all). This concludes the base case.Now, given that
@r
contains only single bytes at the beginning of eachwhile
iteration:The
for
loop explicitly&=
s all values in the result array with 255 except the last one, so we only need to look at that last one.We know that we always remove only one byte from
$x
and$y
:Therefore,
$e*hex
is a multiplication of two single-byte values, which means it’s in the range0 — 0xFE01
.By the inductive hypothesis,
$r[$i]
is one byte; therefore,$s = $r[$i] += $e*hex
is in the range0 — 0xFF00
.Therefore,
$s >> 8
is always one byte.
$y
grows an extra00
in each iteration of thewhile
loop:Therefore, in every iteration of the
while
loop, the innerfor
loop runs for one more iteration than it did in the previouswhile
iteration.Therefore, the
$r[++$i] += $s >> 8
in the last iteration of thefor
loop always adds$s >> 8
to0
, and we already established that$s >> 8
is always one byte.
Therefore, the last value stored in
@r
at the end of thefor
loop is also a single byte.
This concludes a wonderful and exciting challenge. Thanks a lot for posting it!
C Solution
This solution does no input validation. It's also only lightly tested. Speed was not really a consideration. Malloc's memory, and isn't particularly clever about how much it grabs. Guaranteed to be enough, and more than necessary.
m() accepts a string, expects two newlines in the string, one after each number. Expects only numbers, lowercase characters, spaces, and newlines. Expects hex digits to always be a pair.
No multiply operation is ever used (knowingly). Shifting is performed on 8-bit variables. One 16-bit addition is performed. No 32-bit data types.
Shrunk by hand, and only mildly. edit: more obfuscation, fewer chars :D Compiles with warnings with gcc.
Characters: 675
typedef unsigned char u8;
#define x calloc
#define f for
#define l p++
#define E *p>57?*p-87:*p-48
#define g(a) --i;--a;continue
void m(u8*d){short n=0,m=0,a,b,i,k,s;u8*t,*q,*r,*p=d,o;f(;*p!=10;n++,l){}l;f(;*p
!=10;m++,l){}t=x(n,1);q=x(m,1);r=x(n,1);p=d;a=n;i=0;f(;*p!=10;i++,l){if(*p==32){
g(a);}t[i]=E;t[i]<<=4;l;t[i]|=E;}a/=2;b=m;i=0;l;f(;*p!=10;i++,l){if(*p==32){g(b)
;}q[i]=E;q[i]<<=4;l;q[i]|=E;}b/=2;f(k=0;k<8*b;k++){if(q[0]&1){o=0;f(i=0;i<n;i++)
{s=o+t[i]+r[i];o=s>>8;r[i]=s&255;}}f(i=n;i;i--){o=t[i-1]>>7&1;t[i-1]*=2;if(i!=n)
t[i]|=o;}f(i=0;i<m;i++){o=q[i]&1;q[i]/=2;if(i)q[i-1]|=(o<<7);}}k=(r[a+b-1]==0)?a
+b-1:b+a;f(i=0;i<k;i++){printf("%02x ",r[i]);}putchar(10);}
You can test with this:
int main(void){
m("1f 4a 07\n63 a3\n");
m("ff ff ff ff\nff ff ff ff\n");
m("10 20 30 40\n50 60 70\n");
m("01 02 03 04 05 06\n01 01 01\n");
m("00 00 00 00 00 00 00 00 00 00 00 00 01\n00 00 00 00 00 00 00 00 02\n");
return 0;
}
Result:
$ ./long
fd 66 03 a7 04
01 00 00 00 fe ff ff ff
00 05 10 22 34 2d 1c
01 03 06 09 0c 0f 0b 06
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02
OCaml + Batteries, 362 characters
A standard O(n*m) schoolboy multiplication algorithm. Note that in order to meet the challenge requirements, the operations are done on the bytes of strings, which in OCaml are (conveniently, in this case) mutable. Note also that the accumulator s
never overflows 16 bits, since 2(2^8 - 1) + (2^8 - 1)^2 = (2^8 - 1)(2^8 + 1) = 2^16 - 1.
let(@)=List.map
let m a b=Char.(String.(let e s=of_list(((^)"0x"|-to_int|-chr)@nsplit s" ")in
let a,b=e a,e b in let m,n=length a,length b in let c=make(m+n)'\000'in
iteri(fun i d->let s,x=ref 0,code d in iteri(fun j e->let y=code e in
s:=!s+code c.[i+j]+x*y;c.[i+j]<-chr(!s mod
256);s:=!s/256)b;c.[i+n]<-chr!s)a;join" "((code|-Printf.sprintf"%02x")@to_list c)))
For example,
# m "1f 4a 07" "63 a3" ;;
- : string = "fd 66 03 a7 04"
# m "ff ff ff ff" "ff ff ff ff" ;;
- : string = "01 00 00 00 fe ff ff ff"