Program yang baik tidak boleh crash hanya karena pengguna mengetik teks yang salah. Pada bagian ini kita memperketat validasi di setiap titik masukan: pilihan menu harus berada di rentang 1-5, kedua angka harus berhasil diparse sebagai double, dan pembagian dengan nol harus memunculkan pesan yang jelas. Strategi umum kita adalah pola coba-ulang: bila input tidak valid, tampilkan pesan, lalu continue kembali ke awal loop tanpa menghentikan program.

Konsep #

Validasi input terdiri dari dua lapis. Lapis pertama adalah validasi format — apakah teks yang dimasukkan dapat diinterpretasi sebagai angka? strtod menjawab pertanyaan ini dengan parameter endptr: bila endptr == buffer, tidak ada karakter yang berhasil diparse. Lapis kedua adalah validasi semantik — apakah angka tersebut masuk akal untuk konteks ini? Misalnya, pilihan menu harus di rentang 1-5; operand pembagian tidak boleh nol.

Bila salah satu lapis gagal, kita tidak boleh melanjutkan ke perhitungan. Loop utama memakai continue untuk kembali ke menu tanpa membaca angka. Pola ini menjaga agar program tetap hidup — pengguna dapat mencoba ulang berkali-kali sampai memberi input yang benar.

Implementasi #

Pada main.c, kita sudah memvalidasi pilihan menu dan kedua angka. Mari kita periksa kembali logikanya:

c
int pilihan = baca_pilihan();
if (pilihan == MENU_KELUAR) break;
if (pilihan < 1 || pilihan > 4) {
    printf("Pilihan tidak dikenal.\n");
    continue;   /* kembali ke menu tanpa membaca angka */
}

Untuk pembagian, kalkulator_hitung sudah memulangkan CALC_ERR_BAGI_NOL. Pemanggil memeriksa status tersebut dan menampilkan pesan yang sesuai:

c
CalcStatus s = kalkulator_hitung(op, a, b, &hasil);
if (s == CALC_OK) {
    printf("Hasil: %g %s %g = %g\n",
           a, kalkulator_simbol(op), b, hasil);
} else if (s == CALC_ERR_BAGI_NOL) {
    printf("Error: tidak bisa membagi dengan nol.\n");
} else {
    printf("Error: operasi tidak dikenal.\n");
}

Validasi angka dilakukan dengan cek sisa == buffer dari strtod:

c
char *sisa = 0;
double nilai = strtod(buffer, &sisa);
if (sisa == buffer) return 0;   /* tidak ada angka terbaca */
*out = nilai;
return 1;

Kode Lengkap #

Karena tidak ada berkas baru di bagian ini, kita tampilkan versi final main.c dengan seluruh lapisan validasi:

c
#include "calculator.h"

#include <stdio.h>
#include <stdlib.h>

#define MENU_KELUAR 5

static void tampilkan_menu(void) {
    printf("=== Kalkulator Terminal ===\n");
    printf("1. Tambah\n2. Kurang\n3. Kali\n4. Bagi\n5. Keluar\n");
}

static int baca_angka(const char *label, double *out) {
    char buffer[128];
    printf("%s: ", label);
    if (fgets(buffer, sizeof(buffer), stdin) == 0) return 0;
    char *sisa = 0;
    double nilai = strtod(buffer, &sisa);
    if (sisa == buffer) return 0;
    *out = nilai;
    return 1;
}

static int baca_pilihan(void) {
    char buffer[128];
    printf("Pilih operasi (1-5): ");
    if (fgets(buffer, sizeof(buffer), stdin) == 0) return MENU_KELUAR;
    char *p = buffer;
    while (*p == ' ' || *p == '\t') p++;
    if (*p < '1' || *p > '5') return 0;
    return *p - '0';
}

static int tanya_lagi(void) {
    char buffer[16];
    printf("Hitung lagi? (y/t): ");
    if (fgets(buffer, sizeof(buffer), stdin) == 0) return 0;
    return buffer[0] == 'y' || buffer[0] == 'Y';
}

int main(void) {
    int lanjut = 1;
    while (lanjut) {
        tampilkan_menu();
        int pilihan = baca_pilihan();
        if (pilihan == MENU_KELUAR) break;
        if (pilihan < 1 || pilihan > 4) {
            printf("Pilihan tidak dikenal.\n");
            continue;
        }
        double a, b;
        if (!baca_angka("Angka pertama", &a)) {
            printf("Input angka pertama tidak valid.\n");
            continue;
        }
        if (!baca_angka("Angka kedua", &b)) {
            printf("Input angka kedua tidak valid.\n");
            continue;
        }
        Operasi op = (Operasi)pilihan;
        double hasil = 0.0;
        CalcStatus s = kalkulator_hitung(op, a, b, &hasil);
        if (s == CALC_OK) {
            printf("Hasil: %g %s %g = %g\n",
                   a, kalkulator_simbol(op), b, hasil);
        } else if (s == CALC_ERR_BAGI_NOL) {
            printf("Error: tidak bisa membagi dengan nol.\n");
        } else {
            printf("Error: operasi tidak dikenal.\n");
        }
        lanjut = tanya_lagi();
    }
    printf("Terima kasih telah menggunakan Kalkulator Terminal.\n");
    return 0;
}

Pengujian #

Uji kombinasi input yang salah pada berbagai titik:

bash
gcc -std=c11 -Wall -Wextra -o /tmp/p1b4 main.c calculator.c -lm
printf 'sembilan\nbukan-angka\n1\n2.5\n1.5\nt\n' | /tmp/p1b4

Output yang Dihasilkan:

text
=== Kalkulator Terminal ===
1. Tambah
2. Kurang
3. Kali
4. Bagi
5. Keluar
Pilih operasi (1-5): Pilihan tidak dikenal.
=== Kalkulator Terminal ===
1. Tambah
2. Kurang
3. Kali
4. Bagi
5. Keluar
Pilih operasi (1-5): Angka pertama: Input angka pertama tidak valid.
=== Kalkulator Terminal ===
1. Tambah
2. Kurang
3. Kali
4. Bagi
5. Keluar
Pilih operasi (1-5): Angka pertama: Angka kedua: Hasil: 2.5 + 1.5 = 4
Hitung lagi? (y/t): Terima kasih telah menggunakan Kalkulator Terminal.

Uji pembagian dengan nol:

bash
printf '4\n5\n0\nt\n' | /tmp/p1b4

Output yang Dihasilkan:

text
=== Kalkulator Terminal ===
1. Tambah
2. Kurang
3. Kali
4. Bagi
5. Keluar
Pilih operasi (1-5): Angka pertama: Angka kedua: Error: tidak bisa membagi dengan nol.
Hitung lagi? (y/t): Terima kasih telah menggunakan Kalkulator Terminal.

Mari kita telaah alur validasi pada loop utama:

  1. Baris if (pilihan == MENU_KELUAR) break;: cek ini dilakukan lebih dulu karena 5 adalah nilai sah untuk keluar, tetapi tidak sah untuk operasi. Tanpa cek ini, pengguna yang mengetik 5 akan masuk ke pesan “Pilihan tidak dikenal” — perilaku yang membingungkan.
  2. Baris if (pilihan < 1 || pilihan > 4): rentang valid untuk operasi. baca_pilihan sudah memulangkan 0 bila input di luar 1-5, jadi cek ini menangkap nilai 0 dan nilai di atas 5.
  3. Baris if (!baca_angka(...)) continue;: setiap pemanggilan baca_angka punya cek terpisah agar pesan error dapat menyebut angka mana yang gagal. Ini lebih informatif daripada satu pesan generik “input tidak valid”.
  4. Baris else if (s == CALC_ERR_BAGI_NOL): pesan khusus untuk kasus ini, bukan generik “operasi gagal”. Pengguna langsung mengerti harus mengganti angka kedua.

Bagian 5 akan menyusun rangkaian test dan Makefile akhir.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

calculator.c

Unduh berkas
c
/* calculator.c -- implementasi logika perhitungan kalkulator terminal. */

#include "calculator.h"

/* Header ini diperlukan untuk strcmp di kalkulator_label. */
#include <string.h>

CalcStatus kalkulator_hitung(Operasi op, double a, double b, double *hasil) {
    /* Pastikan pointer hasil tidak NULL sebelum menulis. */
    if (hasil == 0) {
        return CALC_ERR_OP_TIDAK_DIKETAHUI;
    }

    switch (op) {
        case OP_TAMBAH:
            *hasil = a + b;
            return CALC_OK;
        case OP_KURANG:
            *hasil = a - b;
            return CALC_OK;
        case OP_KALI:
            *hasil = a * b;
            return CALC_OK;
        case OP_BAGI:
            if (b == 0.0) {
                return CALC_ERR_BAGI_NOL;
            }
            *hasil = a / b;
            return CALC_OK;
        default:
            return CALC_ERR_OP_TIDAK_DIKETAHUI;
    }
}

const char *kalkulator_label(Operasi op) {
    switch (op) {
        case OP_TAMBAH: return "Tambah";
        case OP_KURANG: return "Kurang";
        case OP_KALI:   return "Kali";
        case OP_BAGI:   return "Bagi";
        default:        return "Tidak Diketahui";
    }
}

const char *kalkulator_simbol(Operasi op) {
    switch (op) {
        case OP_TAMBAH: return "+";
        case OP_KURANG: return "-";
        case OP_KALI:   return "*";
        case OP_BAGI:   return "/";
        default:        return "?";
    }
}

main.c

Unduh berkas
c
/* main.c -- titik masuk kalkulator terminal.
 *
 * Memisahkan I/O (di main.c) dari logika perhitungan (di calculator.c)
 * supaya calculator.c dapat diuji tanpa membaca input pengguna.
 */

#include "calculator.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Konstanta menu untuk pilihan keluar. */
#define MENU_KELUAR 5

/* Menampilkan daftar menu utama ke stdout. */
static void tampilkan_menu(void) {
    printf("=== Kalkulator Terminal ===\n");
    printf("1. Tambah\n");
    printf("2. Kurang\n");
    printf("3. Kali\n");
    printf("4. Bagi\n");
    printf("5. Keluar\n");
}

/* Membaca satu baris input dan memparse bilangan double.
 *
 * Mengembalikan 1 bila berhasil, 0 bila input tidak terurai sebagai angka.
 */
static int baca_angka(const char *label, double *out) {
    char buffer[128];

    printf("%s: ", label);
    if (fgets(buffer, sizeof(buffer), stdin) == 0) {
        return 0;
    }

    /* fgets menyimpan newline; kita andalkan strtod untuk berhenti di sana. */
    char *sisa = 0;
    double nilai = strtod(buffer, &sisa);

    /* Bila seluruh isi buffer adalah whitespace atau kosong, gagal. */
    if (sisa == buffer) {
        return 0;
    }
    *out = nilai;
    return 1;
}

/* Membaca pilihan menu 1-5 dari input. */
static int baca_pilihan(void) {
    char buffer[128];

    printf("Pilih operasi (1-5): ");
    if (fgets(buffer, sizeof(buffer), stdin) == 0) {
        return MENU_KELUAR;
    }

    /* Ambil karakter pertama yang bukan whitespace. */
    char *p = buffer;
    while (*p == ' ' || *p == '\t') {
        p++;
    }

    if (*p < '1' || *p > '5') {
        return 0;
    }
    return *p - '0';
}

/* Menanyakan apakah pengguna ingin menghitung lagi. */
static int tanya_lagi(void) {
    char buffer[16];

    printf("Hitung lagi? (y/t): ");
    if (fgets(buffer, sizeof(buffer), stdin) == 0) {
        return 0;
    }
    return buffer[0] == 'y' || buffer[0] == 'Y';
}

int main(void) {
    int lanjut = 1;

    while (lanjut) {
        tampilkan_menu();
        int pilihan = baca_pilihan();

        if (pilihan == MENU_KELUAR) {
            break;
        }
        if (pilihan < 1 || pilihan > 4) {
            printf("Pilihan tidak dikenal.\n");
            continue;
        }

        double a = 0.0;
        double b = 0.0;
        if (!baca_angka("Angka pertama", &a)) {
            printf("Input angka pertama tidak valid.\n");
            continue;
        }
        if (!baca_angka("Angka kedua", &b)) {
            printf("Input angka kedua tidak valid.\n");
            continue;
        }

        Operasi op = (Operasi)pilihan;
        double hasil = 0.0;
        CalcStatus status = kalkulator_hitung(op, a, b, &hasil);

        if (status == CALC_OK) {
            printf("Hasil: %g %s %g = %g\n",
                   a, kalkulator_simbol(op), b, hasil);
        } else if (status == CALC_ERR_BAGI_NOL) {
            printf("Error: tidak bisa membagi dengan nol.\n");
        } else {
            printf("Error: operasi tidak dikenal.\n");
        }

        lanjut = tanya_lagi();
    }

    printf("Terima kasih telah menggunakan Kalkulator Terminal.\n");
    return 0;
}

tests.c

Unduh berkas
c
/* tests.c -- rangkaian test sederhana untuk modul calculator.
 *
 * Tiap test memanggil kalkulator_hitung lalu membandingkan hasil dengan
 * nilai yang diharapkan. Jika ada test yang gagal, program keluar dengan
 * kode bukan nol.
 */

#include "calculator.h"

#include <stdio.h>
#include <math.h>
#include <string.h>

static int test_lulus = 0;
static int test_gagal = 0;

/* Pembanding aman untuk bilangan double. */
static int hampir_sama(double a, double b) {
    return fabs(a - b) < 1e-9;
}

/* Macro untuk mencatat dan melaporkan hasil test. */
#define PERIKSA(kondisi, nama)                                  \
    do {                                                        \
        if (kondisi) {                                          \
            test_lulus++;                                       \
            printf("[LULUS] %s\n", nama);                       \
        } else {                                                \
            test_gagal++;                                       \
            printf("[GAGAL] %s\n", nama);                       \
        }                                                       \
    } while (0)

static void test_tambah(void) {
    double hasil = 0.0;
    CalcStatus s = kalkulator_hitung(OP_TAMBAH, 5.0, 3.0, &hasil);
    PERIKSA(s == CALC_OK && hampir_sama(hasil, 8.0), "Tambah 5 + 3 = 8");
}

static void test_kurang(void) {
    double hasil = 0.0;
    CalcStatus s = kalkulator_hitung(OP_KURANG, 10.0, 4.0, &hasil);
    PERIKSA(s == CALC_OK && hampir_sama(hasil, 6.0), "Kurang 10 - 4 = 6");
}

static void test_kali(void) {
    double hasil = 0.0;
    CalcStatus s = kalkulator_hitung(OP_KALI, 2.5, 4.0, &hasil);
    PERIKSA(s == CALC_OK && hampir_sama(hasil, 10.0), "Kali 2.5 * 4 = 10");
}

static void test_bagi(void) {
    double hasil = 0.0;
    CalcStatus s = kalkulator_hitung(OP_BAGI, 10.0, 4.0, &hasil);
    PERIKSA(s == CALC_OK && hampir_sama(hasil, 2.5), "Bagi 10 / 4 = 2.5");
}

static void test_bagi_nol(void) {
    double hasil = 0.0;
    CalcStatus s = kalkulator_hitung(OP_BAGI, 5.0, 0.0, &hasil);
    PERIKSA(s == CALC_ERR_BAGI_NOL, "Bagi dengan nol menolak");
}

static void test_label_dan_simbol(void) {
    PERIKSA(strcmp(kalkulator_label(OP_TAMBAH), "Tambah") == 0,
           "Label OP_TAMBAH = \"Tambah\"");
    PERIKSA(strcmp(kalkulator_simbol(OP_BAGI), "/") == 0,
           "Simbol OP_BAGI = \"/\"");
}

int main(void) {
    test_tambah();
    test_kurang();
    test_kali();
    test_bagi();
    test_bagi_nol();
    test_label_dan_simbol();

    printf("\nRingkasan: %d lulus, %d gagal\n", test_lulus, test_gagal);
    return test_gagal == 0 ? 0 : 1;
}