Haruskah saya menghindari menggunakan int unsigned di C #?

23

Saya baru-baru ini berpikir tentang penggunaan bilangan bulat tak bertanda di C # (dan saya kira argumen serupa dapat dikatakan tentang "bahasa tingkat tinggi" lainnya)

Ketika Membutuhkan bilangan bulat saya biasanya tidak dihadapkan dengan dilema ukuran bilangan bulat, sebuah contoh akan menjadi properti usia kelas Person (tetapi pertanyaannya tidak terbatas pada properti). Dengan mengingat hal itu, sejauh yang saya bisa lihat, hanya satu keuntungan menggunakan integer yang tidak ditandatangani ("uint") dibandingkan integer yang ditandatangani ("int") - keterbacaan. Jika saya ingin mengungkapkan gagasan bahwa suatu usia hanya dapat menjadi positif, saya dapat mencapainya dengan mengatur jenis usia ke usia.

Di sisi lain, perhitungan pada bilangan bulat tak bertanda dapat menyebabkan kesalahan dalam semua jenis dan membuatnya sulit untuk melakukan operasi seperti mengurangi dua usia. (Saya membaca ini adalah salah satu alasan Java menghilangkan bilangan bulat yang tidak ditandai)

Dalam kasus C # saya juga dapat berpikir bahwa klausa penjaga pada setter akan menjadi solusi yang memberikan yang terbaik dari dua dunia, tetapi, ini tidak akan berlaku ketika saya misalnya, suatu zaman akan diberikan ke beberapa metode. Solusinya adalah dengan mendefinisikan kelas yang disebut Umur dan memiliki usia properti menjadi satu-satunya hal di sana, tetapi pola ini akan membuat saya membuat banyak kelas dan akan menjadi sumber kebingungan (pengembang lain tidak akan tahu kapan objek hanya pembungkus dan ketika itu sesuatu yang lebih sofisticaded).

Apa beberapa praktik umum terbaik mengenai masalah ini? Bagaimana saya harus menghadapi skenario seperti ini?

Belgi
sumber
1
Selain itu, unsigned int tidak sesuai dengan CLS, yang berarti Anda tidak dapat memanggil API yang menggunakannya dari bahasa .NET lainnya.
Nathan Cooper
2
@NathanCooper: ... "tidak dapat memanggil API yang menggunakannya dari beberapa bahasa lain". Metadata untuk mereka adalah standar, sehingga semua .NET bahasa yang mendukung tipe yang tidak ditandatangani akan beroperasi dengan baik.
Ben Voigt
5
Untuk membahas contoh spesifik Anda, saya tidak akan memiliki properti bernama Age. Saya akan memiliki properti bernama Ulang Tahun atau CreationTime atau apa pun, dan menghitung usia darinya.
Eric Lippert
2
"... tapi pola ini akan membuatku membuat banyak kelas dan akan menjadi sumber kebingungan" sebenarnya itu hal yang benar untuk dilakukan. Hanya mencari pola anti Obsesi Primitif yang terkenal .
Songo

Jawaban:

24

Desainer .NET Framework memilih integer bertanda 32 bit sebagai "nomor tujuan umum" mereka karena beberapa alasan:

  1. Ia dapat menangani angka negatif, terutama -1 (yang digunakan Kerangka untuk menunjukkan kondisi kesalahan; inilah sebabnya int yang ditandatangani digunakan di mana-mana pengindeksan diperlukan, meskipun angka negatif tidak berarti dalam konteks pengindeksan).
  2. Ini cukup besar untuk melayani sebagian besar tujuan, sementara cukup kecil untuk digunakan secara ekonomis hampir di mana saja.

Alasan untuk menggunakan int unsigned bukan keterbacaan; itu memiliki kemampuan untuk mendapatkan matematika yang hanya disediakan oleh int tanpa tanda tangan.

Klausa penjaga, validasi, dan prasyarat kontrak adalah cara yang bisa diterima untuk memastikan rentang angka yang valid. Jarang rentang numerik dunia nyata sesuai dengan angka antara nol dan 2 32 -1 (atau apa pun rentang numerik asli dari tipe numerik yang Anda pilih), jadi menggunakan a uintuntuk membatasi kontrak antarmuka Anda ke angka positif adalah jenis di samping intinya.

Robert Harvey
sumber
2
Jawaban bagus! Juga mungkin ada beberapa kasus di mana int unsigned sebenarnya dapat secara tidak sengaja menghasilkan lebih banyak kesalahan (meskipun mungkin yang segera terlihat, tetapi agak membingungkan) - bayangkan pengulangan secara terbalik dengan counter int unsigned karena beberapa ukuran bilangan bulat: for (uint j=some_size-1; j >= 0; --j)- whoops ( tidak yakin apakah ini merupakan masalah dalam C #)! Saya menemukan masalah ini dalam kode sebelum yang mencoba menggunakan int unsigned di sisi C sebanyak mungkin - dan kami akhirnya mengubahnya hanya untuk mendukung intnanti, dan hidup kami jauh lebih mudah dengan lebih sedikit peringatan kompiler juga.
14
"Jarang rentang numerik dunia nyata sesuai dengan angka antara nol dan 2 ^ 32-1." Dalam pengalaman saya, jika Anda akan membutuhkan angka yang lebih besar dari 2 ^ 31, Anda kemungkinan besar akan berakhir juga membutuhkan angka yang lebih besar dari 2 ^ 32, jadi Anda mungkin juga cukup naik ke (menandatangani) int64 di titik itu.
Mason Wheeler
3
@ Panzercrisis: Itu agak parah. Mungkin akan lebih akurat untuk mengatakan "Gunakan intsebagian besar waktu karena itu adalah konvensi yang mapan, dan itulah yang diharapkan kebanyakan orang untuk melihat digunakan secara rutin. Gunakan uintketika Anda membutuhkan kapabilitas khusus a uint." Ingat, perancang Kerangka memutuskan untuk mengikuti konvensi ini secara luas, sehingga Anda bahkan tidak dapat menggunakannya uintdalam banyak konteks Kerangka (tidak kompatibel dengan jenis).
Robert Harvey
2
@Panzercrisis Ini mungkin ungkapan yang terlalu kuat; tapi saya tidak yakin apakah saya pernah menggunakan tipe unsigned di C # kecuali ketika saya menelepon ke win32 apis (di mana konvensi adalah bahwa konstanta / flags / etc adalah unsigned).
Dan Neely
4
Memang sangat jarang. Satu-satunya waktu saya menggunakan int unsigned adalah skenario bit-twiddling.
Robert Harvey
8

Secara umum, Anda harus selalu menggunakan tipe data yang paling spesifik untuk data Anda.

Jika, misalnya, Anda menggunakan Entity Framework untuk menarik data dari database, EF akan secara otomatis menggunakan tipe data yang paling dekat dengan yang digunakan dalam database.

Ada dua masalah dengan ini di C #.
Pertama, sebagian besar pengembang C # hanya menggunakan int, untuk mewakili bilangan bulat (kecuali ada alasan untuk menggunakannya long). Ini berarti bahwa pengembang lain tidak akan berpikir untuk memeriksa tipe data, sehingga mereka akan mendapatkan kesalahan limpahan yang disebutkan di atas. Kedua, dan isu yang lebih penting, adalah / adalah bahwa NET operator aritmatika asli hanya didukung int, uint, long, ulong, float, ganda, dan decimal*. Ini masih terjadi sampai sekarang (lihat bagian 7.8.4 dalam spesifikasi bahasa C # 5.0 ). Anda dapat mengujinya sendiri menggunakan kode berikut:

byte a, b;
a = 1;
b = 2;
var c = a - b;      //In visual studio, hover over "var" and the tip will indicate the data type, or you can get the value from cName below.
string cName = c.GetType().Namespace + '.' + c.GetType().Name;

Hasil dari byte- byteadalah int( System.Int32).

Kedua masalah ini memunculkan praktik "hanya gunakan int untuk bilangan bulat" yang sangat umum.

Jadi untuk menjawab pertanyaan Anda, dalam C # biasanya merupakan ide yang baik untuk tetap intkecuali:

  • Pembuat kode otomatis menggunakan nilai yang berbeda (seperti Entity Framework).
  • Semua pengembang lain di proyek menyadari bahwa Anda menggunakan tipe data yang kurang umum (sertakan komentar yang menunjukkan bahwa Anda menggunakan tipe data dan alasannya).
  • Tipe data yang kurang umum sudah umum digunakan dalam proyek.
  • Program ini membutuhkan manfaat dari tipe data yang kurang umum (Anda memiliki 100 juta dari ini yang perlu Anda simpan dalam RAM, sehingga perbedaan antara a bytedan a intatau a intdan a longsangat penting, atau perbedaan aritmatika dari unsigned sudah disebutkan sebelumnya).

Jika Anda perlu melakukan perhitungan matematika pada data, patuhi jenis yang umum.
Ingat, Anda dapat melakukan cast dari satu tipe ke tipe lainnya. Ini bisa kurang efisien dari sudut pandang CPU, jadi Anda mungkin lebih baik dengan salah satu dari 7 tipe umum, tetapi ini merupakan opsi jika diperlukan.

Pencacahan ( enum) adalah salah satu pengecualian pribadi saya untuk pedoman di atas. Jika saya hanya memiliki beberapa opsi, saya akan menentukan enum sebagai byte atau pendek. Jika saya membutuhkan bit terakhir dalam enum yang ditandai, saya akan menentukan jenisnya uintsehingga saya dapat menggunakan hex untuk mengatur nilai untuk flag.

Jika Anda menggunakan properti dengan kode pembatasan nilai, pastikan untuk menjelaskan dalam tag ringkasan batasan apa yang ada dan mengapa.

* Alias ​​C # digunakan sebagai ganti nama .NET seperti System.Int32karena ini adalah pertanyaan C #.

Catatan: ada blog atau artikel dari pengembang .NET (yang tidak dapat saya temukan), yang menunjukkan terbatasnya fungsi aritmatika dan beberapa alasan mengapa mereka tidak khawatir tentang hal itu. Seingat saya, mereka mengindikasikan bahwa mereka tidak punya rencana untuk menambahkan dukungan untuk tipe data lainnya.

Catatan: Java tidak mendukung tipe data yang tidak ditandatangani dan sebelumnya tidak memiliki dukungan untuk angka bulat 8 atau 16 bit. Karena banyak pengembang C # berasal dari latar belakang Java atau diperlukan untuk bekerja dalam kedua bahasa, keterbatasan satu bahasa terkadang secara artifisial dipaksakan pada yang lain.

Berbilah
sumber
Aturan umum saya adalah, "gunakan int, kecuali Anda tidak bisa".
PerryC
@PerryC Saya percaya itu adalah konvensi yang paling umum. Inti dari jawaban saya adalah untuk memberikan konvensi yang lebih lengkap yang memungkinkan Anda untuk menggunakan fitur bahasa.
Dipotong
6

Anda terutama perlu mengetahui dua hal: data yang Anda wakili, dan setiap langkah perantara dalam perhitungan Anda.

Masuk akal untuk memiliki usia unsigned int, karena kita biasanya tidak mempertimbangkan usia negatif. Tapi kemudian Anda menyebutkan mengurangi satu usia dari yang lain. Jika kita secara buta mengurangi satu bilangan bulat dari bilangan bulat lainnya, maka sangat mungkin untuk berakhir dengan angka negatif, bahkan jika kita sebelumnya sepakat bahwa usia negatif tidak masuk akal. Jadi dalam hal ini Anda ingin perhitungan Anda dilakukan dengan integer yang ditandatangani.

Berkenaan dengan apakah nilai yang tidak ditandatangani buruk atau tidak, saya akan mengatakan bahwa itu adalah generalisasi besar untuk mengatakan nilai yang tidak ditandatangani itu buruk. Java tidak memiliki nilai yang tidak ditandatangani, seperti yang Anda sebutkan, dan itu selalu mengganggu saya. A bytedapat memiliki nilai dari 0-255 atau 0x00-0xFF. Tetapi jika Anda ingin instantiate byte yang lebih besar dari 127 (0x7F), Anda harus menuliskannya sebagai angka negatif atau melemparkan integer ke byte. Anda berakhir dengan kode yang terlihat seperti ini:

byte a = 0x80; // Won't compile!
byte b = (byte) 0x80;
byte c = -128; // Equal to b

Di atas mengganggu saya tanpa akhir. Saya tidak diizinkan memiliki byte yang memiliki nilai 197, meskipun itu adalah nilai yang sangat valid untuk kebanyakan orang waras yang berurusan dengan byte. Saya dapat menggunakan integer atau saya dapat menemukan nilai negatif (197 == -59 dalam kasus ini). Pertimbangkan juga ini:

byte a = 70;
byte b = 80;
byte c = a + b; // c == -106

Jadi seperti yang Anda lihat, menambahkan dua byte dengan nilai yang valid, dan berakhir dengan byte dengan nilai yang valid, akhirnya mengubah tandanya. Bukan hanya itu tetapi tidak segera jelas bahwa 70 + 80 == -106. Secara teknis ini adalah overflow, tetapi dalam pikiran saya (sebagai manusia) satu byte seharusnya tidak melebihi nilai di bawah 0xFF. Ketika saya melakukan bit aritmatika di atas kertas, saya tidak menganggap bit ke-8 sebagai bit tanda.

Saya bekerja dengan banyak bilangan bulat pada tingkat bit, dan memiliki semua yang ditandatangani biasanya membuat semuanya kurang intuitif dan lebih sulit untuk ditangani, karena Anda harus ingat bahwa menggeser angka negatif memberi Anda yang baru 1di angka Anda. Sedangkan menggeser bilangan bulat yang tidak ditandatangani tidak pernah melakukan itu. Sebagai contoh:

signed byte b = 0b10000000;
b = b >> 1; // b == 0b1100 0000
b = b & 0x7F;// b == 0b0100 0000

unsigned byte b = 0b10000000;
b = b >> 1; // b == 0b0100 0000;

Itu hanya menambahkan langkah-langkah tambahan yang saya rasa tidak perlu.

Sementara saya menggunakan di byteatas, hal yang sama berlaku untuk bilangan bulat 32-bit dan 64-bit. Tidak memiliki unsignedmelumpuhkan dan itu mengejutkan saya bahwa ada bahasa tingkat tinggi seperti Java yang tidak memungkinkan mereka sama sekali. Tetapi bagi kebanyakan orang ini adalah masalah, karena banyak programmer tidak berurusan dengan aritmatika tingkat bit.

Pada akhirnya, ini berguna untuk menggunakan bilangan bulat yang tidak ditandatangani jika Anda menganggapnya sebagai bit, dan itu berguna untuk menggunakan bilangan bulat yang ditandatangani ketika Anda menganggapnya sebagai angka.

Shaz
sumber
7
Saya berbagi rasa frustrasi Anda tentang bahasa tanpa tipe integral yang tidak ditandatangani (terutama untuk byte) tetapi saya khawatir ini bukan jawaban langsung untuk pertanyaan yang diajukan di sini. Mungkin Anda bisa menambahkan kesimpulan, yang saya percaya, bisa berupa: "Gunakan bilangan bulat yang tidak ditandatangani jika Anda menganggap nilainya sebagai bit dan bilangan bulat yang ditandatangani jika Anda menganggapnya sebagai angka."
5gon12eder
1
itu yang saya katakan di komentar di atas. senang melihat orang lain berpikir dengan cara yang sama.
robert bristow-johnson