Codegolf — Длинное Умножение, 8 Бит За Раз

  • Автор темы Starcounter
  • Обновлено
  • 22, Oct 2024
  • #1

Вам дана 16-битная машина и предложено выполнить умножение целых чисел произвольного размера. Ваши регистры могут хранить только 16-битные числа, а самая большая инструкция умножения принимает два 8-битных входа и генерирует 16-битный результат.

Ваша программа должна принять на вход два положительных числа произвольного размера и вывести их произведение. Каждое входное число кодируется в отдельной строке как массив байтов с прямым порядком байтов, где каждый байт представляет собой двухзначное шестнадцатеричное число. Вывод должен быть отформатирован аналогичным образом. Возможно, лучше всего объяснить это на примере:

вход

 
 fd 66 03 a7 04
 

выход

1f 4a 07 63 a3

который кодирует умножение 477727*41827=19981887229.

Вы можете предположить, что последний (наиболее значимый) байт каждого входного числа не равен нулю, а последняя часть выводимого числа должна быть ненулевой. Оба входных числа будут иметь длину не более 100 байт.

Самый маленький код выигрывает.

Помните, что максимальное умножение, которое вам разрешено использовать, составляет 1 байт * 1 байт, и никакие целочисленные типы не должны превышать 2 байта!

#код-гольф #математика

Starcounter


Рег
27 Feb, 2017

Тем
67

Постов
185

Баллов
560
  • 26, Oct 2024
  • #2

Перл, 137 символов

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 mul 

Предостережения

  • Иногда печатает доп. // sed -i -e 's#//#;#g' dosmult.asm // nasm -f bin dosmult.asm -o dosmult.com // The default, but never harmed anyone. [bits 16] // 8086 code only! If I use 386 instructions or any of that, // I would ruin the challenge. [cpu 8086] // COM files start at 100h. org 100h section .text start: // Create a 400 byte buffer on the stack. // 200 will be reserved for the input, and 200 for the output. mov cx, 200 // Save 200 in BP for later, it will be useful. mov bp, cx // Push 0 200 times making a 400 byte zeroed buffer on the stack xor ax, ax rep push ax mov di, sp // Read the first line of hex digits. .getline_loop: call gethex // Swap DL to AL and store with STOSB xchg dx, ax stosb // Loop if it was a space jz .getline_loop // Get length sub di, sp // Store length in BX xchg di, bx // Save the pointer to input in SI mov si, sp // Load the pointer to output in DI (SI + 200) lea di, [si + bp] // Begin multiplication, grade school byte by byte // Instead of creating a third buffer, we process the data // as the user inputs it. .loop: call gethex // Save flags from gethex so we know when to stop pushfw // Save SI and DI push si push di // Loop counter mov cx, bx .mult_loop: // Load next byte from first input lodsb // Multiply by the input byte mul dl // 16-bit add to [DI] // We actually add to AX, then do STOSW. add ax, [di] stosw // Add the carry bit. adc word [di], 0 // Decrement DI after STOSW incremented it by 2 to iterate // on each byte. dec di // while (--cx) loop .mult_loop .mult_loop_end: // Restore DI and SI pop di pop si // Increment output pointer inc di // Restore flags from gethex popfw // Jump if we got a space. jz .loop .loop_end: // Was there a trailing zero? cmp byte[di + bx], 0 jz .nodec .dec: // If so, decrement the count. dec bx .nodec: // Calculate the length with ugly pointer arithmetic. // I feel there is a better way to do this. add di, bx // ptr += len add sp, bp // sp += 200 (sp = output) sub di, sp // ptr -= output // Store output array in SI. mov si, sp // Print each byte in hex .print_loop: // Load byte lodsb // puthex(AL >> 4) mov bl, al mov cl, 4 shr al, cl call puthex // puthex(AL & 0Fh) xchg ax, bx and al, 0Fh call puthex // putc(' ') mov al, ' ' call putc // Loop for every byte from DI. dec di jnz .print_loop .print_loop_end: // exit, not bothering to clean anything up. :P int 20h // Prints hex digit in AL. // Must be <= 0fh puthex: cmp al, 9 jbe .no_hexa .hexa: add al, 'a' - '9' - 1 .no_hexa: add al, '0' // Fallthrough // Prints AL to stdout. putc: mov ah, 2h mov dl, al int 21h ret // Reads a hex byte. // Returns in DL. // Sets whether it ended in a space in the Z flag. gethex: mov dl, 0 .start: // Read char into AL mov ah, 1h int 21h // Subtract ASCII '0' sub al, '0' // If we hit a space or newline, they will trigger the 'below' // condition jb .ret // Was AL '0'-'9'? If not, correct it to 'a'-'f'. cmp al, 10 jb .no_hexa .hexa: // Correct AL sub al, 'a' - '9' - 1 .no_hexa: // DL = (DL << 4) + AL // 8086 doesn't have shift by immediate. mov cl, 4 shl dl, cl // Add to BL add dl, al // Loop jmp .start .ret: // Check if AL before the subtraction was a space. cmp al, ' ' - '0' ret byte at the end of the result. Of course the result is still correct even with that extra byte.
  • Печатает дополнительный пробел после последнего шестнадцатеричного байта результата.

Объяснение

Объяснение будет немного длинным, но я думаю, что большинству людей оно покажется интересным.

Прежде всего, когда мне было 10 лет, меня научили следующему маленькому трюку. С его помощью вы можете умножить любые два положительных числа. Я опишу это на примере 13×47. Вы начинаете с написания первого числа, 13, и разделяющий его на 2 (каждый раз округляя вниз), пока не достигнете 1:

00000000: b9 c8 00 89 cd 31 c0 f3 50 89 e7 e8 5d 00 92 aa .....1..P...]... 00000010: 74 f9 29 e7 87 fb 89 e6 8d 3a e8 4e 00 9c 56 57 t.)......:.N..VW 00000020: 89 d9 ac f6 e2 03 05 ab 83 15 00 4f e2 f4 5f 5e ...........O.._^ 00000030: 47 9d 74 e6 80 39 00 74 01 4b 01 df 01 e5 29 ef G.t..9.t.K....). 00000040: 89 ee ac 88 c3 b1 04 d2 e8 e8 10 00 93 24 0f e8 .............$.. 00000050: 0a 00 b0 20 e8 0d 00 4f 75 e8 cd 20 3c 09 76 02 ... ...Ou.. <.v. 00000060: 04 27 04 30 b4 02 88 c2 cd 21 c3 b2 00 b4 01 cd .'.0.....!...... 00000070: 21 2c 30 72 0e 3c 0a 72 02 2c 27 b1 04 d2 e2 00 !,0r.<.r.,'..... 00000080: c2 eb ea 3c f0 c3 ...<..

Теперь рядом с числом 13 напишите другое число, 47, и сохраните его. умножение это в 2 такое же количество раз:

x=>y=>x.map((t,i)=>y.map(u=>(f=i=>(c=s[i]>>8)&&f(i++,s[i]=c+~~s[i],s[i-1]%=256))(i,s[i]=~~s[i++]+`0x${t}`*`0x${u}`)),s=[])&&s.map(t=>(t<16?0:'')+t.toString(16))

Теперь вычеркните все строки, где слева стоит цифра даже. В данном случае это только цифра 6. (Я не могу зачеркнуть ее в коде, поэтому просто удалю ее.) Наконец, вы добавляете все оставшиеся цифры справа:

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

И это правильный ответ. 13 × 47 = 611.

Теперь, поскольку вы все компьютерные фанаты, вы поняли, что то, что мы на самом деле делаем в левом и правом столбцах, 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; } and 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("x ",r[i]);}putchar(10);} , соответственно. Кроме того, мы добавляем # 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" only if 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"x")@to_list c))) . Это преобразуется непосредственно в алгоритм, который я напишу здесь в псевдокоде:

s

Мы можем переписать for to use a multiplication, and then we can easily change this so that it works on a byte-by-byte basis instead of bit-by-bit:

@r

Это все еще содержит умножение на $s >> 8 , which is arbitrary-size, so we need to change that into a loop too. We’ll do that in Perl.

Теперь переведем все на Perl:

  • 0 and $s >> 8 являются входными данными в шестнадцатеричном формате, поэтому они имеют наименее значащий байт первый.

  • Таким образом, вместо for I do $r[++$i] += $s >> 8 . Мне нужен пробел+звезда, потому что в последнем байте может не быть пробела (можно использовать пробел+ while too). This automatically puts the removed byte ( for ) в while .

  • while is simply 00 .

  • $y is actually a numerical array, $s >> 8 . В конце каждый элемент 0 — 0xFF00 contains one byte of the answer, but halfway through the calculation it may contain more than one byte. I’ll prove to you below that each value is never more than два байтов (16 бит) и что результат всегда равен одному байту в конце.

Итак, вот код Perl, раскрытый и прокомментированный:

$s = $r[$i] += $e*hex

Теперь для доказательства того, что это всегда выводит байтыи что расчет никогда не генерирует значения, превышающие два байты. Я докажу это индукцией по $r[$i] loop:

  • Пустой 0 — 0xFE01 at the beginning clearly has no values greater than 0xFF in it (because it has no values in it at all). This concludes the base case.

  • Теперь, учитывая, что $e*hex contains only single bytes at the beginning of each $y итерация:

    • $x loop explicitly &= s все значения в массиве результатов с 255 кроме последнего, поэтому нам нужно посмотреть только на последний.

    • Мы знаем, что всегда удаляем только один байт из for and while :

      • Поэтому, @r is a multiplication of two single-byte values, which means it’s in the range @r .

      • По индуктивному предположению, while is one byte; therefore, # 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 'x ' x @r, @r находится в диапазоне @r .

      • Поэтому, @r is always one byte.

    • result grows an extra $y = "00$y" в каждой итерации y << 8 loop:

      • Таким образом, на каждой итерации $& loop, the inner x & 255 цикл выполняется на одну итерацию больше, чем в предыдущем ? iteration.

      • Таким образом, $x =~ s/.. *//s in the last iteration of the x >> 8 цикл всегда добавляет $y to $x , и мы уже установили, что y is always one byte.

    • Таким образом, последнее значение, сохраненное в input x, y result = 0 while x > 0: result = result + (y * (x & 255)) x = x >> 8 y = y << 8 print result at the end of the if цикл также представляет собой один байт.

На этом замечательная и захватывающая задача завершается. Большое спасибо за публикацию!

 

Milen0601


Рег
30 Oct, 2011

Тем
79

Постов
183

Баллов
608
  • 26, Oct 2024
  • #3

OCaml + Батарейки, 362 символа

Стандартный алгоритм умножения школьников O(n*m). Обратите внимание, что для удовлетворения требований запроса операции выполняются над байтами строк, которые в OCaml (в данном случае удобно) изменяемы. Также обратите внимание, что аккумулятор input x, y result = 0 while x > 0: if x & 1 == 1: result = result + y x = x >> 1 y = y << 1 print result never overflows 16 bits, since 2(2^8 - 1) + (2^8 - 1)^2 = (2^8 - 1)(2^8 + 1) = 2^16 - 1.

x & 1 == 1

Например,

y ||answer||

Решение C

Это решение не выполняет проверку ввода. Это также лишь слегка проверено. Скорость на самом деле не имела значения. Память Маллока, и он не особо разбирается в том, сколько она захватывает. Гарантированно будет достаточно, и даже больше, чем необходимо.

m() принимает строку и ожидает две новые строки в строке, по одной после каждого числа. Ожидаются только числа, символы нижнего регистра, пробелы и символы новой строки. Ожидает, что шестнадцатеричные цифры всегда будут парой.

Никакая операция умножения никогда не используется (сознательно). Сдвиг выполняется для 8-битных переменных. Выполняется одно 16-битное сложение. Нет 32-битных типов данных.

Уменьшился вручную, и то незначительно. редактировать: больше запутывания, меньше символов: D Компилируется с предупреждениями с помощью gcc.

Персонажей: 675

y << 1

Вы можете протестировать это:

x >> 1

Результат:

13 47 3 188 1 376 ---- 611 ||answer||

JavaScript (Node.js), 160 байт

13 47 6 94 3 188 1 376

Попробуйте онлайн!

Хотя гораздо более новый язык, чем в то время

 

Alcohol


Рег
09 May, 2020

Тем
82

Постов
209

Баллов
659
  • 26, Oct 2024
  • #4

8086 DOS-файл .COM, 134 байта

Теперь это работает!

На каком языке лучше это сделать, чем на 16-битной ассемблере?

13 6 3 1

Моя первая и, вероятно, последняя 16-битная программа для 8086. ???

Сборка (NASM с комментариями C из-за подсветки синтаксиса):

00

Естественно, поскольку я использую 16-битный набор команд, я выполняю только 8-битные и 16-битные арифметические действия (и не использую 16-битный набор команд). ($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'x 'x@r,@r ).

Превосходит ответ Perl на 3 байта. Конечно, это двоичный машинный код, а это исходный код, так что, думаю, это не так уж и радует. Однако в 8086 отсутствуют многие возможности Perl (около 1/3 — это просто ввод-вывод и шестнадцатеричное преобразование???).

Я уверен, что здесь возможны несколько оптимизаций. Большая часть моих знаний получена из написания 32-битного и 64-битного кода, поэтому я не полностью разбираюсь в 16-битных трюках.

Ввод осуществляется со стандартного ввода, в шестнадцатеричном формате, разделенном пробелами, каждый из которых заканчивается новой строкой. Он не буферизован по строкам, поэтому опечатки не допускаются! :П

Вывод выводится на стандартный вывод.

 

Seller1


Рег
08 Apr, 2013

Тем
65

Постов
225

Баллов
600
Тем
403,760
Комментарии
400,028
Опыт
2,418,908

Интересно