Tamsayı mod 10 ve tamsayı bölme 10 almanın en hızlı yolu?


10

Bir donanım modül veya bölme işlemlerini desteklemiyorsa, yazılım tarafından modül / bölme simülasyonu yapmak için daha fazla CPU döngüsü gerekir. İşlenen 10 ise bölme ve modül hesaplamanın daha hızlı bir yolu var mı?

Projemde sık sık 10 tamsayı modülünü hesaplamam gerekiyor. Özellikle, PIC16F üzerinde çalışıyorum ve LCD'de bir sayı göstermem gerekiyor. Desteklemek için 4 basamak vardır, bu nedenle modül ve bölme işlevine 4 çağrı (yazılım uygulaması) vardır. Yani, aşağıdaki gibi:

digit = number % 10;    // call to an expensive function
number /= 10;           // call to an expensive function
somehow_lit_segments();

digit = number % 10;    // call to an expensive function
number /= 10;           // call to an expensive function
somehow_lit_segments();

digit = number % 10;    // call to an expensive function
number /= 10;           // call to an expensive function
somehow_lit_segments();

digit = number % 10;    // call to an expensive function
number /= 10;           // call to an expensive function
somehow_lit_segments();

Benzer kodu kullanan başka alanlar da vardır.


Neden birkaç düzine çağrı / saniye sorun oluşturuyor? Proje tamamen işlevsel ve hatasız olmadığı sürece rahatsız olmaz.
Nick T

Ana meşgul döngüsünde sürekli olarak bir sayı görüntülersem, düğme yanıtının yavaşladığını fark ettim. Yani, bir düğmeye basıldığını algılamak için, o düğmeye biraz daha basmam gerekiyor. Bu, sistem saati 32768 Hz çalışırken olur.
Donotalo

Kesmeler mi kullanıyorsunuz? Neden 32kHz xtal kullanıyorsunuz; genellikle daha hızlı çalışır ve boşta uyku moduna geçerseniz daha düşük güç performansı elde edebilirsiniz.
Nick T

Kesmeler kullanıyorum. ancak sadece ekranı güncellemek için yüksek hızlı salınıma geçmeye değmez. Güç bilge. projem için. Yaşam süresinin yaklaşık% 90'ında düşük hızlı saat çalıştırılmalıdır.
Donotalo

2
Genel bir nokta olarak, kitap Hacker'ın Delight Henry S. Warren tarafından, Jr. zeki bit twiddling hile için kaynak. Bölme önerileri aradım ve 10'a bölmek için aşağıdaki cevaplardan daha üstün bir şey yok.
RBerteig

Yanıtlar:


11

Heres burada bulunan bir dayalı birkaç yıl önce kullandığım bir BCD algoritması ikili . 7 segment ekran sürücüsüne harici bir BCD kullanıyordum.

PIC'de bir donanım çarpanı varsa, oldukça hızlıdır, ben bir PIC18F97J60 kullanıyordum. PIC'nizde bir donanım çarpanı yoksa, çarpma için shift + add kullanmayı düşünün.

Bu imzasız bir 16bit int alır ve 5 basamaklı paketlenmiş BCD döndürür, değiştirilebilir ve 4 basamak için daha hızlı yapılabilir. Bölmeyi 10'a yakınlaştırmak için shift + eklemelerini kullanır, ancak sınırlı giriş aralığı göz önüne alındığında bu kullanım için kesindir. Sonucu nasıl kullandığınıza göre hizalamak için sonucu farklı şekilde paketlemek isteyebilirsiniz.

void intToPackedBCD( uint16_t n, uint8_t *digits ) {

    uint8_t d4, d3, d2, d1, d0, q;  //d4 MSD, d0 LSD

    d1 = (n>>4)  & 0xF;
    d2 = (n>>8)  & 0xF;
    d3 = (n>>12) & 0xF;

    d0 = 6*(d3 + d2 + d1) + (n & 0xF);
    q = (d0 * 0xCD) >> 11;
    d0 = d0 - 10*q;

    d1 = q + 9*d3 + 5*d2 + d1;
    q = (d1 * 0xCD) >> 11;
    d1 = d1 - 10*q;

    d2 = q + 2*d2;
    q = (d2 * 0x1A) >> 8;
    d2 = d2 - 10*q;

    d3 = q + 4*d3;
    d4 = (d3 * 0x1A) >> 8;
    d3 = d3 - 10*d4;

    digits[0] = (d4<<4) | (d3);
    digits[1] = (d2<<4) | (d1);
    digits[2] = (d0<<4);
}

harika bağlantı, teşekkürler! sadece hızı optimize etmekle kalmaz, aynı zamanda kod boyutunu da azaltır. Bağlantınızdan "12 bit binary to 4 ASCII Ondalık Basamak" uyguladım çünkü bu herhangi bir çarpımı içermiyor.
Donotalo

8

İmzasız tamsayılar varsa, bit kaydırmalarından bölme ve çarpma oluşturulabilir. Ve (tamsayı) bölünme ve çarpmadan modulo türetilebilir.

10 ile çarpmak için:

y = (x << 3) + (x << 1);

10'a bölmek daha zordur. Birkaç bölme algoritması biliyorum. Doğru hatırlıyorsam, bit kaydırma ve çıkarma kullanarak 10'a hızlı bir şekilde bölmenin bir yolu var, ancak kesin yöntemi hatırlayamıyorum. Bu doğru değilse, bu <130 döngüyü yöneten bir bölme algoritmasıdır . Hangi mikrodu kullandığınızdan emin değilim, ancak onu bir şekilde taşımanız gerekse bile bir şekilde kullanabilirsiniz.

DÜZENLEME: Birisi Stack Overflow'da diyor ki , biraz hataya katlanabiliyor ve büyük bir geçici kaydınız varsa, bu işe yarayacak:

temp = (ms * 205) >> 11;  // 205/2048 is nearly the same as /10

Bölünme ve çarpma yaptığınızı varsayarsak, modulo basittir:

mod = x - ((x / z) * z)

6

Çift dabble algoritması kullanarak herhangi bir bölme olmadan ikili dosyadan paketlenmiş BCD'ye dönüştürebilirsiniz . Yalnızca shift ve add 3 kullanır .

Örnek dönüştürmek için 243 10 = 11.110.011 2 ikili

0000 0000 0000   11110011   Initialization
0000 0000 0001   11100110   Shift
0000 0000 0011   11001100   Shift
0000 0000 0111   10011000   Shift
0000 0000 1010   10011000   Add 3 to ONES, since it was 7
0000 0001 0101   00110000   Shift
0000 0001 1000   00110000   Add 3 to ONES, since it was 5
0000 0011 0000   01100000   Shift
0000 0110 0000   11000000   Shift
0000 1001 0000   11000000   Add 3 to TENS, since it was 6
0001 0010 0001   10000000   Shift
0010 0100 0011   00000000   Shift
   2    4    3
       BCD

Bu algoritma kullanılabilir donanım bölücüsü olmadığında çok etkilidir. Sadece 1'e kadar sola kaydırma daha fazla kullanılır, bu nedenle namlu değiştiricisi olmasa bile hızlıdır


4

İhtiyacınız olan basamak miktarına bağlı olarak kaba kuvvet yöntemini kullanabilirsiniz ( d- giriş numarası, t- çıkış ASCII dizesi):

t--;
if (d >= 1000) t++; *t = '0'; while (d >= 1000) { d -= 1000; *t += 1; }
if (d >= 100) t++; *t = '0'; while (d >= 100) { d -= 100; *t += 1;}
if (d >= 10) t++; *t = '0'; while (d >= 10) { d -= 10; *t += 1;}
t++; *t = '0' + d;

Ayrıca, çarpma veya bir arama tablosu tarafından elde edilen on gücüyle, birden çok if'leri bir döngüye dönüştürebilirsiniz.


2

Bu uygulama notu , ikili dosyadan BCD'ye dönüştürme ve tersi de dahil olmak üzere BCD aritmetiği için algoritmaları açıklar. Appnote, AVR olan Atmel'dir, ancak açıklanan algoritmalar işlemciden bağımsızdır.


1

İyi bir cevabım yok, ancak kardeş sitemiz Stack Overflow'da aynı bölüm ve modulo optimizasyonu konusunda harika bir tartışma var.

Bir arama tablosu uygulamak için yeterli belleğiniz var mı?

Hacker Delight'ın optimum bölüm algoritmaları hakkında bir makalesi vardır .


Hayır, yeterli hafızanız yok. Toplama, çıkarma ve bit kaydırma kullanarak bunu yapmak istiyorum.
Donotalo

1

Bu değeri ikili formda tutmak ve gerektiğinde BCD'ye dönüştürmek (anlamak daha zor "bir dönüştürmek yerine, bu değeri her zaman BCD olarak tutmayı (basit özel" BCD artışı "ve" BCD ekleme "alt rutinlerini kullanarak) düşündünüz mü? ikili BCD "altyordam)?

Bir kerede, tüm bilgisayarlar tüm verileri ondalık basamak olarak sakladı (on konumlu dişliler, beşten ikisi kod vakum tüpleri, BCD, vb.) Ve bu miras bugün hala devam ediyor. (bkz. Gerçek zamanlı saat yongaları neden BCD kullanıyor? ).


LCD'de görüntülenecek sayı -1999 ile 1999 arasında değişen bir değişkendir. Bir sıcaklığı gösterir ve ikili biçimde hesaplanır.
Donotalo

1

PICList PIC işlemcileri programlama insanlar için inanılmaz bir kaynaktır.

BCD dönüşümü

PIC16F için özel olarak optimize edilmiş kullanıma hazır denenmiş ve test edilmiş ikili-BCD alt rutini kullanmayı düşündünüz mü?

Özellikle, PICList'teki insanlar bir PIC16F'de ikili-BCD dönüşümlerini optimize etmek için çok zaman harcadı. Bu rutinler (her biri belirli bir boyut için elle optimize edilmiştir) "PIC Mikrodenetleyici Radix Dönüşüm Matematik Yöntemleri" kısmında özetlenmiştir http://www.piclist.com/techref/microchip/math/radix/index.htm

tamsayı bölme ve mod

PIC16F gibi bir CPU'da, bir sabitle bölmek için uzmanlaşmış bir alt rutin genellikle genel amaçlı bir "A değişkenini B değişkenine böl" rutininden çok daha hızlıdır. Sabitinizi (bu durumda, "0.1") "Sabit Çarpma / Bölme için Kod Üretimi" http://www.piclist.com/techref/piclist/codegen/constdivmul.htm adresine koymak veya http://www.piclist.com/techref/microchip/math/basic.htm yakınındaki konserve rutinler .


1

8x8 donanım çarpımı göz önüne alındığında, yordam boyutlu bir sayının divmod-10'unu yordam yoluyla 0-2559 aralığında 12 bit sayı için hesaplayan bir rutin kullanılarak hesaplanabilir:

  1. OrigH: OrigL'deki orijinal numarayı al
  2. Orijinal numarayı ikiye bölün ve TempH: TempL'de saklayın
  3. TempL * 51'in MSB'sini TempH * 51'in LSB'sine ekleyin. Bu yaklaşık bölümdür
  4. Değerin MSB'sini atarak yaklaşık bölümü 10 ile çarpın.
  5. Bu sonucun LSB'sini orijinal numaranın LSB'sinden çıkarın.
  6. Bu değer 10 veya daha büyükse (maks. 19 olacaktır), 10 değerini çıkarın ve yaklaşık bölüme 1 ekleyin

Sayının MSB'sinin W cinsinden olacağı bir divmod rutini yazmanızı ve LSB'nin FSR tarafından işaret edilmesini öneririm; rutin bölümü azalmadan sonra FSR'de saklamalı ve kalanını W'da bırakmalıdır. 32 bit uzunluğunu 10'a bölmek için, şöyle bir şey kullanılır:

  movlw 0
  lfsr 0, _numarası + 3; MSB'nin üzerine gelin
  call _divmod10_step
  call _divmod10_step
  call _divmod10_step
  call _divmod10_step

Bir divmod-6 adımı, 51 ve 10 yerine 85 ve 6 sabitlerinin kullanılması dışında çok benzer olacaktır. Her iki durumda da, divmod10_step'in 20 döngü (arama / geri dönüş için artı dört) olmasını beklerdim, bu yüzden kısa bir divmod10 olurdu yaklaşık 50 döngü ve uzun bir divmod10 yaklaşık 100 olacaktır (eğer özel durumlar ilk adım ise, birkaç döngü kaydedilebilir).


1

bu en hızlı olmayabilir ama basit bir yoldur.

 a = 65535;

    l = 0;
    m = 0;
    n = 0;
    o = 0;
    p = 0;

    while (a >= 10000)
    {   a -= 10000;
        l += 1;
    }
     while (a >= 1000)
    {   a -= 1000;
        m += 1;
    }
     while (a >= 100)
    {   a -= 100;
        n += 1;
    }
     while (a >= 10)
    {   a -= 10;
        o += 1;
    }
     while (a > 0)
    {   a -= 1;
        p += 1;
    }
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.