Membaca angka dari pengguna tampak sederhana, tetapi tanpa validasi yang cermat, program bisa crash, menghasilkan nilai absurd, atau bahkan membuka celah keamanan. Fungsi scanf("%d", ...) memang membaca integer, tetapi fungsi tersebut tidak memberitahu kita apakah input valid penuh atau ada karakter sampah tersisa. Pada chapter ini kita akan mempelajari pendekatan yang lebih kokoh: membaca seluruh baris dengan fgets, lalu mengonversinya memakai strtol dan strtod yang melaporkan kegagalan secara eksplisit. Strategi ini menjadi dasar penanganan input yang akan kita kembangkan lebih jauh di chapter Penanganan Input Tidak Valid.
Membaca Integer dengan strtol #
Fungsi strtol mengonversi string menjadi bilangan long. Fungsi ini menerima tiga argumen: string sumber, pointer ke pointer endptr (untuk melaporkan di mana parsing berhenti), dan basis bilangan (10 untuk desimal, 16 untuk heksadesimal, 0 untuk auto-detect). Dengan memeriksa endptr, kita bisa tahu apakah seluruh string berhasil dikonversi atau ada karakter yang tidak dikenal.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char input[64];
printf("Masukkan bilangan bulat: ");
if (fgets(input, sizeof(input), stdin) != NULL) {
char *endptr;
long nilai = strtol(input, &endptr, 10);
if (endptr == input) {
printf("Error: tidak ada angka terbaca.\n");
} else if (*endptr != '\0' && *endptr != '\n') {
printf("Warning: karakter tersisa: '%s'\n", endptr);
printf("Nilai terbaca: %ld\n", nilai);
} else {
printf("Nilai terbaca: %ld\n", nilai);
}
}
return 0;
}Output yang Dihasilkan untuk berbagai input:
$ echo "42" | ./program
Masukkan bilangan bulat: Nilai terbaca: 42
$ echo "17abc" | ./program
Masukkan bilangan bulat: Warning: karakter tersisa: 'abc
'
Nilai terbaca: 17
$ echo "bukan_angka" | ./program
Masukkan bilangan bulat: Error: tidak ada angka terbaca.Mari kita telaah baris-baris kode di atas:
- Baris 6 (
char input[64];): buffer untuk menampung satu baris input. Ukuran 64 cukup untuk angka panjang sekaligus newline. - Baris 9 (
if (fgets(input, sizeof(input), stdin) != NULL)): membaca satu baris penuh denganfgetsyang aman dari buffer overflow. Pemeriksaan!= NULLmenangani kasus EOF. - Baris 10 (
char *endptr;): pointer yang akan diisistrtoldengan alamat karakter pertama yang tidak ikut diparse. - Baris 11 (
long nilai = strtol(input, &endptr, 10);): mengonversi stringinputmenjadilongdengan basis 10.strtolmelewatkan whitespace di awal, lalu membaca karakter angka (dan tanda minus opsional). Setelah itu,endptrmenunjuk ke karakter pertama yang bukan angka. - Baris 13 (
if (endptr == input)): jikaendptrsama denganinput(awal string), tidak ada karakter angka yang berhasil dibaca. Artinya input tidak diawali angka sama sekali. - Baris 15 (
else if (*endptr != '\0' && *endptr != '\n')): jikaendptrmenunjuk ke karakter selain null terminator atau newline, berarti ada karakter sisa setelah angka. Kita melaporkannya sebagai warning dan tetap mencetak nilai yang berhasil dibaca. - Baris 19 (
else): kasus sukses penuh — seluruh baris input adalah angka, mungkin dengan newline di akhir.
Mendeteksi Overflow dengan errno #
Salah satu keunggulan strtol dibanding scanf("%d", ...) adalah kemampuannya mendeteksi overflow. Jika input melebihi batas long, strtol mengembalikan LONG_MAX atau LONG_MIN dan mengatur variabel global errno menjadi ERANGE. Header <errno.h> menyediakan konstanta ERANGE dan <limits.h> menyediakan LONG_MIN serta LONG_MAX.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
int main(void) {
char input[64];
printf("Masukkan bilangan besar: ");
if (fgets(input, sizeof(input), stdin) != NULL) {
errno = 0;
char *endptr;
long nilai = strtol(input, &endptr, 10);
if (endptr == input) {
printf("Error: input tidak diawali angka.\n");
} else if (errno == ERANGE) {
printf("Error: nilai di luar rentang long.\n");
printf("LONG_MIN = %ld, LONG_MAX = %ld\n", LONG_MIN, LONG_MAX);
} else {
printf("Nilai: %ld\n", nilai);
if (*endptr != '\0' && *endptr != '\n') {
printf("Sisa karakter: \"%s\"\n", endptr);
}
}
}
return 0;
}Output yang Dihasilkan untuk berbagai input:
$ echo "123" | ./program
Masukkan bilangan besar: Nilai: 123
$ echo "99999999999999999999999" | ./program
Masukkan bilangan besar: Error: nilai di luar rentang long.
LONG_MIN = -9223372036854775808, LONG_MAX = 9223372036854775807
$ echo "-9223372036854775808" | ./program
Masukkan bilangan besar: Nilai: -9223372036854775808Mari kita telaah baris-baris kode di atas:
- Baris 10 (
errno = 0;): mengreseterrnoke nol sebelum memanggilstrtol. Ini wajib karenaerrnotidak otomatis direset oleh fungsi pustaka; jika tidak direset, kita mungkin membaca error dari panggilan sebelumnya yang tidak relevan. - Baris 12 (
long nilai = strtol(input, &endptr, 10);): pemanggilan sama seperti sebelumnya, tetapi sekarang kita juga memeriksaerrnosetelahnya. - Baris 16 (
else if (errno == ERANGE)): jikastrtolmendeteksi nilai di luar bataslong, fungsi tersebut mengaturerrno = ERANGEdan mengembalikanLONG_MAXatauLONG_MIN. Kita melaporkan rentang yang valid sebagai panduan bagi pengguna. - Baris 19 (
else): jika tidak ada error, cetak nilai. Untuk inputLONG_MINpersis, nilai tersebut tetap valid dan tidak memicuERANGE.
Membaca Float dengan strtod #
Untuk bilangan pecahan, kita memakai strtod yang mengembalikan double. Selain notasi desimal biasa seperti 3.14, strtod juga mendukung notasi ilmiah (2.5e3 berarti 2500) dan notasi heksadesimal (0x1p4 berarti 16). Fleksibilitas ini membuat strtod lebih tangguh daripada scanf("%f", ...).
#include <stdio.h>
#include <stdlib.h>
int main(void) {
/* Parse berbagai format bilangan */
char *samples[] = {"3.14", "2.5e3", " -0.5", "0x1p4", "1.234e-2"};
int n = sizeof(samples) / sizeof(samples[0]);
printf("Format parsing dengan strtod:\n");
for (int i = 0; i < n; i++) {
char *endptr;
double v = strtod(samples[i], &endptr);
printf(" \"%s\" -> %.4f (sisa: \"%s\")\n", samples[i], v, endptr);
}
/* Interaktif */
char input[64];
printf("\nMasukkan bilangan pecahan: ");
if (fgets(input, sizeof(input), stdin) != NULL) {
char *endptr;
double nilai = strtod(input, &endptr);
if (endptr == input) {
printf("Error: tidak ada angka terbaca.\n");
} else {
printf("Nilai : %.6f\n", nilai);
printf("Pembulatan 2 des : %.2f\n", nilai);
}
}
return 0;
}Output yang Dihasilkan (input: 3.14159):
Format parsing dengan strtod:
"3.14" -> 3.1400 (sisa: "")
"2.5e3" -> 2500.0000 (sisa: "")
" -0.5" -> -0.5000 (sisa: "")
"0x1p4" -> 16.0000 (sisa: "")
"1.234e-2" -> 0.0123 (sisa: "")
Masukkan bilangan pecahan: Nilai : 3.141590
Pembulatan 2 des : 3.14Mari kita telaah baris-baris kode di atas:
- Baris 6 (
char *samples[] = {"3.14", "2.5e3", ...};): array berisi lima contoh string yang merepresentasikan angka dalam format berbeda. Kita akan memparse semuanya denganstrtod. - Baris 11 (
double v = strtod(samples[i], &endptr);):strtodmengembalikandouble. Sama sepertistrtol, parameter kedua adalah pointer keendptruntuk melaporkan posisi akhir parsing. - Baris 12 (
printf(" \"%s\" -> %.4f (sisa: \"%s\")\n", ...)): mencetak string asli, nilaidoubleyang diparse dengan 4 digit desimal, dan sisa string yang tidak ikut diparse. Untuk input yang valid penuh, sisa berupa string kosong"". - Baris 18 (
char input[64];): buffer untuk input interaktif dari pengguna. - Baris 21 (
double nilai = strtod(input, &endptr);): memparse input pengguna. Karena kita memakaifgets, input mengandung newline di akhir yang juga akan ditangani sebagai karakter tersisa — dalam kasus iniendptrakan menunjuk ke\n, tetapi karena kita hanya mencetaknilai, newline tersebut tidak memengaruhi output. - Baris 23 (
if (endptr == input)): jikaendptrsama denganinput, tidak ada angka terbaca sama sekali. - Baris 27 (
printf("Pembulatan 2 des : %.2f\n", nilai);): demonstrasi bahwa nilaidoublebisa kita format ulang dengan presisi berbeda.%.2fmembulatkan ke dua digit desimal.