Konversi tipe secara implisit (implicit type conversion) adalah salah satu perilaku paling penting namun sering diabaikan di C. Setiap kali operator bekerja pada dua operand bertipe berbeda, atau setiap kali sebuah nilai disimpan ke variabel bertipe berbeda, compiler C melakukan konversi otomatis untuk menyamakan tipe—tanpa meminta kita menulis cast eksplisit. Pada Chapter ini kita akan menelaah tiga konteks utama konversi implisit: konversi pada assignment, konversi pada arithmetic operand (yang melibatkan integer promotions dan usual arithmetic conversions—akan diuraikan tuntas di dua Chapter berikutnya), serta default argument promotions pada pemanggilan fungsi variadic seperti printf. Kita juga akan menyoroti jebakan klasik: 7 / 2 yang menghasilkan 3 meski disimpan ke double.

Konversi pada Assignment #

Saat nilai bertipe T1 ditugaskan ke variabel bertipe T2, compiler mengonversi nilai tersebut ke tipe T2 terlebih dahulu sebelum menyimpannya. Jika T2 lebih lebar dari T1 (misalnya int ke long atau float ke double), konversi bersifat aman tanpa kehilangan data. Jika T2 lebih sempit (misalnya double ke int), pecahan dipotong (truncation) dan nilai bisa meluap (overflow).

c
#include <stdio.h>

int main(void) {
    int i = 5;
    double d = 2.5;

    /* int + double -> double */
    double hasil1 = i + d;
    printf("%d + %.2f = %.2f (tipe: double)\n", i, d, hasil1);

    /* int / int -> int (truncated), lalu disimpan ke double */
    double hasil2 = 7 / 2;
    printf("7 / 2 disimpan ke double = %.2f (bukan 3.5!)\n", hasil2);

    /* Untuk mendapat 3.5, salah satu operand harus floating */
    double hasil3 = 7.0 / 2;
    printf("7.0 / 2 = %.2f\n", hasil3);

    /* Konversi pada assignment: double ke int memotong pecahan */
    int bulat = 3.99;
    printf("3.99 disimpan ke int = %d\n", bulat);

    /* Konversi char ke int (integer promotion) */
    char c = 'A';
    int ascii = c + 1;
    printf("'%c' + 1 = %d (karakter '%c')\n", c, ascii, (char)ascii);
    return 0;
}

Output yang Dihasilkan:

text
5 + 2.50 = 7.50 (tipe: double)
7 / 2 disimpan ke double = 3.00 (bukan 3.5!)
7.0 / 2 = 3.50
3.99 disimpan ke int = 3
'A' + 1 = 66 (karakter 'B')

Mari kita telaah baris-baris kode di atas:

  1. Baris 8 (double hasil1 = i + d;): ekspresi i + d menggabungkan int dan double; i dikonversi ke double menjadi 5.0, lalu dijumlahkan dengan 2.5 menjadi 7.5; hasil double disimpan ke hasil1.
  2. Baris 13 (double hasil2 = 7 / 2;): operand 7 dan 2 keduanya int, sehingga pembagian 7 / 2 dievaluasi dalam int menjadi 3 (pecahan dibuang); nilai 3 inilah yang dikonversi ke double menjadi 3.0 dan disimpan ke hasil2. Inilah jebakan paling umum di C—pembagian int sudah selesai sebelum konversi ke double terjadi.
  3. Baris 17 (double hasil3 = 7.0 / 2;): operand 7.0 bertipe double, sehingga operand 2 dikonversi menjadi 2.0; pembagian berlangsung dalam double menghasilkan 3.5.
  4. Baris 21 (int bulat = 3.99;): konversi double ke int memotong pecahan ke arah nol; hasil 3 meskipun nilai asal 3.99 sangat dekat ke 4.
  5. Baris 25 (int ascii = c + 1;): variabel c bertipe char dipromosikan ke int melalui integer promotions sebelum dijumlahkan dengan 1; hasil 66 (kode ASCII 'B').

Demosi Tipe dan Hilangnya Data #

Ketika nilai disimpan ke variabel bertipe lebih sempit, compiler tetap melakukan konversi namun data bisa hilang. Demosi int ke short membuang bit atas; demosi double ke int memotong pecahan; demosi unsigned ke signed (atau sebaliknya dengan ukuran sama) tidak mengubah pola bit namun mengubah interpretasi nilai.

c
#include <stdio.h>

int main(void) {
    /* Konversi saat assignment: demosi tipe */
    int i = 1000000;       /* nilai di dalam rentang int */
    short s = i;            /* int -> short: potong bit */
    printf("int -> short : %d -> %d\n", i, s);

    /* Demosi double ke int */
    double pi = 3.14159265;
    int truncated = pi;
    printf("double -> int: %.8f -> %d\n", pi, truncated);

    /* Konversi unsigned-signed */
    unsigned int u = 4294967295U;   /* UINT_MAX di 32-bit */
    int signed_val = u;
    printf("unsigned %u -> int %d\n", u, signed_val);

    /* Konversi float -> double */
    float f = 1.5f;
    double d = f;           /* float -> double: aman */
    printf("float %.2f -> double %.2f\n", f, d);
    return 0;
}

Output yang Dihasilkan:

text
int -> short : 1000000 -> 16960
double -> int: 3.14159265 -> 3
unsigned 4294967295 -> int -1
float 1.50 -> double 1.50

Pada baris ke-6, nilai 1000000 (biner 0000 0000 0000 1111 0100 0010 0100 0000) disalin ke short (16 bit) sehingga 16 bit atas hilang; sisa 16 bit bawah 0100 0010 0100 0000 merepresentasikan 16960. Pada baris ke-13, 4294967295U (semua bit 1 pada 32-bit) memiliki pola yang sama ketika diinterpretasikan sebagai int bersifat signed, yaitu -1 dalam representasi two’s complement. Konversi float ke double pada baris ke-18 bersifat aman karena double memiliki presisi yang lebih besar; tidak ada data yang hilang.

Default Argument Promotions #

Saat memanggil fungsi tanpa prototype (jarang di C modern) atau memanggil fungsi variadic seperti printf, compiler melakukan default argument promotions pada setiap argumen:

  • Tipe char dan short dipromosikan ke int (atau unsigned int jika int tidak cukup menampung).
  • Tipe float dipromosikan ke double.

Akibatnya, format specifier %f pada printf menerima double, bukan float; %d menerima int, bukan char atau short. Inilah mengapa kita bisa melewatkan variabel char dan short langsung ke printf dengan %d tanpa cast eksplisit.

c
#include <stdio.h>

int main(void) {
    /* Konversi di argumen fungsi variadic (default argument promotions) */
    char c = 'X';
    short s = 100;
    float f = 3.14f;

    /* default argument promotions:
       - char/short -> int
       - float -> double
    */
    printf("char  via %%d : %d\n", c);
    printf("short via %%d : %d\n", s);
    printf("float via %%f : %f\n", f);

    /* Promosi di ekspresi campuran */
    int a = 100;
    long b = 2000000000L;
    long sum = a + b;       /* a dipromosi ke long */
    printf("\nint + long = %ld (long)\n", sum);

    /* Konversi tanda (signed/unsigned) */
    int neg = -5;
    unsigned int un = 10u;
    /* neg dikonversi ke unsigned: (UINT_MAX - 4) + 10 = 5 (wrap-around mod 2^32) */
    printf("neg + un via %%u : %u\n", neg + un);
    return 0;
}

Output yang Dihasilkan:

text
char  via %d : 88
short via %d : 100
float via %f : 3.140000

int + long = 2000000100 (long)
neg + un via %u : 5

Pada baris ke-14, variabel c bertipe char dipromosikan ke int sebelum diteruskan ke printf; nilai 'X' (kode ASCII 88) dicetak dengan %d. Pada baris ke-21, variabel a bertipe int dipromosikan ke long agar ukurannya sama dengan b; penjumlahan berlangsung dalam long menghasilkan 2000000100. Pada baris ke-27, ekspresi neg + un menggabungkan int dan unsigned int dengan rank sama; nilai neg = -5 dikonversi ke unsigned int menjadi UINT_MAX - 4 = 4294967291; ditambah 10 dan dimodulo 2^32 menghasilkan 5.

Konversi Aman dengan Cast Eksplisit #

Konversi implisit kadang tidak diinginkan atau membingungkan. Pada Chapter type casting berikutnya, kita akan melihat bagaimana operator cast (tipe) ekspresi memungkinkan kita melakukan konversi secara sadar dan terdokumentasi. Sebelum sampai di sana, perlu dipahami bahwa konversi implisit selalu terjadi—satu-satunya pertanyaan adalah apakah kita sengaja mengendalikannya atau membiarkan compiler memilih.

Konversi implisit adalah pondasi mengapa C bisa tetap ringkas walaupun memiliki banyak tipe data. Dengan memahami kapan konversi terjadi dan bagaimana data bisa hilang, kita dapat menulis kode yang aman terhadap kehilangan presisi maupun overflow.