Konversi Tipe dan Casting

Promosi tipe implisit, cast eksplisit, dan jebakan pemotongan nilai saat konversi antartipe.

Daftar isi

Ekspresi di dalam C sering mencampur tipe yang berbeda, misalnya 7 / 2.0 yang menggabungkan int dan double, sehingga satu nilai harus diubah ke tipe yang lain sebelum dihitung. Topik ini membahas konversi yang dilakukan kompiler secara otomatis, konversi yang diminta secara eksplisit melalui cast (cast), serta jebakan pemotongan nilai yang menyertai keduanya. Setelah membacanya, alasan hasil 7 / 2 berbeda dengan 7 / 2.0 dapat dijelaskan dan pemotongan nilai yang tak disengaja dapat dihindari.

Konversi implisit saat mencampur tipe#

Bila kedua operan sebuah operasi aritmetika bertipe berbeda, kompiler mengubah nilai bertipe sempit menuju tipe yang lebih luas, lalu operasinya dihitung pada tipe yang luas itu. Aturan ini disebut konversi aritmetika biasa (usual arithmetic conversion) dan berjalan tanpa satu baris kode pun dari penulisnya. Program berikut menguji perilaku tersebut:

c konversi-implisit.c
#include <stdio.h>

int main(void) {
    int bulat = 7;
    double pecahan = 2.0;
    char huruf = 'A';

    printf("bulat / 2        = %d\n", bulat / 2);
    printf("bulat / 2.0      = %.1f\n", bulat / pecahan);
    printf("huruf + 1        = %d\n", huruf + 1);
    printf("sizeof(huruf)    = %zu byte\n", sizeof(huruf));
    printf("sizeof(huruf+1)  = %zu byte\n", sizeof(huruf + 1));
    return 0;
}
text Keluaran
bulat / 2        = 3
bulat / 2.0      = 3.5
huruf + 1        = 66
sizeof(huruf)    = 1 byte
sizeof(huruf+1)  = 4 byte

Dua baris pertama adalah contoh intinya. Ekspresi bulat / 2 menghitung pembagian dua int sehingga hasilnya 3, sedangkan bulat / 2.0 mengubah bulat menjadi double lebih dahulu sehingga hasilnya 3.5. Konversi itu hanya menyentuh operan yang bertipe sempit, sedangkan hasil ekspresi mengikuti tipe yang lebih luas. Karena itu, menugaskan bulat / 2.0 ke variabel int tetap memotong nilai pecahannya.

Konversi arah yang aman adalah dari tipe sempit ke tipe luas, misalnya int ke double, karena tidak ada nilai yang hilang. Arah sebaliknya berpotensi memotong nilai, dan konversi semacam ini tetap dilakukan kompiler tanpa peringatan ketika penugasan memintanya. Karena itu, pemotongan paling sering terjadi bukan pada ekspresi, melainkan pada penugasan hasil ekspresi ke variabel yang tipenya sempit.

Promosi bilangan bulat#

Aturan khusus berlaku untuk tipe yang lebih sempit daripada int, yaitu char dan short. Setiap kali tipe ini dipakai di dalam ekspresi aritmetika, nilainya otomatis dinaikkan menjadi int lebih dahulu, dan proses ini disebut promosi bilangan bulat (integer promotion). Baris ketiga sampai kelima program di atas membuktikannya. huruf + 1 menghasilkan 66 karena 'A' dinaikkan menjadi 65 lebih dahulu. Ekspresi sizeof(huruf + 1) bernilai 4 byte karena hasil penjumlahan tersebut bertipe int, bukan char.

Promosi ini terjadi begitu sering sehingga jarang disadari. Fungsi variadik (variadic) seperti printf, yaitu fungsi yang menerima jumlah argumen bebas, juga menerima promosi yang sama. char dan short naik menjadi int sebelum sampai ke fungsi, sehingga %d dapat dipakai untuk keduanya. Promosi juga menjaga hasil tidak mudah melimpah, karena penjumlahan dua nilai char dihitung pada ruang 32 bit sebelum ditampung kembali. Kesadaran akan promosi penting terutama saat bekerja dengan sizeof dan operasi pada tipe kecil, karena hasil antara selalu berukuran int meskipun operannya kecil.

Cast eksplisit#

Konversi juga dapat diminta secara tertulis dengan cast, yaitu nama tipe yang diberi tanda kurung dan diletakkan di depan nilai yang ingin diubah, misalnya (double) total_nilai. Cast membuat niat konversi terbaca dari kodenya dan memberi kendali penuh atas kapan konversi dilakukan. Sebagai operator, cast mengikat lebih kuat daripada seluruh operator aritmetika, sehingga cast selalu berlaku untuk operan tepat di depannya kecuali tanda kurung menambah lingkupnya. Program berikut menghitung rata-rata dua nilai, kasus klasik yang gagal tanpa cast:

c cast-pemotongan.c
#include <stdio.h>

int main(void) {
    int total_nilai = 8 + 7;
    double rata_cast = (double) total_nilai / 2;
    double rata_bulat = total_nilai / 2;
    double rata_salah = (double) (total_nilai / 2);

    printf("Rata-rata dengan cast : %.1f\n", rata_cast);
    printf("Rata-rata tanpa cast  : %.1f\n", rata_bulat);
    printf("Cast di tempat salah  : %.1f\n", rata_salah);

    printf("(int) 3.99            = %d\n", (int) 3.99);
    printf("(int) -3.99           = %d\n", (int) -3.99);

    int potong = 3.99;
    printf("int potong = 3.99     = %d\n", potong);
    return 0;
}
text Keluaran
Rata-rata dengan cast : 7.5
Rata-rata tanpa cast  : 7.0
Cast di tempat salah  : 7.0
(int) 3.99            = 3
(int) -3.99           = -3
int potong = 3.99     = 3

Rata-rata 8 dan 7 seharusnya 7.5, tetapi total_nilai / 2 menghitung pembagian bulat terlebih dahulu dan menghasilkan 7. Cast (double) total_nilai mengubah pembilang menjadi double sebelum pembagian, sehingga pembagian dilakukan secara pecahan dan hasilnya benar. Yang diubah cast hanyalah operan tepat di depannya, sehingga urutan inilah yang menentukan hasil. Bentuk penulisannya fleksibel terhadap spasi, sehingga (double)nilai, (double) nilai, dan ( double )nilai bermakna sama, sedangkan kebiasaan yang rapi adalah menambahkan satu spasi setelah tipe agar mudah dibaca.

Jebakan pemotongan dan penempatan cast#

Baris Cast di tempat salah memperlihatkan jebakan yang lebih halus, yaitu cast yang diletakkan setelah pembagian selesai. Ekspresi (double) (total_nilai / 2) menghitung pembagian bulat 15 / 2 lebih dahulu sehingga pecahannya sudah hilang, lalu cast hanya mengubah 7 menjadi 7.0. Cast harus menjangkau nilai sebelum operasi yang membuang pecahan, bukan setelahnya.

Konversi dari pecahan ke bilangan bulat memotong bagian desimal menuju nol, bukan membulatkan. Baris (int) 3.99 menghasilkan 3 dan (int) -3.99 menghasilkan -3, sehingga pembulatan aritmetika biasa harus dilakukan sendiri, misalnya dengan menambah 0.5 untuk nilai positif sebelum dipotong. Penugasan int potong = 3.99; melakukan pemotongan yang sama secara diam-diam karena konversi penugasan tidak menuntut cast. Hasil ekspresi pun dapat terpotong dengan cara yang sama, sehingga rata-rata nilai ujian yang dihitung pada variabel int selalu kehilangan desimalnya meskipun variabel penampungnya bertipe double.

Kesalahan umum#

Kesalahan pertama adalah mengira posisi cast tidak berpengaruh. Seperti ditunjukkan rata_salah, cast yang terlambat tidak dapat mengembalikan pecahan yang sudah dibuang oleh pembagian bulat. Aturan praktisnya, letakkan cast pada operan sebelum operasi yang menyebabkan pemotongan, dan pastikan tanda kurungnya mengapit seluruh operan yang ingin diubah.

Kesalahan kedua adalah menyempitkan tipe secara diam-diam hingga nilai keluar dari rentang. Menugaskan int bernilai 300 ke char tidak memiliki hasil yang dijamin standar karena nilai tersebut melampaui rentang char pada umumnya, sehingga hasilnya bergantung kompiler dan mesin. Konversi semacam ini sebaiknya dihindari, dan bila memang diperlukan, tulis castnya secara eksplisit lalu pastikan rentang nilainya sudah dicek sebelumnya.

Kesalahan ketiga adalah mengandalkan cast untuk memperbaiki hasil yang salah akibat pemotongan yang sudah terjadi di tengah ekspresi. Cast hanya mengubah tipe nilai yang ada, bukan mengulang perhitungannya, sehingga akar masalahnya harus dicari pada urutan operasi, bukan ditutupi di ujung.

Ringkasan#

  • Mencampur tipe dalam satu operasi membuat kompiler mengubah operan yang lebih sempit menuju tipe yang lebih luas sebelum menghitung.
  • char dan short selalu dipromosikan menjadi int di dalam ekspresi aritmetika, sehingga hasil antaranya berukuran int.
  • Cast berbentuk (tipe) nilai meminta konversi secara tertulis, dan posisinya menentukan apakah pemotongan terjadi sebelum atau sesudah operasi.
  • Konversi pecahan ke bilangan bulat memotong menuju nol, bukan membulatkan, dan konversi yang menyempitkan tipe dapat membuang nilai tanpa peringatan.

Referensi#