Moltiplicare a lungo, 8 bit alla volta


13

Ti viene data una macchina a 16 bit e ti viene detto di implementare la moltiplicazione di numeri interi di dimensioni arbitrarie. I tuoi registri possono contenere solo numeri a 16 bit e la più grande istruzione di moltiplicazione accetta due ingressi a 8 bit e genera un risultato a 16 bit.

Il programma deve prendere come input due numeri positivi di dimensioni arbitrarie e produrre il loro prodotto. Ogni numero di input è codificato su una propria riga come un array di byte little-endian in cui ogni byte è un numero esadecimale a 2 cifre. L'output deve essere formattato in modo simile. Forse meglio spiegato con un esempio:

ingresso

1f 4a 07
63 a3

produzione

fd 66 03 a7 04

che codifica per la moltiplicazione 477727 * 41827 = 19981887229.

Si può presumere che l'ultimo (più significativo) byte di ciascun numero di input sia diverso da zero e che l'ultimo blocco del numero emesso debba essere diverso da zero. Entrambi i numeri di input avranno una lunghezza massima di 100 byte.

Vince il codice più piccolo.

Ricorda, il moltiplicatore più grande che ti è consentito utilizzare è 1 byte * 1 byte e nessun tipo intero più grande di 2 byte!


Questo è fondamentale per le lingue che non hanno un tipo predefinito di 8 bit, come Haskell.
FUZxxl,

1
Che dire dell'aggiunta? Possiamo fingere di avere una funzione di aggiunta di dimensioni arbitrarie già pronta? Se no, che cosa possiamo noi aggiungere?
Timwi,

@Timwi: puoi fare qualsiasi cosa tu voglia 16 bit alla volta. Aggiunge, cambia, qualunque cosa. Qualsiasi operazione più grande di cui hai bisogno per sintetizzarti.
Keith Randall,

+1 per l'ordine byte corretto
Mee

Risposte:


13

Perl, 137 caratteri

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

Avvertenze

  • A volte stampa un 00byte extra alla fine del risultato. Ovviamente il risultato è ancora corretto anche con quel byte extra.
  • Stampa uno spazio extra dopo l'ultimo byte esadecimale nel risultato.

Spiegazione

La spiegazione sarà un po 'lunga, ma penso che molte persone qui la troveranno interessante.

Prima di tutto, quando avevo 10 anni, mi è stato insegnato il seguente piccolo trucco. Puoi moltiplicare due numeri positivi per questo. Lo descriverò usando l'esempio di 13 × 47. Inizi scrivendo il primo numero, 13, e dividendolo per 2 (arrotondando ogni volta verso il basso) fino a raggiungere 1:

13
 6
 3
 1

Ora, accanto al 13 scrivi l'altro numero, 47 e continui a moltiplicarlo per 2 lo stesso numero di volte:

13     47
 6     94
 3    188
 1    376

Ora cancelli tutte le linee in cui il numero a sinistra è pari . In questo caso, questo è solo il 6. (Non riesco a fare lo strike-through nel codice, quindi lo rimuoverò.) Infine, aggiungi tutti i numeri rimanenti sulla destra:

13     47
 3    188
 1    376
     ----
      611

E questa è la risposta giusta. 13 × 47 = 611.

Ora, dato che siete tutti fanatici dei computer, avrete capito che ciò che stiamo effettivamente facendo nelle colonne sinistra e destra è x >> 1e y << 1, rispettivamente. Inoltre, aggiungiamo il ysolo se x & 1 == 1. Questo si traduce direttamente in un algoritmo, che scriverò qui in pseudocodice:

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

Possiamo riscrivere il ifper usare una moltiplicazione, e quindi possiamo facilmente cambiarlo in modo che funzioni su una base byte per byte anziché bit per bit:

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

Questo contiene ancora una moltiplicazione con ydimensioni arbitrarie, quindi dobbiamo trasformarlo anche in un ciclo. Lo faremo in Perl.

Ora traduci tutto in Perl:

  • $xe $ysono gli input in formato esadecimale, quindi hanno prima il byte meno significativo .

  • Quindi, invece di x >> 8me $x =~ s/.. *//s. Ho bisogno di spazio + stella perché l'ultimo byte potrebbe non avere uno spazio su di esso (potrebbe usare anche spazio + ?). Ciò inserisce automaticamente il byte rimosso ( x & 255) in $&.

  • y << 8è semplicemente $y = "00$y".

  • La resultrealtà è una matrice numerica, @r. Alla fine, ogni elemento di @rcontiene un byte della risposta, ma a metà del calcolo può contenere più di un byte. Ti dimostrerò di seguito che ogni valore non è mai più di due byte (16 bit) e che il risultato è sempre un byte alla fine.

Quindi ecco il codice Perl svelato e commentato:

# 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

Ora per la prova che ciò genera sempre byte e che il calcolo non genera mai valori maggiori di due byte. Lo dimostrerò per induzione over the whileloop:

  • Il vuoto @rall'inizio non contiene chiaramente valori superiori a 0xFF (perché non contiene alcun valore). Questo conclude il caso base.

  • Ora, dato che @rcontiene solo singoli byte all'inizio di ogni whileiterazione:

    • Il forciclo &=s esplicitamente tutti i valori nella matrice dei risultati con 255 tranne l'ultimo , quindi dobbiamo solo guardare l'ultimo.

    • Sappiamo che rimuoviamo sempre solo un byte da $xe $y:

      • Pertanto, $e*hexè una moltiplicazione di due valori a byte singolo, il che significa che è nell'intervallo 0 — 0xFE01.

      • Per l'ipotesi induttiva, $r[$i]è un byte; pertanto, $s = $r[$i] += $e*hexè nella gamma 0 — 0xFF00.

      • Pertanto, $s >> 8è sempre un byte.

    • $ycresce un extra 00in ogni iterazione del whileciclo:

      • Pertanto, in ogni iterazione del whileciclo, il forciclo interno viene eseguito per un'iterazione in più rispetto a quella whiledell'iterazione precedente .

      • Pertanto, $r[++$i] += $s >> 8nell'ultima iterazione del forciclo si aggiunge sempre $s >> 8a 0, e abbiamo già stabilito che $s >> 8è sempre un byte.

    • Pertanto, l'ultimo valore memorizzato @ralla fine del forloop è anche un singolo byte.

Questo conclude una sfida meravigliosa ed eccitante. Grazie mille per averlo pubblicato!


4

Soluzione C.

Questa soluzione non convalida l'input. È anche solo leggermente testato. La velocità non era davvero una considerazione. La memoria di Malloc, e non è particolarmente intelligente su quanto afferra. Garantito per essere sufficiente e più del necessario.

m () accetta una stringa, si aspetta due nuove righe nella stringa, una dopo ogni numero. Si aspetta solo numeri, caratteri minuscoli, spazi e nuove righe. Si aspetta che le cifre esadecimali siano sempre una coppia.

Non viene mai utilizzata alcuna operazione di moltiplicazione (consapevolmente). Lo spostamento viene eseguito su variabili a 8 bit. Viene eseguita un'aggiunta a 16 bit. Nessun tipo di dati a 32 bit.

Ridotto a mano, e solo leggermente. modifica: più offuscamento, meno caratteri: D Compila con avvisi con gcc.

Personaggi: 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);}

Puoi provare con questo:

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

Risultato:

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

Batterie OCaml +, 362 caratteri

Un algoritmo di moltiplicazione O (n * m) standard per scolari. Si noti che per soddisfare i requisiti della sfida, le operazioni vengono eseguite sui byte delle stringhe, che in OCaml sono (convenientemente, in questo caso) mutabili. Si noti inoltre che l'accumulatore snon trabocca mai di 16 bit, poiché 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)))

Per esempio,

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

0

JavaScript (Node.js) , 160 byte

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))

Provalo online!

Molto più nuovo linguaggio di quel tempo però

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.