長い乗算、一度に8ビット


13

16ビットのマシンが与えられ、任意のサイズの整数の乗算を実装するように指示されます。レジスタは16ビットの数値のみを保持でき、最大の乗算命令は2つの8ビット入力を取り、16ビットの結果を生成します。

プログラムは、入力として2つの任意のサイズの正数を取り、それらの積を出力する必要があります。各入力番号は、各バイトが2桁の16進数であるリトルエンディアンのバイト配列として、独自の行でエンコードされます。出力は同様にフォーマットする必要があります。おそらく例を挙げて説明するのが最も良いでしょう:

入力

1f 4a 07
63 a3

出力

fd 66 03 a7 04

乗算477727 * 41827 = 19981887229をエンコードします。

各入力番号の最​​後の(最上位)バイトがゼロ以外であり、出力する番号の最後のチャンクがゼロ以外であると仮定できます。両方の入力番号の長さは最大100バイトです。

最小のコードが勝ちます。

使用できる最大の乗算は1バイト* 1バイトであり、2バイトを超える整数型は使用できません。


これは、Haskellなどのデフォルトの8ビットタイプを持たない言語にとって重要です。
FUZxxl

1
追加についてはどうですか?既製の任意サイズの追加関数を装うことができますか?ない場合は、何をすることができ、我々は追加しますか?
ティムウィ

@Timwi:一度に16ビットにすることができます。追加、シフト、何でも。自分自身を合成するために必要なより大きな操作。
キースランドール

正しいバイト順序のために+1
12Me21

回答:


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結果の最後に余分なバイトを出力する場合があります。もちろん、その余分なバイトがあっても結果は正しいままです。
  • 結果の最後の16進バイトの後に余分なスペースを出力します。

説明

説明は少し長くなりますが、ここのほとんどの人は面白いと思うと思います。

まず、私が10歳のときに、次の小さなトリックを教えられました。これで任意の2つの正数を乗算できます。これについては、13×47の例を使用して説明します。最初の数字である13を書き、1に達するまで2で割る(切り捨てる)ことから始めます。

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のみを追加し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そして、$y16進形式の入力ですので、彼らは持っている最も重要なバイトを最初に

  • したがって、x >> 8私がする代わりに$x =~ s/.. *//s。最後のバイトにスペースがない場合があるため、スペース+スターが必要です(スペース+ ?も使用できます)。これは削除されたバイト(x & 255)を自動的に入れ$&ます。

  • y << 8は単純$y = "00$y"です。

  • result実際の数値配列です@r。最後に、の各要素に@rは1バイトの回答が含まれますが、計算の途中で複数のバイトが含まれることがあります。各値が2バイト(16ビット)を超えないこと、および結果が常に最後の1バイトであることを以下で証明します。

したがって、ここに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を出力し、計算が2バイトを超える値を生成しないことの証明のためです。whileループ上の帰納法でこれを証明します。

  • 最初の空@rには明らかに0xFFより大きい値がありません(値がまったくないため)。これで基本ケースは終わりです。

  • ここで、@rwhile反復の先頭に単一バイトのみが含まれている場合:

    • forループは明示的に&=255で結果配列内のすべての値をsの最後を除いて、我々はそれだけで、最後の1を見てする必要があるので、。

    • $xand から常に1バイトのみを削除することを知っています$y

      • したがって、$e*hexは2つのシングルバイト値の乗算です0 — 0xFE01。つまり、範囲内にあります。

      • 帰納的仮説により、 $r[$i]は1バイトです。したがって、$s = $r[$i] += $e*hexはの範囲にあります0 — 0xFF00

      • したがって、 $s >> 8常に1バイトです。

    • $y 余分に成長する 00whileループの各反復でにします。

      • したがって、 whileループの内側のforループは、前のwhile反復で行ったよりも1回多く反復します。

      • したがって、ループの$r[++$i] += $s >> 8最後の反復ではfor常にに加算さ$s >> 80$s >> 8常に1バイトであることがすでに確立されています。

    • したがって、に格納された最後の値@rの最後にforループは単一バイトです。

これで、すばらしい刺激的な挑戦が終わりました。投稿してくれてありがとう!


4

Cソリューション

このソリューションは入力検証を行いません。また、わずかにテストされています。速度は実際には考慮されませんでした。Mallocのメモリ、およびそれがどれだけ取得するかについては特に賢いわけではありません。十分であり、必要以上に保証されています。

m()は文字列を受け入れ、各番号の後に文字列内の2つの改行が必要です。数字、小文字、スペース、および改行のみを想定しています。16進数が常にペアになることを期待します。

乗算演算は(意図的に)使用されません。シフトは8ビット変数で実行されます。1つの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、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"

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.