Довго розмножувати, 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

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в кінці результату друкується додатковий байт. Звичайно, результат все-таки правильний, навіть якщо цей додатковий байт.
  • Друкує додатковий пробіл після останнього шістнадцяткового байта в результаті.

Пояснення

Пояснення буде трохи довгим, але я думаю, що більшості людей тут буде цікаво.

Перш за все, коли мені було 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. Це перекладається безпосередньо в алгоритм, про який я напишу тут у псевдокоді:

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

Тепер для підтвердження того, що це завжди видає байти і що обчислення ніколи не генерує значення, більші за два байти. Я докажу це індукцією через whileцикл:

  • Порожній @rна початку явно не має в ньому значень, більших за 0xFF (тому що в ньому взагалі немає значень). Це завершує базовий випадок.

  • Тепер, з огляду на те, що @rмістить лише один байт на початку кожної whileітерації:

    • forЦикл явно &=S все значення в результуючому масиві з 255 , за винятком останнього , так що ми тільки повинні дивитися на цьому останньому.

    • Ми знаємо , що ми завжди видалити тільки один байт з $xі $y:

      • Отже, $e*hexце множення двох однобайтових значень, а значить, воно знаходиться в діапазоні 0 — 0xFE01.

      • За припущенням індукції, $r[$i]становить один байт; отже, $s = $r[$i] += $e*hexзнаходиться в діапазоні 0 — 0xFF00.

      • Тому $s >> 8завжди є один байт.

    • $yзростає додатково 00в кожній ітерації whileциклу:

      • Тому в кожній ітерації whileциклу внутрішня forпетля працює на ще одну ітерацію, ніж це було зроблено в попередній whileітерації.

      • Таким чином, $r[++$i] += $s >> 8в останній ітерації forциклу завжди додає $s >> 8до 0, і ми вже встановили , що $s >> 8завжди один байт.

    • Тому останнє значення, збережене в @rкінці forциклу, також є єдиним байтом.

Це завершує чудовий та захоплюючий виклик. Дякую велике за публікацію!


4

C Рішення

Це рішення не робить перевірки вводу. Це також лише легкий тест. Швидкість насправді не враховувала. Пам'ять Маллока і не особливо розумна щодо того, наскільки вона захоплює. Гарантовано вистачить, і більше, ніж потрібно.

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"

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.