Cara tercepat untuk mendapatkan integer mod 10 dan integer divide 10?

10

Jika suatu perangkat keras tidak mendukung operasi modulus atau divisi, dibutuhkan lebih banyak siklus CPU untuk mensimulasikan modulus / divisi dengan perangkat lunak. Apakah ada cara yang lebih cepat untuk menghitung pembagian dan modulus jika operan adalah 10?

Dalam proyek saya, saya sering perlu menghitung bilangan bulat bilangan bulat 10. Secara khusus, saya sedang mengerjakan PIC16F dan perlu menunjukkan angka pada LCD. Ada 4 digit untuk didukung, jadi ada 4 panggilan ke modulus dan fungsi pembagian (implementasi perangkat lunak). Yaitu, seperti berikut:

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

Ada area lain yang menggunakan kode serupa.

Donotalo
sumber
Mengapa beberapa lusin panggilan / detik menjadi masalah? Saya tidak akan repot kecuali proyek ini berfungsi penuh dan bebas bug.
Nick T
Saya perhatikan bahwa jika saya terus menampilkan beberapa nomor dalam loop sibuk utama, respons tombol menjadi lambat. Yaitu, untuk mendeteksi bahwa suatu tombol telah ditekan, saya harus menekan tombol itu sedikit lebih lama. Ini terjadi ketika jam sistem berjalan 32768 Hz.
Donotalo
Apakah Anda menggunakan interupsi? Mengapa Anda menggunakan xtal 32kHz; biasanya Anda bisa mendapatkan kinerja daya yang lebih rendah jika Anda beroperasi lebih cepat dan tidur ketika idle.
Nick T
Saya menggunakan interupsi. tetapi hanya untuk memperbarui tampilan, tidak layak untuk beralih ke osilasi kecepatan tinggi. bijaksana kekuatan. untuk proyek saya. itu harus dijalankan jam kecepatan rendah hampir 90% dari waktu hidupnya.
Donotalo
2
Sebagai catatan umum, buku Hacker Delight oleh Henry S. Warren, Jr. adalah yang sumber untuk pintar tipu bit-memutar-mutar. Saya mencari saran pembagian, dan tidak memiliki apa pun untuk dibagi dengan 10 yang lebih unggul daripada jawaban di bawah ini.
RBerteig

Jawaban:

11

Berikut ini adalah algoritma biner ke BCD yang saya gunakan beberapa tahun lalu berdasarkan yang ditemukan di sini . Saya menggunakan BCD eksternal untuk 7 seg display driver sehingga hasilnya dapat ditulis ke port yang tepat secara langsung sebagai paket BCD untuk output.

Ini cukup cepat jika Anda memiliki pengganda perangkat keras di PIC, saya menggunakan PIC18F97J60. Jika Anda tidak memiliki pengganda perangkat keras pada PIC Anda, pertimbangkan untuk menggunakan shift + add untuk perkaliannya.

Ini mengambil int 16bit yang tidak ditandatangani dan mengembalikan BCD yang dikemas dengan 5 digit, dapat dimodifikasi dan dibuat lebih cepat untuk 4 digit. Ini menggunakan shift + tambahan untuk perkiraan pembagian dengan 10 tetapi mengingat kisaran input terbatas itu tepat untuk penggunaan ini. Anda mungkin ingin mengemas hasil secara berbeda juga agar sesuai dengan cara Anda menggunakan hasilnya.

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);
}
Menandai
sumber
tautan bagus, terima kasih! tidak hanya mengoptimalkan kecepatan, tetapi juga mengurangi ukuran kode. Saya telah menerapkan "biner 12 bit hingga 4 Angka Desimal ASCII" dari tautan Anda karena itu tidak melibatkan perkalian.
Donotalo
8

Dengan asumsi bilangan bulat bertanda, pembagian dan perkalian dapat dibentuk dari bit shift. Dan dari divisi (integer) dan multiplikasi, modulo dapat diturunkan.

Untuk mengalikan dengan 10:

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

Membagi dengan 10 lebih sulit. Saya tahu beberapa algoritma pembagian. Jika saya ingat dengan benar, ada cara untuk membaginya dengan 10 dengan cepat menggunakan bit shift dan pengurangan, tetapi saya tidak dapat mengingat metode yang tepat. Jika itu tidak benar, maka ini adalah algoritma pembagian yang mengelola <130 siklus . Saya tidak yakin mikro apa yang Anda gunakan, tetapi Anda bisa menggunakannya dalam beberapa cara, bahkan jika Anda harus porting.

EDIT: Seseorang berkata di Stack Overflow , jika Anda dapat mentolerir sedikit kesalahan dan memiliki register sementara yang besar, ini akan bekerja:

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

Dengan asumsi Anda memiliki pembagian dan perkalian, modulo sederhana:

mod = x - ((x / z) * z)
Thomas O
sumber
6

Anda dapat mengonversi dari biner ke BCD yang dikemas tanpa pembagian apa pun menggunakan algoritma dabble ganda . Hanya menggunakan shift dan menambahkan 3 .

Misalnya mengonversi 243 10 = 11110011 2 menjadi biner

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

Algoritma ini sangat efisien ketika tidak ada pembagi perangkat keras yang tersedia. Terlebih lagi hanya shift kiri sebanyak 1 yang digunakan, jadi ini cepat bahkan ketika barrel shifter tidak tersedia

phuclv
sumber
4

Bergantung pada jumlah digit yang Anda butuhkan, Anda mungkin dapat menggunakan metode brute force ( d- nomor input, t- string ASCII keluaran):

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;

Anda juga bisa mengubah multiple ifs menjadi loop, dengan kekuatan sepuluh diperoleh dengan perkalian atau tabel pencarian.

jpc
sumber
2

Catatan aplikasi ini menjelaskan algoritma untuk aritmatika BCD, termasuk konversi dari biner ke BCD dan sebaliknya. Appnote adalah oleh Atmel, yang merupakan AVR, tetapi algoritma yang dijelaskan adalah prosesor-independen.

stevenvh
sumber
1

Saya tidak memiliki jawaban yang baik, tetapi ada diskusi yang bagus tentang situs saudara Stack Overflow kami tentang topik yang sama persis tentang optimasi divisi dan modulo.

Apakah Anda memiliki cukup memori untuk mengimplementasikan tabel pencarian?

Hackers Delight memiliki makalah tentang algoritma pembagian optimal.

Adam Lawrence
sumber
tidak, tidak punya cukup memori. Saya ingin melakukan itu menggunakan penambahan, pengurangan, dan pergeseran bit.
Donotalo
1

Pernahkah Anda mempertimbangkan untuk memegang nilai itu sebagai BCD sepanjang waktu (menggunakan subrutin "BCD increment" dan "BCD add" khusus sederhana), daripada menahan nilai itu dalam bentuk biner dan mengonversi ke BCD sesuai kebutuhan (menggunakan konversi yang lebih sulit dipahami " dari biner ke BCD "subroutine)?

Pada satu waktu, semua komputer menyimpan semua data sebagai angka desimal (roda gigi sepuluh posisi, tabung vakum kode dua-dari-lima, BCD, dll.), Dan warisan itu masih ada sampai sekarang. (lihat Mengapa chip jam waktu nyata menggunakan BCD ).

davidcary
sumber
Angka yang akan ditampilkan pada LCD adalah variabel, mulai dari -1999 hingga 1999. Ini menunjukkan suhu dan dihitung dalam format biner.
Donotalo
1

The PICList adalah sumber daya yang luar biasa bagi orang-orang pemrograman prosesor PIC.

Konversi BCD

Sudahkah Anda mempertimbangkan untuk menggunakan subrutin biner-ke-BCD yang dicoba dan diuji secara khusus yang dioptimalkan untuk PIC16F?

Secara khusus, orang-orang di PICList telah menghabiskan banyak waktu untuk mengoptimalkan konversi biner ke BCD pada PIC16F. Rutinitas tersebut (masing-masing tangan dioptimalkan untuk ukuran tertentu) dirangkum di "Metode Microcontoller Radix Conversion Math PIC" http://www.piclist.com/techref/microchip/math/radix/index.htm

divisi integer dan mod

Pada CPU seperti PIC16F, subrutin yang khusus untuk dibagi dengan konstanta seringkali jauh lebih cepat daripada tujuan umum "membagi variabel A dengan variabel B". Anda mungkin ingin meletakkan konstanta Anda (dalam hal ini, "0,1") dalam "Pembuatan Kode untuk Penggandaan / Divisi Konstan" http://www.piclist.com/techref/piclist/codegen/constdivmul.htm atau periksa rutinitas kalengan dekat http://www.piclist.com/techref/microchip/math/basic.htm .

davidcary
sumber
1

Diberikan penggandaan perangkat keras 8x8, seseorang dapat menghitung divmod-10 dari nomor ukuran sewenang-wenang dengan menggunakan rutin yang menghitungnya untuk nomor 12-bit dalam kisaran 0-2559 melalui prosedur:

  1. Asumsikan nomor asli dalam OrigH: OrigL
  2. Membagi nomor asli dengan dua dan menyimpannya di TempH: TempL
  3. Tambahkan MSB TempL * 51 ke LSB TempH * 51. Itu adalah hasil bagi perkiraan
  4. Lipat gandakan perkiraan quotient dengan 10, buang nilai MSB.
  5. Kurangi LSB dari hasil itu dari LSB dari nomor aslinya.
  6. Jika nilai itu 10 atau lebih besar (maks akan menjadi 19), kurangi 10 dan tambahkan 1 ke perkiraan perkiraan

Saya akan menyarankan menulis rutin divmod yang MSB nomornya akan berada di W, dan LSB ditunjuk oleh FSR; rutin harus menyimpan hasil bagi dalam FSR dengan post-decrement dan meninggalkan sisanya di W. Untuk membagi panjang 32-bit dengan 10, orang kemudian akan menggunakan sesuatu seperti:

  movlw 0
  lfsr 0, _number + 3; Tunjuk ke MSB
  panggil _divmod10_step
  panggil _divmod10_step
  panggil _divmod10_step
  panggil _divmod10_step

Langkah divmod-6 akan sangat mirip, kecuali menggunakan konstanta 85 dan 6 daripada 51 dan 10. Dalam kedua kasus, saya berharap divmod10_step akan menjadi 20 siklus (ditambah empat untuk panggilan / pengembalian) sehingga divmod10 pendek akan menjadi sekitar 50 siklus dan divmod10 panjang akan menjadi sekitar 100 (jika satu kasus khusus langkah pertama, seseorang bisa menghemat beberapa siklus).

supercat
sumber
1

ini mungkin bukan yang tercepat tetapi merupakan cara sederhana.

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