Uzun çarpma, bir seferde 8 bit


13

Size 16 bit bir makine verilir ve rasgele boyutlu tamsayıların çarpımı uygulamanız istenir. Kayıtlarınız yalnızca 16 bit sayıları tutabilir ve en büyük çarpma komutu iki 8 bit giriş alır ve 16 bit sonuç üretir.

Programınız iki boyutlu gelişigüzel pozitif sayı girmeli ve ürününü çıkarmalıdır. Her giriş numarası kendi satırında küçük bir endian bayt dizisi olarak kodlanır, burada her bayt 2 basamaklı bir onaltılık sayıdır. Çıktı benzer şekilde biçimlendirilmelidir. Belki de en iyi bir örnekle açıklanmıştır:

giriş

1f 4a 07
63 a3

çıktı

fd 66 03 a7 04

477727 * 41827 = 19981887229 çarpmasını kodlayan.

Her giriş numarasının son (en önemli) baytının sıfır olmadığını ve çıkardığınız sayının son yığınının sıfır olmadığını varsayabilirsiniz. Her iki giriş numarası en fazla 100 bayt uzunluğunda olacaktır.

En küçük kod kazanır.

Unutmayın, kullanmanıza izin verilen en büyük çarpan 1 bayt * 1 bayttır ve 2 bayttan daha büyük tamsayı türü yoktur!


Haskell gibi varsayılan 8-bit tipi olmayan diller için bu çok önemlidir.
FUZxxl

1
Ekleme ne olacak? Hazır bir keyfi boyut ekleme fonksiyonuna sahip miyiz? Değilse, ne olabilir biz ekleyebilir?
Timwi

@Timwi: Bir seferde 16 bitlik istediğiniz her şeyi yapabilirsiniz. Ekler, değiştirir, her neyse. Kendinizi sentezlemek için ihtiyacınız olan daha büyük işlemler.
Keith Randall

Doğru bayt sırası için +1
12Me21

Yanıtlar:


13

Perl, 137 karakter

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

Uyarılar

  • Bazen 00sonucun sonuna fazladan bir bayt yazdırır . Tabii ki bu ekstra baytta bile sonuç hala doğrudur.
  • Sonuçtaki son onaltılık bayttan sonra fazladan bir boşluk yazdırır.

açıklama

Açıklama biraz uzun olacak, ama bence buradaki insanların çoğu bunu ilginç bulacak.

Her şeyden önce, 10 yaşındayken, aşağıdaki küçük numarayı öğrettim. Bununla iki pozitif sayıyı çarpabilirsiniz. Bunu 13 × 47 örneğini kullanarak açıklayacağım. İlk sayıyı 13 yazarak ve 1'e ulaşana kadar 2'ye (her seferinde aşağı doğru) bölerek başlıyorsunuz :

13
 6
 3
 1

Şimdi, 13'ün yanında başka bir sayı olan 47'yi yazıyorsunuz ve aynı sayıda 2 ile çarpmaya devam ediyorsunuz :

13     47
 6     94
 3    188
 1    376

Soldaki sayısıdır Artık tüm satırları üstünü bile . Bu durumda, bu sadece 6.

13     47
 3    188
 1    376
     ----
      611

Ve bu doğru cevap. 13 × 47 = 611.

Şimdi, hepiniz bilgisayar meraklıları olduğunuz için, aslında sol ve sağ sütunlarda yaptığımızın sırasıyla x >> 1ve olduğunu fark etmiş olacaksınız y << 1. Ayrıca, ysadece eğer x & 1 == 1. Bu, doğrudan sözde kodda yazacağım bir algoritmaya doğrudan çevirir:

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

ifBir çarpma kullanmak için yeniden yazabiliriz ve sonra bunu kolayca değiştirebiliriz, böylece bit-bit yerine bayt-bayt temelinde çalışır:

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

Bu hala ykeyfi boyutta bir çarpma içerir , bu yüzden bunu da bir döngüye dönüştürmeliyiz. Bunu Perl'de yapacağız.

Şimdi her şeyi Perl'e çevirin:

  • $xve $ysahip oldukları, yani altıgen biçiminde girişler en önemli biti önce .

  • Böylece, x >> 8ben yerine $x =~ s/.. *//s. Son bayt üzerinde bir boşluk olmayabilir çünkü boşluk + yıldıza ihtiyacım var (boşluk + ?da kullanabilirsiniz ). Bu, kaldırılan baytı ( x & 255) otomatik olarak koyar $&.

  • y << 8basitçe $y = "00$y".

  • resultAslında sayısal bir dizidir @r. Sonunda, öğesinin her bir elemanı @rcevabın bir baytını içerir, ancak hesaplamanın yarısında birden fazla bayt içerebilir. Her bir değerin asla iki bayttan (16 bit) fazla olmadığını ve sonucun her zaman sonunda bir bayt olduğunu size kanıtlayacağım .

İşte Perl kodu çözülmüş ve yorumlanmıştır:

# 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

Şimdi bunun her zaman bayt verdiğini ve hesaplamanın asla iki bayttan daha büyük değerler üretmediğini kanıtlamak için . Bunu whiledöngü üzerinde indüksiyonla kanıtlayacağım :

  • Boş @r(hepsi bir zamanlar içinde hiçbir değerlere sahip olduğundan) başlangıç kısmında açıkça o 0xFF daha değerler büyük ise. Bu temel durumu sonuçlandırır.

  • Şimdi, @rher whileyinelemenin başında yalnızca tek bayt içeren şu değer verilir :

    • forDöngü açıkça &=255 ile sonuç dizide tüm değerleri s sonuncusu hariç sadece o sonuncusu bakmak gerekir bu yüzden.

    • Her zaman yalnızca bir bayt kaldırdığımızı biliyoruz $xve $y:

      • Bu nedenle, $e*hexiki tek bayt değerin çarpımıdır, yani aralıkta olduğu anlamına gelir 0 — 0xFE01.

      • Endüktif hipotezle $r[$i]bir bayttır; bu nedenle $s = $r[$i] += $e*hexaralıktadır 0 — 0xFF00.

      • Bu nedenle, $s >> 8her zaman bir bayttır.

    • $ydöngünün 00her yinelemesinde ekstra büyür while:

      • Bu nedenle, whiledöngünün her yinelemesinde , iç fordöngü önceki whileyinelemeden daha fazla bir yineleme için çalışır .

      • Bu nedenle, $r[++$i] += $s >> 8son tekrarında fordöngü hep ekler $s >> 8için 0ve biz bunu çoktan $s >> 8hep bir byte olduğunu.

    • Bu nedenle, döngünün @rsonunda depolanan son değer forde tek bir bayttır.

Bu harika ve heyecan verici bir meydan okumaya son verir. Gönderdiğiniz için çok teşekkürler!


4

C Çözümü

Bu çözüm giriş doğrulaması yapmaz. Ayrıca sadece hafifçe test edilmiştir. Hız gerçekten dikkate alınmadı. Malloc'un hafızası ve ne kadar tuttuğu konusunda özellikle akıllı değil. Yeterli ve gereğinden fazla olması garanti.

m () bir dizeyi kabul eder, dizede her sayıdan bir tane olmak üzere iki yeni satır bekler. Yalnızca sayılar, küçük harfler, boşluklar ve yeni satırlar beklenir. Onaltılık basamakların her zaman bir çift olmasını bekler.

Hiçbir çarpma işlemi kullanılmaz (bilerek). Vites değiştirme 8-bit değişkenlerde gerçekleştirilir. 16 bitlik bir ekleme yapılır. 32 bit veri türü yok.

Elle ve sadece hafifçe küçüldü. edit: daha fazla gizleme, daha az karakter: D gcc ile uyarıları derler.

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

Bununla test edebilirsiniz:

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

Sonuç:

$ ./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 + Piller, 362 karakter

Standart bir O (n * m) öğrenci çarpma algoritması. Zorluk gereksinimlerini karşılamak için, işlemlerin OCaml'de (uygun olarak bu durumda) değiştirilebilir olan dizelerin baytları üzerinde yapıldığını unutmayın. Ayrıca akümülatörün sasla 16 bitten taşmadığına dikkat edin, çünkü 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)))

Örneğin,

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

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

Çevrimiçi deneyin!

Yine de o zamandan çok daha yeni bir dil

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.