Nhân dài, 8 bit cùng một lúc


13

Bạn được cấp một máy 16 bit và được yêu cầu thực hiện phép nhân các số nguyên có kích thước tùy ý. Các thanh ghi của bạn chỉ có thể chứa các số 16 bit và lệnh nhân lớn nhất sẽ có hai đầu vào 8 bit và tạo ra kết quả 16 bit.

Chương trình của bạn phải lấy đầu vào là hai số dương có kích thước tùy ý và xuất sản phẩm của chúng. Mỗi số đầu vào được mã hóa trên một dòng riêng dưới dạng một mảng byte cuối nhỏ trong đó mỗi byte là một số hex 2 chữ số. Đầu ra phải được định dạng tương tự. Có lẽ giải thích tốt nhất với một ví dụ:

đầu vào

1f 4a 07
63 a3

đầu ra

fd 66 03 a7 04

mã hóa phép nhân 477727 * 41827 = 19981887229.

Bạn có thể giả sử rằng byte cuối cùng (có ý nghĩa nhất) của mỗi số đầu vào là khác không và đoạn cuối cùng của số bạn xuất ra phải là khác không. Cả hai số đầu vào sẽ dài tối đa 100 byte.

Mã nhỏ nhất thắng.

Hãy nhớ rằng, bội số lớn nhất bạn được phép sử dụng là 1 byte * 1 byte và không có loại số nguyên nào lớn hơn 2 byte!


Điều này rất quan trọng đối với các ngôn ngữ, không có loại 8 bit mặc định, như Haskell.
FUZxxl

1
Còn về sự bổ sung? Chúng ta có thể giả vờ có một chức năng bổ sung kích thước tùy ý làm sẵn không? Nếu không, chúng ta có thể thêm gì?
Timwi

@Timwi: Bạn có thể làm bất cứ điều gì bạn muốn 16 bit mỗi lần. Thêm, thay đổi, bất cứ điều gì. Bất kỳ hoạt động lớn hơn, bạn cần phải tự tổng hợp.
Keith Randall

+1 cho thứ tự byte chính xác
12Me21

Câu trả lời:


13

Perl, 137 ký tự

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

Hãy cẩn thận

  • Đôi khi in thêm một 00byte vào cuối kết quả. Tất nhiên kết quả vẫn đúng ngay cả với byte thêm đó.
  • In một khoảng trắng thừa sau byte hex cuối cùng trong kết quả.

Giải trình

Lời giải thích sẽ hơi dài, nhưng tôi nghĩ hầu hết mọi người ở đây sẽ thấy nó thú vị.

Trước hết, khi tôi 10 tuổi, tôi được dạy những mẹo nhỏ sau đây. Bạn có thể nhân hai số dương bất kỳ với số này. Tôi sẽ mô tả điều này bằng ví dụ 13 × 47. Bạn bắt đầu bằng cách viết số đầu tiên, 13 và chia cho 2 (làm tròn xuống mỗi lần) cho đến khi bạn đạt 1:

13
 6
 3
 1

Bây giờ, bên cạnh 13 bạn viết số khác, 47 và tiếp tục nhân nó với 2 số lần giống nhau:

13     47
 6     94
 3    188
 1    376

Bây giờ bạn gạch bỏ tất cả các dòng trong đó số bên trái là số chẵn . Trong trường hợp này, đây chỉ là số 6. (Tôi không thể thực hiện nhập mã, vì vậy tôi sẽ xóa nó đi.) Cuối cùng, bạn thêm tất cả các số còn lại ở bên phải:

13     47
 3    188
 1    376
     ----
      611

Và đây là câu trả lời đúng. 13 × 47 = 611.

Bây giờ, vì bạn là tất cả các chuyên viên máy tính, bạn sẽ nhận ra rằng những gì chúng ta thực sự đang làm ở cột bên trái và bên phải là x >> 1y << 1tương ứng. Hơn nữa, chúng tôi thêm ychỉ khi x & 1 == 1. Điều này chuyển trực tiếp thành một thuật toán, mà tôi sẽ viết ở đây bằng mã giả:

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

Chúng ta có thể viết lại ifđể sử dụng phép nhân, và sau đó chúng ta có thể dễ dàng thay đổi điều này để nó hoạt động trên cơ sở từng byte thay vì từng bit một:

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

Điều này vẫn chứa một phép nhân với ykích thước tùy ý, vì vậy chúng ta cũng cần thay đổi nó thành một vòng lặp. Chúng tôi sẽ làm điều đó trong Perl.

Bây giờ dịch mọi thứ sang Perl:

  • $x$ylà các đầu vào ở định dạng hex, vì vậy chúng có byte đầu tiên ít quan trọng nhất .

  • Như vậy, thay vì x >> 8tôi làm $x =~ s/.. *//s. Tôi cần khoảng trắng + sao vì byte cuối cùng có thể không có khoảng trắng trên đó (cũng có thể sử dụng khoảng trắng + ?). Điều này tự động đặt byte ( x & 255) bị loại bỏ vào $&.

  • y << 8chỉ đơn giản là $y = "00$y".

  • Đây resultthực sự là một mảng số , @r. Cuối cùng, mỗi phần tử @rchứa một byte của câu trả lời, nhưng khi tính toán được một nửa, nó có thể chứa nhiều hơn một byte. Tôi sẽ chứng minh cho bạn dưới đây rằng mỗi giá trị không bao giờ nhiều hơn hai byte (16 bit) và kết quả luôn luôn là một byte ở cuối.

Vì vậy, đây là mã Perl làm sáng tỏ và nhận xét:

# 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

Bây giờ để chứng minh rằng điều này luôn xuất ra byte và phép tính không bao giờ tạo ra các giá trị lớn hơn hai byte. Tôi sẽ chứng minh điều này bằng cách cảm ứng qua whilevòng lặp:

  • Khoảng trống @rở đầu rõ ràng không có giá trị nào lớn hơn 0xFF trong đó (vì nó không có giá trị nào trong đó). Điều này kết luận trường hợp cơ sở.

  • Bây giờ, được cho là @rchỉ chứa các byte đơn ở đầu mỗi whilelần lặp:

    • Các forvòng lặp một cách rõ ràng &=là tất cả các giá trị trong mảng kết quả với 255 ngoại trừ người cuối cùng , vì vậy chúng ta chỉ cần nhìn vào đó người cuối cùng.

    • Chúng tôi biết rằng chúng tôi luôn xóa chỉ một byte từ $x$y:

      • Do đó, $e*hexlà một phép nhân của hai giá trị byte đơn, có nghĩa là nó nằm trong phạm vi 0 — 0xFE01.

      • Theo giả thuyết quy nạp, $r[$i]là một byte; do đó, $s = $r[$i] += $e*hexlà trong phạm vi 0 — 0xFF00.

      • Do đó, $s >> 8luôn luôn là một byte.

    • $yphát triển thêm 00trong mỗi lần lặp của whilevòng lặp:

      • Do đó, trong mỗi lần lặp của whilevòng lặp, vòng lặp bên trong sẽ forchạy thêm một lần lặp so với whilelần lặp trước .

      • Do đó, $r[++$i] += $s >> 8trong lần lặp cuối cùng của forvòng lặp luôn luôn thêm $s >> 8vào 0và chúng tôi đã thiết lập $s >> 8luôn luôn là một byte.

    • Do đó, giá trị cuối cùng được lưu trữ ở @rcuối forvòng lặp cũng là một byte đơn.

Điều này kết thúc một thử thách tuyệt vời và thú vị. Cảm ơn rất nhiều vì đã đăng nó!


4

Giải pháp C

Giải pháp này không có xác nhận đầu vào. Nó cũng chỉ được thử nghiệm nhẹ. Tốc độ không thực sự là một cân nhắc. Bộ nhớ của Malloc, và không đặc biệt thông minh về việc nó lấy bao nhiêu. Đảm bảo là đủ, và hơn mức cần thiết.

m () chấp nhận một chuỗi, mong đợi hai dòng mới trong chuỗi, một sau mỗi số. Chỉ mong đợi số, ký tự chữ thường, dấu cách và dòng mới. Mong các chữ số hex luôn luôn là một cặp.

Không có hoạt động nhân được bao giờ được sử dụng (cố ý). Sự thay đổi được thực hiện trên các biến 8 bit. Một bổ sung 16 bit được thực hiện. Không có loại dữ liệu 32 bit.

Nhún bằng tay, và chỉ nhẹ nhàng. chỉnh sửa: obfuscation nhiều hơn, ít ký tự hơn: D Biên dịch với các cảnh báo với gcc.

Nhân vật: 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);}

Bạn có thể kiểm tra với điều này:

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

Kết quả:

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

Pin OCaml +, 362 ký tự

Một thuật toán nhân học sinh tiêu chuẩn O (n * m). Lưu ý rằng để đáp ứng các yêu cầu thách thức, các thao tác được thực hiện trên các byte của chuỗi, trong OCaml là (thuận tiện, trong trường hợp này) có thể thay đổi. Cũng lưu ý rằng bộ tích lũy skhông bao giờ vượt quá 16 bit, vì 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)))

Ví dụ,

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.