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.

c
#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:

text
$ 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:

  1. Baris 6 (char input[64];): buffer untuk menampung satu baris input. Ukuran 64 cukup untuk angka panjang sekaligus newline.
  2. Baris 9 (if (fgets(input, sizeof(input), stdin) != NULL)): membaca satu baris penuh dengan fgets yang aman dari buffer overflow. Pemeriksaan != NULL menangani kasus EOF.
  3. Baris 10 (char *endptr;): pointer yang akan diisi strtol dengan alamat karakter pertama yang tidak ikut diparse.
  4. Baris 11 (long nilai = strtol(input, &endptr, 10);): mengonversi string input menjadi long dengan basis 10. strtol melewatkan whitespace di awal, lalu membaca karakter angka (dan tanda minus opsional). Setelah itu, endptr menunjuk ke karakter pertama yang bukan angka.
  5. Baris 13 (if (endptr == input)): jika endptr sama dengan input (awal string), tidak ada karakter angka yang berhasil dibaca. Artinya input tidak diawali angka sama sekali.
  6. Baris 15 (else if (*endptr != '\0' && *endptr != '\n')): jika endptr menunjuk ke karakter selain null terminator atau newline, berarti ada karakter sisa setelah angka. Kita melaporkannya sebagai warning dan tetap mencetak nilai yang berhasil dibaca.
  7. 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.

c
#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:

text
$ 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: -9223372036854775808

Mari kita telaah baris-baris kode di atas:

  1. Baris 10 (errno = 0;): mengreset errno ke nol sebelum memanggil strtol. Ini wajib karena errno tidak otomatis direset oleh fungsi pustaka; jika tidak direset, kita mungkin membaca error dari panggilan sebelumnya yang tidak relevan.
  2. Baris 12 (long nilai = strtol(input, &endptr, 10);): pemanggilan sama seperti sebelumnya, tetapi sekarang kita juga memeriksa errno setelahnya.
  3. Baris 16 (else if (errno == ERANGE)): jika strtol mendeteksi nilai di luar batas long, fungsi tersebut mengatur errno = ERANGE dan mengembalikan LONG_MAX atau LONG_MIN. Kita melaporkan rentang yang valid sebagai panduan bagi pengguna.
  4. Baris 19 (else): jika tidak ada error, cetak nilai. Untuk input LONG_MIN persis, nilai tersebut tetap valid dan tidak memicu ERANGE.

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", ...).

c
#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):

text
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.14

Mari kita telaah baris-baris kode di atas:

  1. Baris 6 (char *samples[] = {"3.14", "2.5e3", ...};): array berisi lima contoh string yang merepresentasikan angka dalam format berbeda. Kita akan memparse semuanya dengan strtod.
  2. Baris 11 (double v = strtod(samples[i], &endptr);): strtod mengembalikan double. Sama seperti strtol, parameter kedua adalah pointer ke endptr untuk melaporkan posisi akhir parsing.
  3. Baris 12 (printf(" \"%s\" -> %.4f (sisa: \"%s\")\n", ...)): mencetak string asli, nilai double yang diparse dengan 4 digit desimal, dan sisa string yang tidak ikut diparse. Untuk input yang valid penuh, sisa berupa string kosong "".
  4. Baris 18 (char input[64];): buffer untuk input interaktif dari pengguna.
  5. Baris 21 (double nilai = strtod(input, &endptr);): memparse input pengguna. Karena kita memakai fgets, input mengandung newline di akhir yang juga akan ditangani sebagai karakter tersisa — dalam kasus ini endptr akan menunjuk ke \n, tetapi karena kita hanya mencetak nilai, newline tersebut tidak memengaruhi output.
  6. Baris 23 (if (endptr == input)): jika endptr sama dengan input, tidak ada angka terbaca sama sekali.
  7. Baris 27 (printf("Pembulatan 2 des : %.2f\n", nilai);): demonstrasi bahwa nilai double bisa kita format ulang dengan presisi berbeda. %.2f membulatkan ke dua digit desimal.