Integer pada Zig selalu memiliki lebar yang dinyatakan secara eksplisit, misalnya u8, i32, atau u64. Lebar tersebut menentukan rentang nilai yang dapat disimpan serta perilaku aritmetika ketika hasil operasi melewatinya. Topik ini membahas pemilihan tipe integer, perilaku overflow pada aritmetika biasa, serta operator pembungkusan (wrapping) dan penjenuhan (saturating) yang membuat keputusan aritmetika tampak jelas di dalam kode. Contoh-contohnya dijalankan dengan Zig 0.17.0, sehingga setiap keluaran dapat direproduksi.
Konsep#
Memilih lebar integer#
Lebar integer merupakan bagian dari nama tipenya. Tipe tanpa tanda (unsigned) memakai awalan u dan hanya menampung nilai nol ke atas, sedangkan tipe bertanda (signed) memakai awalan i dan menampung nilai negatif maupun positif. Angka di belakang awalan menyatakan lebar dalam bit, sehingga u8 menampung nilai 0 sampai 255 dan i8 menampung nilai -128 sampai 127. Lebar lain yang tersedia adalah 16, 32, 64, dan 128 bit, ditambah usize serta isize yang lebarnya mengikuti arsitektur mesin sasaran. Lebar nol juga sah sebagai u0 yang hanya dapat menyimpan nilai 0.
| Tipe | Rentang nilai | Pemakaian yang lazim |
|---|---|---|
u8 | 0 sampai 255 | data byte, hitungan kecil |
i32 | sekitar -2,1 miliar sampai 2,1 miliar | hitungan umum |
u32 | 0 sampai sekitar 4,3 miliar | hitungan tanpa nilai negatif |
u64 | 0 sampai sekitar 1,8 × 10^19 | ukuran besar dan penanda waktu |
usize | mengikuti arsitektur | indeks dan panjang koleksi |
Pilihlah lebar terkecil yang masih menampung seluruh nilai yang mungkin terjadi pada data. Penghitung hari dalam sebulan cukup memakai u8, sedangkan penanda waktu dalam nanodetik memerlukan u64. Indeks perulangan dan panjang koleksi memakai usize karena tipe itulah yang digunakan pustaka standar untuk operasi semacam itu. Konsistensi pemilihan lebar menghilangkan konversi yang tidak perlu dan membuat niat kode terbaca dari deklarasinya.
Aritmetika biasa dan panic overflow#
Operator aritmetika biasa, yaitu +, -, dan *, memeriksa apakah hasil operasi masih muat pada tipe sasarannya. Apabila hasil melewati rentang tipe, kejadian itu disebut overflow, dan program berhenti dengan panic pada mode Debug. Panic adalah penghentian program yang disengaja oleh runtime Zig, disertai pesan penyebab dan jejak tumpukan (stack trace). Perilaku ini menjadikan kekeliruan aritmetika terdeteksi sejak pengujian, bukan tersembunyi sebagai nilai yang salah. Ia juga berbeda dari bahasa yang membungkus nilai secara diam-diam, karena hasil yang salah tidak pernah dianggap sebagai perilaku wajar.
Contoh berikut menaikkan penghitung u8 yang sudah berada pada nilai maksimumnya.
// tidak-dijalankan
var penghitung: u8 = 255;
penghitung = penghitung + 1; // 256 tidak muat pada u8Ketika contoh tersebut dijalankan, program berhenti dengan pesan panic: integer overflow beserta jejak tumpukan yang menunjuk baris penjumlahannya, dan proses berakhir dengan kode keluar bukan nol. Nomor thread dan alamat memori pada jejak tumpukan berubah dari satu eksekusi ke eksekusi lain, sehingga keluaran panic tidak dikutip secara persis di sini. Bagian yang stabil dan penting adalah penyebabnya, yaitu integer overflow, beserta baris kode pemicunya.
Contoh Lengkap#
Operator wrapping pada odometer#
Pembungkusan (wrapping) melanjutkan hitungan dari ujung rentang yang berlawanan, sebagaimana odometer kendaraan yang kembali ke nol setelah mencapai nilai maksimumnya. Operator pembungkusannya adalah +%, -%, *%, dan <<%. Operator tersebut menerapkan aritmetika modulo terhadap lebar tipe, sehingga 255 +% 1 pada u8 menghasilkan 0. Pemakaian operator ini menyatakan bahwa pembungkusan memang diinginkan, bukan kecelakaan aritmetika.
Program berikut memutar odometer u8 tiga kali mulai dari nilai 253.
const std = @import("std");
pub fn main() void {
var odometer: u8 = 253;
odometer +%= 1;
std.debug.print("Setelah satu kilometer: {d}\n", .{odometer});
odometer +%= 1;
std.debug.print("Setelah dua kilometer: {d}\n", .{odometer});
odometer +%= 1;
std.debug.print("Setelah tiga kilometer: {d}\n", .{odometer});
}Setelah satu kilometer: 254
Setelah dua kilometer: 255
Setelah tiga kilometer: 0Jalankan berkas tersebut dengan perintah berikut.
# menjalankan program odometer
zig run odometer.zigSetelah satu kilometer: 254
Setelah dua kilometer: 255
Setelah tiga kilometer: 0Nilai melampaui 255 pada penambahan ketiga dan kembali ke 0 tanpa penghentian program. Penambahan yang sama dengan operator + akan memicu panic seperti pada bagian sebelumnya. Dengan demikian, perbedaan + dan +% semata-mata soal niat yang dinyatakan secara eksplisit di dalam kode.
Pemakaian yang wajar mencakup penghitung yang berputar dalam siklus tertutup, misalnya penanda giliran pada antrean tiket loket dengan jumlah langkah yang tetap. Hindari operator ini untuk besaran yang benar-benar tumbuh, misalnya akumulasi ukuran berkas dalam arsip, karena nilai yang dibungkus membuat hasil perhitungan keliru secara senyap. Apabila ragu, mulailah dengan operator biasa dan biarkan panic pada mode Debug menunjukkan titik yang perlu diputuskan.
Literal comptime_int tidak membungkus#
Sebelum diberi tipe konkret, setiap literal integer bertipe comptime_int, yaitu tipe waktu kompilasi yang lebarnya tidak dibatasi. Karena tidak memiliki lebar, comptime_int tidak memiliki batas untuk dibungkus. Operator +% yang diterapkan langsung pada literal hanya melakukan penjumlahan biasa, dan hasilnya tetap akurat tanpa kehilangan nilai.
// tidak-dijalankan
const hasil_literal = 255 +% 2; // 257, comptime_int tidak membungkus
var penghitung: u8 = 255;
penghitung +%= 2; // 1, u8 membungkus pada 255Nilai hasil_literal pada contoh adalah 257, sedangkan penghitung berakhir pada 1. Perbedaan itu muncul karena penghitung memiliki lebar u8 sejak deklarasinya, sedangkan literal tidak pernah diberi lebar. Pastikan operator pembungkusan diterapkan pada nilai yang sudah bertipe konkret, misalnya melalui variabel atau parameter fungsi, agar semantik pembungkusannya benar-benar berlaku. Aturan yang sama berlaku bagi operator penjenuhan pada literal.
Perilaku ini tidak terbatas pada operator pembungkusan. Literal 9223372036854775808 yang melebihi kapasitas i64 tetap sah sebagai comptime_int, dan kekeliruannya baru muncul ketika literal itu diikat pada tipe konkret yang tidak muat. Pengetahuan ini menjelaskan mengapa perhitungan antar literal pada waktu kompilasi tidak pernah meluap, sedangkan perhitungan runtime mengikuti lebar tipe operannya.
Operator saturating pada batas kapasitas#
Penjenuhan (saturating) menahan hasil pada batas rentang alih-alih melanjutkannya dari ujung yang berlawanan. Operatornya adalah +|, -|, dan *|, sehingga penambahan pada nilai maksimum tetap menghasilkan nilai maksimum, dan pengurangan pada nol tetap menghasilkan nol. Operator ini cocok untuk besaran yang memiliki batas fisik, misalnya kapasitas tempat duduk atau sisa stok gudang.
Program berikut menghitung pengurangan stok yang melewati nol dan penambahan kursi yang melewati nilai maksimum u8.
const std = @import("std");
pub fn main() void {
var stok: u8 = 4;
stok = stok -| 12;
std.debug.print("Stok setelah dikurangi: {d}\n", .{stok});
var kursi: u8 = 250;
kursi = kursi +| 12;
std.debug.print("Kursi setelah ditambah: {d}\n", .{kursi});
}Stok setelah dikurangi: 0
Kursi setelah ditambah: 255Pengurangan 4 -| 12 berhenti pada 0 karena stok tidak mungkin negatif, sedangkan penambahan 250 +| 12 berhenti pada 255 karena itulah nilai maksimum u8. Kedua hasil tersebut adalah nilai batas yang sah secara bisnis, bukan nilai yang meluap menjadi kecil. Apabila perilaku yang diinginkan adalah pembungkusan, gunakan -% dan +%, dan pertahankan satu semantik yang konsisten untuk satu besaran.
Operator penjenuhan juga menghilangkan kebutuhan percabangan penjaga, yaitu pola if (sisa < 0) yang biasa ditulis manual sebelum penugasan. Kode menjadi lebih pendek, dan niatnya terbaca langsung dari operator yang dipilih. Untuk besaran yang batasnya bukan 0 atau nilai maksimum tipe, percabangan tetap diperlukan karena operator ini hanya menahan hasil pada kedua ujung rentang.
Perilaku pada mode Debug dan mode rilis#
Zig mengompilasi program dalam beberapa mode build, dan mode bawaan zig run adalah Debug. Pada mode ini, setiap operasi aritmetika yang berpotensi meluap disertai pemeriksaan runtime, sehingga contoh pada bagian overflow berhenti dengan panic. Pada mode rilis seperti ReleaseFast, pemeriksaan tersebut dapat digugurkan demi kecepatan eksekusi, dan program tidak lagi menjamin deteksi overflow. Karena itu, pilihan operator menjadi bagian dari niat program, sedangkan pemeriksaan Debug hanya jaring pengaman selama pengembangan berlangsung.
Mode build dipilih melalui flag pada perintah kompilasi, misalnya -O ReleaseFast untuk kecepatan maksimum. Selama tahap pengembangan, mode Debug sebaiknya dipertahankan agar setiap peluapan terdeteksi dini. Ketika pengukuran menunjukkan perlunya mode rilis, tinjau ulang seluruh aritmetika yang melibatkan nilai mendekati batas rentangnya, lalu pastikan pilihan operatornya sudah eksplisit.
Kesalahan umum#
Kesalahan pertama berupa menghitung luapan sebagai perilaku yang pasti. Operator + yang meluap menghasilkan panic pada mode Debug dan ReleaseSafe, tetapi perilaku tidak terdefinisi pada mode rilis tanpa pemeriksaan. Uji batas selalu dilakukan pada mode Debug, dan operator +% atau +| ditulis eksplisit bila pembungkus atau penjenuhan memang dikehendaki.
Kesalahan kedua berupa memakai literal besar pada tipe kecil tanpa menempatkan pengecekannya. Literal 256 yang ditempatkan pada u8 ditolak pada waktu kompilasi, sedangkan hasil perhitungan runtime yang meluap baru terlihat saat berjalan. Memisahkan konstanta waktu kompilasi dan hasil runtime membuat kedua pemeriksaan bekerja pada tempat yang tepat.
Ringkasan#
- Lebar integer merupakan bagian dari nama tipe, misalnya
u8dani32, sehingga rentang nilai setiap variabel terbaca langsung dari deklarasinya. - Aritmetika biasa memicu panic ketika hasil meluap pada mode Debug, operator
+%,-%,*%membungkus hasil, dan operator+|,-|,*|menjenuhkan hasil pada batas rentang. - Literal integer bertipe
comptime_inttanpa lebar, sehingga operator pembungkusan pada literal tidak membungkus dan perlu diterapkan pada nilai bertipe konkret. - Pemeriksaan overflow pada mode rilis dapat hilang, sehingga pilihan operator aritmetika harus disengaja dan tidak bergantung pada panic.
Referensi#
- Integers — Zig Language Reference — Daftar tipe integer beserta rentang dan perilaku luapannya.
- Operators — Zig Language Reference — Tabel operator aritmetika termasuk varian pembungkusan dan penjenuhan.