ลองคูณทีละ 8 บิต


13

คุณจะได้รับเครื่อง 16- บิตและบอกให้ใช้การคูณจำนวนเต็มตามอำเภอใจ การลงทะเบียนของคุณสามารถเก็บตัวเลข 16 บิตได้เท่านั้นและคำสั่งการคูณที่ใหญ่ที่สุดใช้อินพุต 8 บิตสองรายการและสร้างผลลัพธ์แบบ 16 บิต

โปรแกรมของคุณจะต้องป้อนข้อมูลเป็นจำนวนบวกสองค่าโดยพลการและส่งออกผลิตภัณฑ์ของตน หมายเลขอินพุตแต่ละหมายเลขจะถูกเข้ารหัสในบรรทัดของตัวเองเป็นอาร์เรย์ไบต์แบบ endian น้อยโดยที่แต่ละไบต์เป็นตัวเลขฐานสิบหก 2 หลัก ผลลัพธ์จะต้องอยู่ในรูปแบบที่คล้ายกัน บางทีอธิบายได้ดีที่สุดด้วยตัวอย่าง:

อินพุต

1f 4a 07
63 a3

เอาท์พุต

fd 66 03 a7 04

ซึ่งเข้ารหัสการคูณ 477727 * 41827 = 19981887229

คุณสามารถสันนิษฐานได้ว่าไบต์สุดท้าย (สำคัญที่สุด) ของแต่ละหมายเลขอินพุตคือไม่ใช่ศูนย์และอันสุดท้ายของหมายเลขที่คุณส่งออกต้องไม่ใช่ศูนย์ หมายเลขอินพุตทั้งสองจะมีความยาวไม่เกิน 100 ไบต์

รหัสที่เล็กที่สุดชนะ

โปรดจำไว้ว่าตัวคูณที่ใหญ่ที่สุดที่คุณได้รับอนุญาตให้ใช้คือ 1 ไบต์ * 1 ไบต์และไม่มีประเภทจำนวนเต็มใดที่ใหญ่กว่า 2 ไบต์!


นี่เป็นสิ่งสำคัญสำหรับภาษาที่ไม่มีประเภท 8 บิตเริ่มต้นเช่น Haskell
FUZxxl

1
นอกจากนี้แล้ว เราสามารถทำเป็นว่ามีฟังก์ชั่นการเติมขนาดตามอำเภอใจได้หรือไม่? ถ้าไม่เราสามารถเพิ่มอะไรได้บ้าง
Timwi

@Timwi: คุณสามารถทำอะไรก็ได้ที่คุณต้องการได้ครั้งละ 16 บิต เพิ่มกะอะไรก็ตาม การดำเนินการใด ๆ ที่มีขนาดใหญ่กว่าที่คุณต้องการจะสังเคราะห์เอง
Keith Randall

+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ไบต์พิเศษที่ส่วนท้ายของผลลัพธ์ แน่นอนผลยังคงถูกต้องแม้จะมีไบต์พิเศษ
  • พิมพ์พื้นที่พิเศษหลังจากไบต์ฐานสิบหกสุดท้ายในผลลัพธ์

คำอธิบาย

คำอธิบายจะยาวไปหน่อย แต่ฉันคิดว่าคนส่วนใหญ่ที่นี่จะพบว่ามันน่าสนใจ

ก่อนอื่นเลยเมื่อฉันอายุ 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 >> 1และy << 1ตามลำดับ นอกจากนี้เราเพิ่มเฉพาะในกรณีที่y x & 1 == 1นี่แปลเป็นอัลกอริทึมโดยตรงซึ่งฉันจะเขียนที่นี่ใน pseudocode:

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ฉันต้องการ space + star เพราะไบต์สุดท้ายอาจไม่มีที่ว่าง (สามารถใช้ space + ?ด้วย) สิ่งนี้จะทำให้ไบต์ที่ถูกลบ ( x & 255) เข้าไป$&โดยอัตโนมัติ

  • y << 8$y = "00$y"เป็นเพียง

  • เป็นจริงอาร์เรย์ตัวเลขresult @rในตอนท้ายแต่ละองค์ประกอบของ@rประกอบด้วยหนึ่งไบต์ของคำตอบ แต่ครึ่งทางผ่านการคำนวณมันอาจมีมากกว่าหนึ่งไบต์ ฉันจะพิสูจน์ให้คุณด้านล่างว่าแต่ละค่าไม่เกินสองไบต์ (16 บิต) และผลลัพธ์จะเป็นหนึ่งไบต์เสมอในตอนท้าย

ดังนั้นนี่คือรหัส Perl unraveled และแสดงความคิดเห็น:

# 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

ทีนี้เพื่อพิสูจน์ว่ามันจะให้ผลเป็นไบต์เสมอและการคำนวณนั้นจะไม่สร้างค่าที่มากกว่าสองไบต์ ฉันจะพิสูจน์เรื่องนี้โดยอุปนัยเหนือwhileวง:

  • @rการเริ่มต้นว่างเปล่าอย่างชัดเจนไม่มีค่ามากกว่า 0xFF ในนั้น (เพราะไม่มีค่าเลย) สรุปกรณีฐานนี้

  • ตอนนี้ให้ที่@rมีเพียงไบต์เดียวที่จุดเริ่มต้นของwhileการทำซ้ำแต่ละ:

    • การforวนซ้ำอย่างชัดเจนคือ&=ค่าทั้งหมดในอาร์เรย์ผลลัพธ์ที่มี 255 ยกเว้นค่าสุดท้ายดังนั้นเราจะต้องดูค่าสุดท้ายเท่านั้น

    • เรารู้ว่าเราจะลบเพียงหนึ่งไบต์จาก$xและ$y:

      • ดังนั้นเป็นคูณสองค่าไบต์เดี่ยวซึ่งหมายความว่ามันอยู่ในช่วง$e*hex0 — 0xFE01

      • โดยสมมติฐานอุปนัย$r[$i]เป็นหนึ่งไบต์ จึงอยู่ในช่วง$s = $r[$i] += $e*hex0 — 0xFF00

      • ดังนั้นจึง$s >> 8เป็นหนึ่งไบต์เสมอ

    • $yเพิ่มพิเศษ00ในแต่ละรอบของการwhileวนซ้ำ:

      • ดังนั้นในการวนซ้ำทุกครั้งของwhileลูปการวนรอบภายในforจะทำงานซ้ำมากกว่าหนึ่งครั้งในการwhileวนซ้ำก่อนหน้านี้

      • ดังนั้น$r[++$i] += $s >> 8ในการย้ำสุดท้ายของforห่วงเสมอเพิ่ม$s >> 8ไป0และเราจัดตั้งขึ้นแล้วที่$s >> 8อยู่เสมอหนึ่งไบต์

    • ดังนั้นค่าสุดท้ายที่เก็บไว้ใน@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"

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.