长乘法,一次8位


13

您将得到一台16位计算机,并被告知实现任意大小的整数的乘法。您的寄存器只能容纳16位数字,最大的乘法指令需要两个8位输入并生成16位结果。

您的程序必须将两个任意大小的正数作为输入并输出其乘积。每个输入数字在其自己的行上编码为小尾数字节数组,其中每个字节为两位数的十六进制数字。输出必须采用类似的格式。也许最好用一个例子来解释:

输入

1f 4a 07
63 a3

输出

fd 66 03 a7 04

编码乘法477727 * 41827 = 19981887229。

您可以假定每个输入数字的最后一个(最高有效)字节为非零,并且您输出的数字的最后一块必须为非零。两个输入数字的最大长度为100个字节。

最小的代码获胜。

请记住,允许使用的最大乘法是1字节* 1字节,并且没有大于2字节的整数类型!


这对于没有默认8位类型的语言(例如Haskell)至关重要。
FUZxxl 2011年

1
那加法呢?我们可以假装具有现成的任意大小的加法函数吗?如果没有,我们可以添加什么?
Timwi

@Timwi:您可以一次执行16位操作。加,换,随便什么。您需要综合进行任何较大的操作。
基思·兰德尔

+1表示正确的字节顺序
12

Answers:


13

Perl,137个字符

($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

注意事项

  • 有时00在结果末尾打印一个额外的字节。当然,即使有该额外的字节,结果仍然是正确的。
  • 在结果的最后一个十六进制字节之后打印一个额外的空格。

说明

解释会有点长,但是我想这里的大多数人都会觉得很有趣。

首先,当我10岁时,我被教导以下小技巧。您可以将任意两个正数相乘。我将以13×47为例进行描述。首先写入第一个数字13,然后其除以2(每次向下舍入),直到达到1:

13
 6
 3
 1

现在,在13旁边,写下另一个数字47,并将其乘以 2相同的次数:

13     47
 6     94
 3    188
 1    376

现在,您将左边的数字为偶数的所有线都划掉了。在这种情况下,这仅是6。(我无法在代码中删除线,因此我将其删除。)最后,在右侧添加所有剩余的数字:

13     47
 3    188
 1    376
     ----
      611

这是正确的答案。13×47 = 611。

现在,由于您都是计算机极客,因此您已经意识到我们实际上在左右两列中分别执行的是x >> 1y << 1。此外,我们添加y唯一的if x & 1 == 1。这直接转换为算法,我将在此处以伪代码编写该算法:

input x, y
result = 0
while x > 0:
    if x & 1 == 1:
        result = result + y
    x = x >> 1
    y = y << 1
print result

我们可以重写if以使用乘法,然后我们可以轻松地更改它,以使其在逐字节而不是逐位的基础上工作:

input x, y
result = 0
while x > 0:
    result = result + (y * (x & 255))
    x = x >> 8
    y = y << 8
print result

它仍然包含与的乘法y,后者是任意大小的,因此我们也需要将其更改为循环。我们将在Perl中做到这一点。

现在将所有内容翻译为Perl:

  • $x并且$y是十六进制格式的输入,因此它们的最低有效字节在前

  • 因此,代替x >> 8我做$x =~ s/.. *//s。我需要空格+星号,因为最后一个字节上可能没有空格(也可以使用空格+ ?)。这会自动将删除的字节(x & 255)放入$&

  • y << 8很简单$y = "00$y"

  • result实际上是一个数值数组,@r。最后,的每个元素都@r包含一个字节的答案,但是在计算的一半,它可能包含一个以上的字节。下面我将向您证明,每个值永远都不会超过两个字节(16位),并且结果始终是一个字节。

因此,这里是Perl代码的摘要和注释:

# 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

现在为了证明它始终输出bytes,并且计算永远不会产生大于两个字节的值。我将通过while循环归纳来证明这一点:

  • @r开头的空值显然没有大于0xFF的值(因为它根本没有值)。到此结束基本情况。

  • 现在,假设@r在每次while迭代的开始仅包含单个字节:

    • for循环明确&=S中的结果阵列255中的所有值除了最后一个,所以我们只需要看看这最后一个。

    • 我们知道我们总是只从$x和中删除一个字节$y

      • 因此,$e*hex是两个单字节值的乘积,这意味着它在range内0 — 0xFE01

      • 由归纳假设,$r[$i]是一个字节;因此,$s = $r[$i] += $e*hex在范围内0 — 0xFF00

      • 因此,$s >> 8总是一个字节。

    • $y00在循环的每次迭代中增加一个额外的值while

      • 因此,在循环的每次迭代中while,内部for循环的运行次数都比前while一次迭代多。

      • 因此,循环$r[++$i] += $s >> 8的最后一次迭代中的for总是添加$s >> 80,并且我们已经建立了$s >> 8始终为1个字节的字节。

    • 因此,循环@r末尾存储的最后一个值for也是一个字节。

这结束了一个奇妙而令人兴奋的挑战。非常感谢您发布!


4

C解决方案

此解决方案不进行输入验证。它也只是经过轻微测试。速度并不是真正的考虑因素。Malloc的内存,并不是特别聪明。保证足够,并且超过必要。

m()接受一个字符串,期望字符串中有两个换行符,每个数字后都有一个。只应包含数字,小写字符,空格和换行符。期望十六进制数字始终是一对。

(不知道)从未使用过乘法运算。移位是对8位变量执行的。进行一次16位加法。没有32位数据类型。

用手收缩,且仅适度收缩。 编辑:更多的混淆,更少的字符:D编译带有gcc的警告。

字数: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);}

您可以对此进行测试:

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;
}

结果:

$ ./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 

3

OCaml +电池,362个字符

一种标准的O(n * m)小学生乘法算法。请注意,为了满足挑战要求,对字符串的字节进行了操作,在OCaml中,字符串是可变的(在这种情况下很方便)。还要注意,累加器s永远不会溢出16位,因为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)))

例如,

# 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"

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.