Membandingkan sedikit ke boolean

12

Katakanlah saya memiliki satu set bendera, yang dikodekan dalam uint16_t flags. Sebagai contoh AMAZING_FLAG = 0x02,. Sekarang, saya punya fungsi. Fungsi ini perlu memeriksa apakah saya ingin mengubah bendera, karena jika saya ingin melakukan itu, saya perlu menulis ke flash. Dan itu mahal. Oleh karena itu, saya ingin cek yang memberi tahu saya apakah flags & AMAZING_FLAGsama dengan doSet. Ini adalah ide pertama:

setAmazingFlag(bool doSet)
{
    if ((flags & AMAZING_FLAG) != (doSet ? AMAZING_FLAG : 0)) {
        // Really expensive thing
        // Update flags
    }
}

Ini bukan pernyataan intuitif jika. Saya merasa harus ada cara yang lebih baik, seperti:

if ((flags & AMAZING_FLAG) != doSet){

}

Tapi ini tidak benar-benar berfungsi, truetampaknya sama dengan 0x01.

Jadi, apakah ada cara yang rapi untuk membandingkan sedikit dengan boolean?

Cheiron
sumber
2
Tidak sepenuhnya jelas apa yang dibutuhkan logika Anda. Apakah ini (flags & AMAZING_FLAG) && doSet:?
kaylum
Pertanyaannya tidak jelas. Kami ingin memeriksa apakah 'bendera' 0x01 atau tidak. Apakah itu yang Anda inginkan? Jika ya, maka kita dapat menggunakan operator bitwise '&'.
Vikas Vijayan
jika Anda ingin membuatnya lebih mudah dibaca setAmazingFlag hanya ketika doSet benar, maka nama fungsi memeriksa lebih baik jika tidak Anda memiliki fungsi yang mungkin atau mungkin tidak melakukan apa yang dikatakan namanya, membuat pembacaan kode yang buruk
AndersK
Jika semua yang Anda coba lakukan adalah membuatnya lebih mudah dibaca, buat saja fungsi `flagsNotEqual``.
Jeroen3

Jawaban:

18

Untuk mengonversi angka yang bukan nol menjadi 1 (benar), ada trik lama: terapkan !operator (bukan) dua kali.

if (!!(flags & AMAZING_FLAG) != doSet){
pengguna253751
sumber
4
Atau (bool)(flags & AMAZING_FLAG) != doSet- yang saya temukan lebih langsung. (Meskipun ini menunjukkan bahwa Mr Microsoft memiliki masalah dengan ini.) Juga, ((flags & AMAZING_FLAG) != 0)mungkin persis apa yang dikompilasi dan sangat eksplisit.
Chris Hall
"Juga, ((flag & AMAZING_FLAG)! = 0) mungkin persis seperti apa yang dikompilasi dan benar-benar eksplisit". Apakah itu? Bagaimana jika doSet benar?
Cheiron
@ ChrisHall Apakah (bool) 2 menghasilkan 1 atau masih 2, di C?
user253751
3
C11 Standard, 6.3.1.2 Tipe Boolean: "Ketika nilai skalar apa pun dikonversi menjadi _Bool, hasilnya adalah 0 jika nilainya sebanding dengan 0; jika tidak, hasilnya adalah 1.". Dan 6.5.4 Operator pemeran: "Mendahului ekspresi oleh nama tipe tanda kurung mengubah nilai ekspresi ke tipe bernama."
Chris Hall
@Cheiron, agar lebih jelas: ((bool)(flags & AMAZING_FLAG) != doSet)memiliki efek yang sama seperti (((flags & AMAZING_FLAG) != 0) != doSet)dan mereka berdua mungkin akan mengkompilasi ke hal yang persis sama. Yang disarankan (!!(flags & AMAZING_FLAG) != doSet)adalah setara, dan saya membayangkan akan mengkompilasi ke hal yang sama juga. Ini sepenuhnya masalah selera yang menurut Anda lebih jelas: (bool)pemeran mengharuskan Anda untuk mengingat bagaimana pemeran dan konversi ke _Bool berfungsi; yang !!membutuhkan beberapa senam mental lainnya, atau bagi Anda untuk mengetahui "trik".
Chris Hall
2

Anda perlu mengonversi bit mask ke pernyataan boolean, yang dalam C sama dengan nilai 0atau 1.

  • (flags & AMAZING_FLAG) != 0. Cara paling umum.

  • !!(flags & AMAZING_FLAG). Agak umum, juga OK untuk digunakan, tetapi agak samar.

  • (bool)(flags & AMAZING_FLAG). Cara C modern dari C99 dan seterusnya.

Ambil salah satu dari alternatif di atas, lalu bandingkan dengan boolean Anda menggunakan !=atau ==.

Lundin
sumber
1

Dari sudut pandang logis, flags & AMAZING_FLAGhanya sedikit operasi yang menutupi semua flag lainnya. Hasilnya adalah nilai numerik.

Untuk menerima ke nilai boolean, Anda akan menggunakan perbandingan

(flags & AMAZING_FLAG) == AMAZING_FLAG

dan sekarang dapat membandingkan nilai logis ini dengan doSet.

if (((flags & AMAZING_FLAG) == AMAZING_FLAG) != doSet)

Dalam C mungkin ada singkatan, karena aturan konversi angka ke nilai boolean. Jadi Anda juga bisa menulis

if (!(flags & AMAZING_FLAG) == doSet)

untuk menulis yang lebih singkat. Tetapi versi sebelumnya lebih baik dalam hal keterbacaan.

Ctx
sumber
1

Anda dapat membuat topeng berdasarkan doSetnilai:

#define AMAZING_FLAG_IDX 1
#define AMAZING_FLAG (1u << AMAZING_FLAG_IDX)
...

uint16_t set_mask = doSet << AMAZING_FLAG_IDX;

Sekarang cek Anda dapat terlihat seperti ini:

setAmazingFlag(bool doSet)
{
    const uint16_t set_mask = doSet << AMAZING_FLAG_IDX;

    if (flags & set_mask) {
        // Really expensive thing
        // Update flags
    }
}

Pada beberapa arsitektur, !!dapat dikompilasi ke cabang dan dengan ini, Anda mungkin memiliki dua cabang:

  1. Normalisasi oleh !!(expr)
  2. Dibandingkan dengan doSet

Keuntungan dari proposal saya adalah cabang tunggal yang dijamin.

Catatan: pastikan Anda tidak memperkenalkan perilaku tidak terdefinisi dengan menggeser ke kiri lebih dari 30 (dengan asumsi integer adalah 32 bit). Ini dapat dengan mudah dicapai oleh astatic_assert(AMAZING_FLAG_IDX < sizeof(int)*CHAR_BIT-1, "Invalid AMAZING_FLAG_IDX");

Alex Lop.
sumber
1
Segala macam ekspresi boolean memiliki potensi untuk menghasilkan cabang. Saya akan membongkar dulu, sebelum menerapkan trik optimasi manual aneh.
Lundin
1
@Lundin, tentu, itu sebabnya saya menulis "Pada beberapa arsitektur" ...
Alex Lop.
1
Ini sebagian besar masalah untuk optimizer kompiler dan tidak begitu banyak ISA itu sendiri.
Lundin
1
@Lundin saya tidak setuju. Beberapa arsitektur memiliki ISA untuk bit set bersyarat / reset dalam hal tidak diperlukan brach, yang lain tidak. godbolt.org/z/4FDzvw
Alex Lop.
Catatan: Jika Anda melakukan ini, silakan menentukan AMAZING_FLAGdalam hal AMAZING_FLAG_IDX(misalnya #define AMAZING_FLAG ((uint16_t)(1 << AMAZING_FLAG_IDX))), sehingga Anda tidak memiliki data yang sama yang didefinisikan dalam dua tempat seperti yang satu dapat diperbarui (misalnya, dari 0x4ke 0x8) sementara yang lain ( IDXdari 2) yang tersisa tidak berubah .
ShadowRanger
-1

Ada beberapa cara untuk melakukan tes ini:

Operator ternary mungkin menghasilkan lompatan mahal:

if ((flags & AMAZING_FLAG) != (doSet ? AMAZING_FLAG : 0))

Anda juga dapat menggunakan konversi boolean, yang mungkin efisien atau tidak:

if (!!(flags & AMAZING_FLAG) != doSet)

Atau alternatifnya yang setara:

if (((flags & AMAZING_FLAG) != 0) != doSet)

Jika multiplikasi murah, Anda dapat menghindari lompatan dengan:

if ((flags & AMAZING_FLAG) != doSet * AMAZING_FLAG)

Jika flagstidak ditandatangani dan kompilernya sangat pintar, divisi di bawah ini dapat dikompilasi ke perubahan sederhana:

if ((flags & AMAZING_FLAG) / AMAZING_FLAG != doSet)

Jika arsitektur menggunakan aritmatika komplemen dua, berikut adalah alternatif lain:

if ((flags & AMAZING_FLAG) != (-doSet & AMAZING_FLAG))

Bergantian, orang bisa mendefinisikan flags sebagai struktur dengan bitfields dan menggunakan sintaks yang lebih mudah dibaca:

if (flags.amazing_flag != doSet)

Sayangnya, pendekatan ini biasanya disukai karena spesifikasi bitfield tidak memungkinkan kontrol yang tepat atas implementasi bit-level.

chqrlie
sumber
Sasaran utama setiap bagian kode harus mudah dibaca, yang sebagian besar contoh Anda tidak. Masalah yang lebih besar muncul ketika Anda benar-benar memuat contoh Anda ke dalam kompiler: dua contoh implementasi awal memiliki kinerja terbaik: paling sedikit jumlah lompatan dan beban (ARM 8.2, -Os) (mereka setara). Semua implementasi lainnya berkinerja lebih buruk. Secara umum, seseorang harus menghindari melakukan hal-hal mewah (optimasi mikro), karena Anda akan mempersulit kompiler untuk mencari tahu apa yang sedang terjadi, yang menurunkan kinerja. Oleh karena itu, -1.
Cheiron