Apa praktik terbaik terkait int yang tidak ditandatangani?

43

Saya menggunakan int unsigned di mana-mana, dan saya tidak yakin apakah saya harus melakukannya. Ini bisa dari basis data kolom id kunci utama ke penghitung, dll. Jika suatu angka tidak boleh negatif, maka saya akan selalu menggunakan int yang tidak ditandatangani.

Namun saya perhatikan dari kode lain bahwa tidak ada orang lain yang melakukan hal ini. Apakah ada sesuatu yang penting yang saya abaikan?

Sunting: Karena pertanyaan ini, saya juga memperhatikan bahwa di C, mengembalikan nilai negatif untuk kesalahan lebih umum daripada melemparkan pengecualian seperti pada C ++.

wting
sumber
26
Hanya hati-hati untuk for(unsigned int n = 10; n >= 0; n --)(loop tak terhingga)
Chris Burt-Brown
3
Dalam C dan C ++, int unsigned telah secara tepat mendefinisikan perilaku overflow (modulo2 ^ n). Int yang ditandatangani tidak. Pengoptimal semakin mengeksploitasi perilaku overflow yang tidak terdefinisi, yang mengarah ke hasil yang mengejutkan dalam beberapa kasus.
Steve314
2
Pertanyaan bagus! Saya juga pernah tergoda untuk menggunakan rentang t uints tetapi menemukan bahwa risiko / ketidaknyamanan melebihi manfaat / kenyamanan. Sebagian besar perpustakaan, seperti yang Anda katakan, menerima int reguler di mana Anda tidak akan melakukannya. Ini membuatnya sulit untuk dikerjakan, tetapi juga menimbulkan pertanyaan: apakah itu layak? Dalam praktiknya (dengan anggapan bahwa Anda tidak melakukan hal-hal dengan cara yang bodoh), Anda jarang memiliki nilai -218 di mana yang positif diharapkan. -218 itu pasti datang dari suatu tempat, kan? dan Anda dapat melacak asalnya. Jarang terjadi. Memanfaatkan pernyataan, pengecualian, kontrak kode untuk membantu Anda.
Pekerjaan
@ William Ting: Jika ini hanya tentang C / C ++, Anda harus menambahkan tag yang sesuai untuk pertanyaan Anda.
CesarGon
2
@ Chris: Seberapa penting masalah loop tak terbatas dalam kenyataan? Maksud saya, jika ia berhasil dirilis, maka kodenya jelas tidak diuji. Bahkan ketika Anda perlu beberapa jam untuk men-debug-nya saat pertama kali Anda membuat kesalahan ini, kedua kalinya Anda harus tahu apa yang harus dicari terlebih dahulu ketika kode Anda tidak berhenti berulang.
Amankan

Jawaban:

28

Apakah ada sesuatu yang penting yang saya abaikan?

Ketika perhitungan melibatkan tipe yang ditandatangani dan tidak ditandatangani serta ukuran yang berbeda, aturan untuk promosi tipe dapat menjadi rumit dan mengarah pada perilaku yang tidak terduga .

Saya percaya ini adalah alasan utama mengapa Java menghilangkan tipe int yang tidak ditandatangani.

Michael Borgwardt
sumber
3
Solusi lain akan mengharuskan Anda untuk secara manual memasukkan nomor Anda yang sesuai. Inilah yang tampaknya dilakukan oleh Go (saya hanya bermain-main dengannya sedikit saja), dan saya lebih menyukainya daripada pendekatan Java.
Tikhon Jelvis
2
Itu adalah alasan yang bagus untuk Java untuk tidak memasukkan jenis unsigned 64-bit, dan mungkin alasan yang baik untuk tidak memasukkan tipe unsigned 32-bit [meskipun semantik menambahkan nilai 32-bit yang ditandatangani dan tidak ditandatangani tidak akan sulit-- operasi semacam itu seharusnya hanya menghasilkan hasil yang ditandatangani 64-bit]. Namun, tipe yang tidak ditandatangani lebih kecil daripada tidak intakan menimbulkan kesulitan seperti itu (karena perhitungan apa pun akan dipromosikan ke int); Saya tidak ada yang baik untuk dikatakan tentang kurangnya tipe byte yang tidak ditandatangani.
supercat
17

Saya pikir Michael memiliki poin yang valid, tetapi IMO alasan mengapa semua orang menggunakan int sepanjang waktu (terutama di for (int i = 0; i < max, i++) adalah bahwa kami mempelajarinya seperti itu. Ketika setiap contoh dalam buku ' bagaimana belajar pemrograman ' digunakan intdalam satu forlingkaran, sangat sedikit yang akan mempertanyakan praktik itu.

Alasan lainnya intadalah 25% lebih pendek dari itu uint, dan kita semua malas ... ;-)

Treb
sumber
2
Saya setuju dengan masalah pendidikan. Kebanyakan orang sepertinya tidak pernah mempertanyakan apa yang mereka baca: Jika itu ada di buku, itu tidak mungkin salah, kan?
Matthieu M.
1
Itu juga mungkin mengapa semua orang menggunakan postfix ++ketika menambahkan, meskipun fakta bahwa perilakunya yang jarang diperlukan dan bahkan mungkin mengarah pada pengulangan yang tidak berguna atas salinan jika indeks loop adalah iterator atau tipe non-fundamental lainnya (atau kompiler sangat padat) .
underscore_d
Hanya saja, jangan lakukan sesuatu seperti "for (uint i = 10; i> = 0; --i)". Menggunakan hanya ints untuk variabel loop menghindari kemungkinan ini.
David Thornley
11

Pengkodean informasi jangkauan ke dalam tipe adalah A Good Thing. Ini berlaku dengan menggunakan angka yang masuk akal pada waktu kompilasi.

Banyak arsitektur tampaknya memiliki instruksi khusus untuk menangani int-> floatkonversi. Konversi dari unsignedbisa lebih lambat (sedikit) .

Benjamin Bannier
sumber
8

Mencampur jenis yang ditandatangani dan tidak ditandatangani dapat membawa Anda ke dunia kesakitan. Dan Anda tidak dapat menggunakan semua tipe yang tidak ditandatangani karena Anda akan menemukan hal-hal yang memiliki rentang valid yang menyertakan angka negatif atau membutuhkan nilai untuk menunjukkan kesalahan dan -1 yang paling alami. Jadi hasil akhirnya adalah banyak programmer menggunakan semua tipe integer yang telah ditandatangani.

David Schwartz
sumber
1
Mungkin lebih baik tidak mencampurkan nilai yang valid dengan indikasi kesalahan dalam variabel yang sama dan menggunakan variabel terpisah untuk ini. Memang, pustaka standar C tidak memberikan contoh yang baik di sini.
Amankan
7

Bagi saya jenis banyak tentang komunikasi. Dengan menggunakan int yang tidak ditandatangani, Anda memberi tahu saya bahwa nilai yang ditandatangani bukan nilai yang valid. Ini memungkinkan saya untuk menambahkan beberapa informasi saat membaca kode Anda di samping nama variabel. Idealnya saya tipe non anonim akan memberi tahu saya lebih banyak, tetapi itu memberi saya lebih banyak informasi daripada jika Anda telah menggunakan int di mana-mana.

Sayangnya tidak semua orang sangat sadar tentang apa yang dikomunikasikan kode mereka, dan itu mungkin alasan Anda melihat int di mana-mana meskipun nilai setidaknya tidak ditandatangani.

daramarak
sumber
4
Tetapi saya mungkin ingin membatasi nilai saya hanya untuk 1 hingga 12 bulan. Apakah saya menggunakan tipe lain untuk itu? Bagaimana dengan sebulan? Beberapa bahasa sebenarnya memungkinkan membatasi nilai seperti itu. Lainnya, seperti .Net / C # memberikan Kontrak Kode. Tentu, bilangan bulat non-negatif terjadi agak sering, tetapi sebagian besar bahasa yang mendukung jenis ini tidak mendukung pembatasan lebih lanjut. Jadi, haruskah seseorang menggunakan campuran uints dan pengecekan error, atau hanya melakukan semuanya melalui pengecekan error? Sebagian besar perpustakaan tidak meminta uint di mana masuk akal untuk menggunakannya, maka menggunakan satu dan casting dapat merepotkan.
Pekerjaan
@ Job saya akan mengatakan Anda harus menggunakan semacam kompiler / interpreter pembatasan yang diberlakukan pada bulan Anda. Ini mungkin memberi Anda beberapa pelat ketel untuk disiapkan, tetapi untuk masa depan Anda memiliki batasan yang dipaksakan yang mencegah kesalahan dan berkomunikasi lebih jelas tentang apa yang Anda harapkan. Mencegah kesalahan dan mengurangi komunikasi jauh lebih penting daripada ketidaknyamanan saat menerapkan.
daramarak
1
"Saya mungkin ingin membatasi nilai saya selama satu bulan menjadi 1 hingga 12 saja" Jika Anda memiliki seperangkat nilai yang terbatas seperti bulan, Anda harus menggunakan jenis enumerasi, bukan bilangan bulat mentah.
Josh Caswell
6

Saya menggunakan unsigned intC ++ untuk indeks array, sebagian besar, dan untuk setiap penghitung yang dimulai dari 0. Saya pikir itu baik untuk mengatakan secara eksplisit "variabel ini tidak boleh negatif".

quant_dev
sumber
14
Anda mungkin harus menggunakan size_t untuk ini di c ++
JohnB
2
Saya tahu, saya tidak bisa diganggu.
quant_dev
3

Anda harus peduli tentang ini ketika Anda berurusan dengan integer yang mungkin benar-benar mendekati atau melebihi batas int yang ditandatangani. Karena maksimum positif integer 32 bit adalah 2.147.483.647 maka Anda harus menggunakan int yang tidak ditandatangani jika Anda tahu itu akan a) tidak pernah negatif dan b) mungkin mencapai 2.147.483.648. Dalam kebanyakan kasus, termasuk kunci basis data dan penghitung, saya bahkan tidak akan pernah mendekati angka-angka seperti ini jadi saya tidak repot-repot mengkhawatirkan diri saya sendiri apakah bit tanda digunakan untuk nilai numerik atau untuk menunjukkan tanda.

Saya akan mengatakan: gunakan int kecuali Anda tahu Anda membutuhkan int unsigned.

Joel Etherton
sumber
2
Ketika bekerja dengan nilai yang dapat mencapai nilai maksimum, Anda harus mulai memeriksa operasi untuk bilangan bulat bilangan bulat, terlepas dari tanda. Pemeriksaan ini biasanya lebih mudah untuk tipe yang tidak ditandatangani, karena sebagian besar operasi memiliki hasil yang terdefinisi dengan baik tanpa perilaku yang ditentukan dan implementasi.
Amankan
3

Ini merupakan tradeoff antara kesederhanaan dan keandalan. Semakin banyak bug yang dapat ditangkap pada waktu kompilasi, semakin dapat diandalkan perangkat lunaknya. Orang dan organisasi yang berbeda berada pada titik yang berbeda di sepanjang spektrum itu.

Jika Anda pernah melakukan pemrograman keandalan tinggi di Ada, Anda bahkan menggunakan tipe berbeda untuk variabel seperti jarak berjalan kaki vs jarak dalam meter, dan kompiler menandainya jika Anda secara tidak sengaja menetapkan satu ke yang lain. Itu sempurna untuk memprogram rudal yang dipandu, tetapi pembunuhan berlebihan (yang dimaksudkan) jika Anda memvalidasi formulir web. Tidak ada yang salah dengan cara apa pun asalkan sesuai dengan persyaratan.

Karl Bielefeldt
sumber
2

Saya cenderung setuju dengan alasan Joel Etherton, tetapi sampai pada kesimpulan yang berlawanan. Cara saya melihatnya, bahkan jika Anda tahu bahwa angka tidak akan pernah mendekati batas jenis yang ditandatangani, jika Anda tahu bahwa angka negatif tidak akan terjadi, maka ada sangat sedikit alasan untuk menggunakan varian yang ditandatangani dari suatu jenis.

Untuk alasan yang sama mengapa saya miliki, dalam beberapa contoh pilih, digunakan BIGINT(integer 64 bit) daripada INTEGER(integer 32-bit) dalam tabel SQL Server. Probabilitas bahwa data akan mencapai batas 32-bit dalam jumlah waktu yang wajar adalah sangat kecil, tetapi jika itu terjadi, konsekuensi dalam beberapa situasi bisa sangat menghancurkan. Pastikan untuk memetakan jenis antar bahasa dengan benar, atau Anda akan berakhir dengan keanehan menarik di ujung jalan ...

Yang mengatakan, untuk beberapa hal, seperti nilai kunci primer basis data, ditandatangani atau tidak ditandatangani benar-benar tidak masalah, karena kecuali jika Anda secara manual memperbaiki data yang rusak atau sesuatu di sepanjang garis itu, Anda tidak akan pernah berurusan dengan nilai secara langsung; itu adalah pengidentifikasi, tidak lebih. Dalam kasus-kasus itu, konsistensi mungkin lebih penting daripada pemilihan ketepatan yang tepat. Jika tidak, Anda berakhir dengan beberapa kolom kunci asing yang ditandatangani dan yang lainnya tidak ditandatangani, tanpa pola yang jelas - atau keanehan yang menarik lagi.

sebuah CVn
sumber
Jika Anda bekerja dengan data yang diekstrak dari sistem SAP, saya sangat merekomendasikan BIGINT untuk bidang ID (seperti CustomerNumber, ArticleNumber dll). Selama tidak ada yang menggunakan string alfanumerik sebagai ID, itu ... sigh
Treb
1

Saya akan merekomendasikan bahwa di luar penyimpanan data yang dibatasi ruang dan konteks pertukaran data, seseorang harus menggunakan tipe yang ditandatangani. Dalam kebanyakan kasus di mana integer bertanda 32-bit akan terlalu kecil tetapi nilai unsigned 32-bit sudah cukup untuk hari ini, tidak akan lama sebelum nilai unsigned 32-bit juga tidak cukup besar.

Waktu utama seseorang harus menggunakan tipe yang tidak ditandatangani adalah ketika seseorang mengumpulkan nilai-nilai ganda menjadi yang lebih besar (misalnya, mengubah empat byte menjadi angka 32-bit) atau mendekomposisi nilai yang lebih besar menjadi yang lebih kecil (misalnya menyimpan angka 32-bit sebagai empat byte) ), atau ketika seseorang memiliki kuantitas yang diharapkan "berguling" secara berkala dan orang perlu menghadapinya (pikirkan meter utilitas perumahan; kebanyakan dari mereka memiliki angka yang cukup untuk memastikan bahwa mereka tidak akan mungkin berguling di antara pembacaan jika mereka dibaca tiga kali setahun, tetapi tidak cukup untuk memastikan mereka tidak terguling dalam masa manfaat meteran). Jenis yang tidak ditandatangani sering memiliki 'keanehan' yang cukup sehingga hanya boleh digunakan dalam kasus-kasus di mana semantik mereka diperlukan.

supercat
sumber
1
"Saya akan merekomendasikan [...] umumnya menggunakan tipe yang ditandatangani." Hm, Anda lupa menyebutkan kelebihan dari tipe yang ditandatangani dan hanya memberikan daftar kapan harus menggunakan tipe yang tidak ditandatangani. "Keanehan" ? Sementara sebagian besar operasi yang tidak ditandatangani memiliki perilaku dan hasil yang terdefinisi dengan baik, Anda memasukkan perilaku yang tidak terdefinisi dan implementasi yang didefinisikan saat menggunakan tipe yang ditandatangani (overflow, bit shift, ...). Anda memiliki definisi aneh tentang "keanehan" di sini.
Amankan
1
@Secure: "Keanehan" yang saya rujuk berkaitan dengan semantik operator perbandingan, terutama dalam operasi yang melibatkan tipe campuran bertanda tangan dan tidak bertanda tangan. Anda benar bahwa perilaku tipe yang ditandatangani tidak terdefinisi saat menggunakan nilai yang cukup besar untuk meluap, tetapi perilaku tipe yang tidak ditandatangani dapat mengejutkan bahkan ketika berhadapan dengan angka yang relatif kecil. Misalnya, (-3) + (1u) lebih besar dari -1. Juga, beberapa hubungan asosiatif matematika normal yang berlaku untuk angka tidak berlaku untuk unsigned. Misalnya, (ab)> c tidak menyiratkan (ac)> b.
supercat
1
@Secure: Walaupun benar bahwa seseorang tidak selalu dapat bergantung pada perilaku asosiatif dengan nomor bertanda "besar", perilaku tersebut berfungsi seperti yang diharapkan ketika berhadapan dengan angka yang "kecil" relatif terhadap domain bilangan bulat yang ditandatangani. Sebaliknya, non-asosiasi yang disebutkan di atas bermasalah dengan nilai yang tidak ditandai "2 3 1". Kebetulan, fakta bahwa perilaku yang ditandatangani memiliki perilaku yang tidak terdefinisi ketika digunakan di luar batas dapat memungkinkan peningkatan pembuatan kode pada beberapa platform saat menggunakan nilai yang lebih kecil dari ukuran kata asli.
supercat
1
Seandainya komentar ini ada di jawaban Anda sejak awal, alih-alih rekomendasi dan "panggilan nama" tanpa memberikan alasan, saya tidak akan berkomentar. ;) Meskipun saya masih tidak setuju dengan "keanehan" di sini, itu hanyalah definisi dari jenisnya. Gunakan alat yang tepat untuk pekerjaan yang diberikan, dan ketahui alatnya, tentu saja. Jenis yang tidak ditandatangani adalah alat yang salah ketika Anda membutuhkan +/- relasi. Ada alasan mengapa size_ttidak ditandatangani dan ptrdiff_tditandatangani.
Amankan
1
@Secure: Jika yang diinginkan seseorang adalah merepresentasikan urutan bit, tipe unsigned sangat bagus; Saya pikir kita sepakat di sana. Dan pada beberapa micros kecil, tipe unsigned bisa lebih efisien untuk jumlah numerik. Mereka juga berguna dalam kasus di mana delta mewakili jumlah numerik tetapi nilai aktual tidak (misalnya nomor urut TCP). Di sisi lain, setiap kali seseorang mengurangi nilai yang tidak ditandatangani, kita harus khawatir tentang kasing sudut bahkan ketika angkanya kecil; matematika seperti itu dengan nilai-nilai yang ditandatangani hanya menyajikan kasus sudut ketika jumlahnya besar.
supercat
1

Saya menggunakan int unsigned untuk membuat kode dan maksud saya lebih jelas. Satu hal yang saya lakukan untuk menjaga terhadap konversi tersirat yang tidak terduga ketika melakukan aritmatika dengan kedua tipe bertanda tangan dan tidak bertanda adalah menggunakan short unsigned (biasanya 2 byte) untuk variabel unsigned saya. Ini efektif karena beberapa alasan:

  • Ketika Anda melakukan aritmatika dengan variabel pendek yang tidak ditandatangani dan literal (yang bertipe int) atau variabel bertipe int, ini memastikan variabel yang tidak ditandatangani akan selalu dipromosikan ke int sebelum mengevaluasi ekspresi, karena int selalu memiliki peringkat lebih tinggi daripada pendek. . Ini menghindari perilaku tak terduga yang melakukan aritmatika dengan tipe bertanda tangan dan tidak bertanda, dengan asumsi hasil dari ekspresi cocok dengan int yang sudah ditandatangani tentunya.
  • Sebagian besar waktu, variabel unsigned yang Anda gunakan tidak akan melebihi nilai maks dari 2-byte pendek unsigned (65.535)

Prinsip umum adalah bahwa jenis variabel tidak bertanda tangan Anda harus memiliki peringkat lebih rendah dari jenis variabel yang ditandatangani untuk memastikan promosi ke jenis yang ditandatangani. Maka Anda tidak akan memiliki perilaku luapan yang tidak terduga. Jelas Anda tidak bisa memastikan ini setiap saat, tetapi (kebanyakan) seringkali layak untuk memastikan hal ini.

Sebagai contoh, baru-baru ini saya memiliki sesuatu untuk loop seperti ini:

const unsigned short cuint = 5;
for(unsigned short i=0; i<10; ++i)
{
    if((i-2)%cuint == 0)
    {
       //Do something
    }
}

Huruf '2' adalah tipe int. Jika saya adalah unsigned int bukannya short unsigned, maka dalam sub-ekspresi (i-2), 2 akan dipromosikan menjadi int unsigned (karena int unsigned memiliki prioritas lebih tinggi daripada int yang ditandatangani). Jika i = 0, maka sub-ekspresi sama dengan (0u-2u) = beberapa nilai besar karena melimpah. Gagasan yang sama dengan i = 1. Namun, karena saya adalah kependekan dari unsigned, maka dipromosikan ke jenis yang sama dengan literal '2', yang ditandatangani int, dan semuanya berfungsi dengan baik.

Untuk keamanan tambahan: dalam kasus yang jarang di mana arsitektur yang Anda laksanakan pada penyebab int menjadi 2 byte, ini dapat menyebabkan kedua operan dalam ekspresi aritmatika dipromosikan menjadi int tidak bertanda dalam kasus di mana variabel pendek yang tidak ditandai tidak cocok ke dalam int 2-byte yang ditandatangani, yang terakhir memiliki nilai maksimum 32.767 <65.535. (Lihat https://stackoverflow.com/questions/17832815/c-implicit-conversion-signed-unsigned untuk lebih jelasnya). Untuk mencegah hal ini, Anda cukup menambahkan static_assert ke program Anda sebagai berikut:

static_assert(sizeof(int) == 4, "int must be 4 bytes");

dan itu tidak akan dikompilasi pada arsitektur di mana int adalah 2 byte.

AdmiralAdama
sumber