Usual arithmetic conversions adalah aturan yang menentukan tipe akhir ketika dua operand operator biner memiliki tipe berbeda. Aturan ini diterapkan setelah integer promotions bekerja pada operand bertipe kecil, dan menentukan tipe apa yang dipakai untuk evaluasi aktual operator. Pada Chapter ini kita akan menelaah algoritma usual arithmetic conversions secara bertahap, mulai dari kasus sederhana int + long, kasus signed/unsigned yang sering memunculkan kejutan, hingga hierarki floating-point. Kita juga akan melihat bagaimana operator ? : menerapkan aturan yang sama pada kedua cabangnya—alasan mengapa int ? int : double menghasilkan double walaupun hanya cabang int yang dipilih.

Algoritma Usual Arithmetic Conversions #

ISO/IEC 9899 §6.3.1.8 mengatur algoritma berikut saat dua operand bertipe berbeda:

  1. Jika salah satu operand bertipe floating-point, keduanya dikonversi ke tipe floating-point dengan rank tertinggi di antara keduanya. Hierarki: float < double < long double.
  2. Jika keduanya bertipe integer (setelah integer promotions):
    • Jika kedua operand bertipe sama, tidak ada konversi lebih lanjut.
    • Jika kedua operand bertipe signed atau keduanya unsigned, konversi ke tipe dengan rank lebih tinggi.
    • Jika operand unsigned memiliki rank lebih tinggi atau sama dengan operand signed, operand signed dikonversi ke tipe unsigned tersebut.
    • Jika operand signed memiliki rank lebih tinggi dan dapat merepresentasikan semua nilai operand unsigned, operand unsigned dikonversi ke tipe signed.
    • Jika tidak, kedua operand dikonversi ke versi unsigned dari tipe signed.
c
#include <stdio.h>

int main(void) {
    int i = 5;
    long l = 10L;
    long long ll = 100LL;
    unsigned u = 7U;
    double d = 2.5;

    /* int + long: int -> long, hasil long */
    long r1 = i + l;
    printf("int + long         = %ld (long)\n", r1);

    /* long + long long: long -> long long */
    long long r2 = l + ll;
    printf("long + long long   = %lld (long long)\n", r2);

    /* int + unsigned int: int -> unsigned int (rank sama, signed->unsigned) */
    unsigned int r3 = i + u;
    printf("int + unsigned    = %u (unsigned int)\n", r3);

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

    /* unsigned + double: unsigned -> double */
    double r5 = u + d;
    printf("unsigned + double = %.2f (double)\n", r5);
    return 0;
}

Output yang Dihasilkan:

text
int + long         = 15 (long)
int + unsigned    = 12 (unsigned int)
int + double      = 7.50 (double)
unsigned + double = 9.50 (double)

Mari kita telaah baris-baris kode di atas:

  1. Baris 12 (long r1 = i + l;): i bertipe int, l bertipe long. Karena long memiliki rank lebih tinggi, i dikonversi ke long; penjumlahan berlangsung dalam long menghasilkan 15.
  2. Baris 16 (unsigned int r3 = i + u;): keduanya memiliki rank sama (32-bit). Aturan ketiga berlaku: operand unsigned (u) memiliki rank sama dengan operand signed (i), sehingga i dikonversi ke unsigned int; penjumlahan menghasilkan 12 (unsigned).
  3. Baris 20 (double r4 = i + d;): d bertipe floating-point; i dikonversi ke double menjadi 5.0; 5.0 + 2.5 = 7.5.
  4. Baris 24 (double r5 = u + d;): meskipun u bertipe unsigned int, aturan pertama berlaku: u dikonversi ke double menjadi 7.0; 7.0 + 2.5 = 9.5.

Jebakan signed/unsigned dengan Rank Sama #

Skenario paling berbahaya adalah ketika operand signed dan unsigned memiliki rank yang sama, misalnya int dan unsigned int. Aturan ketiga menyatakan bahwa operand signed dikonversi ke tipe unsigned, bahkan jika nilai operand signed negatif. Akibatnya, nilai negatif berubah menjadi nilai unsigned yang sangat besar, dan perbandingan dapat menghasilkan kebalikan dari yang diharapkan.

c
#include <stdio.h>

int main(void) {
    /* Jebakan: signed vs unsigned dengan rank sama */
    int neg = -1;
    unsigned int pos = 5U;

    /* neg dikonversi ke unsigned int: UINT_MAX */
    /* (UINT_MAX) + 5 wrap-around = 4 */
    unsigned int hasil = neg + pos;
    printf("-1 (int) + 5 (unsigned) = %u (unsigned)\n", hasil);

    /* Perbandingan signed/unsigned */
    int a = -5;
    unsigned int b = 3U;
    if (a < b) {     /* a dikonversi ke unsigned (besar) */
        printf("-5 < 3 (seharusnya benar) -> terpilih\n");
    } else {
        printf("-5 < 3 (seharusnya benar) -> TIDAK terpilih!\n");
        printf("  karena -5 jadi unsigned = %u\n", (unsigned)a);
    }

    /* Solusi: cast eksplisit */
    if ((long)a < (long)b) {
        printf("cast ke long: -5 < 3 -> terpilih (benar)\n");
    }
    return 0;
}

Output yang Dihasilkan:

text
-1 (int) + 5 (unsigned) = 4 (unsigned)
-5 < 3 (seharusnya benar) -> TIDAK terpilih!
  karena -5 jadi unsigned = 4294967291
cast ke long: -5 < 3 -> terpilih (benar)

Pada baris ke-9, ekspresi neg + pos dengan neg = -1 mengonversi -1 ke unsigned int menjadi UINT_MAX = 4294967295. Menambahkan 5 menghasilkan 4294967300 yang dibungkus (modulo 2^32) menjadi 4. Pada baris ke-16, perbandingan a < b dengan a = -5 dan b = 3U mengikuti aturan ketiga: a dikonversi ke unsigned int menjadi 4294967291. Karena 4294967291 < 3 adalah salah, cabang “TIDAK terpilih” yang dieksekusi. Pada baris ke-24, cast eksplisit (long)a dan (long)b memaksa kedua operand menjadi long (yang lebih besar dari unsigned int); long dapat merepresentasikan kedua nilai secara signed, sehingga -5 < 3 benar.

Hierarki Floating-Point #

Pada operand floating-point, algoritma usual arithmetic conversions lebih sederhana: keduanya dikonversi ke tipe dengan rank tertinggi. Hierarki resmi adalah float < double < long double. Konversi float ke double bersifat aman (presisi naik); sebaliknya konversi double ke float dapat kehilangan presisi dan rentang.

c
#include <stdio.h>

int main(void) {
    /* Floating-point hierarchy: float -> double -> long double */
    float f = 1.5f;
    double d = 2.5;
    long double ld = 3.5L;

    double r1 = f + d;       /* float -> double */
    printf("float + double        = %.2f (double)\n", r1);

    long double r2 = d + ld;  /* double -> long double */
    printf("double + long double  = %.2Lf (long double)\n", r2);

    /* Promosi pada operator ternary */
    int a = 5;
    double b = 3.5;
    /* tipe hasil: kedua cabang disamakan ke double */
    double r3 = (a > 0) ? a : b;
    printf("int ? int : double   = %.2f (double)\n", r3);

    /* Tipe pada assignment */
    int hasil_int = (int)(d + f);   /* truncate dari double */
    printf("(int)(2.5 + 1.5)    = %d\n", hasil_int);
    return 0;
}

Output yang Dihasilkan:

text
float + double        = 4.00 (double)
double + long double  = 6.00 (long double)
int ? int : double   = 5.00 (double)
(int)(2.5 + 1.5)    = 4

Pada baris ke-9, ekspresi f + d mengonversi f (1.5f) ke double (1.5); 1.5 + 2.5 = 4.0. Pada baris ke-13, d + ld mengonversi d ke long double; 2.5 + 3.5 = 6.0. Pada baris ke-20, operator ? : menerapkan usual arithmetic conversions pada kedua cabangnya: a bertipe int, b bertipe double; kedua cabang disamakan ke double, sehingga hasil ekspresi bertipe double. Walaupun hanya cabang a yang dievaluasi (karena a > 0), hasil int 5 tetap dikonversi ke double 5.0 saat disimpan ke r3.

Tipe Hasil Operator Ternary #

Operator conditional ? : adalah salah satu tempat di mana usual arithmetic conversions bekerja secara tidak terduga: kedua cabang harus memiliki tipe yang kompatibel, dan compiler akan menerapkan aturan konversi pada keduanya terlepas dari cabang mana yang dipilih saat runtime. Pola cond ? -1 : 1u menghasilkan unsigned int (bukan int) karena 1u adalah unsigned int—ini adalah bug klasik yang menyebabkan -1 berubah menjadi 4294967295 saat kondisi salah.

Dengan pemahaman usual arithmetic conversions, kita memiliki bekal lengkap untuk memprediksi tipe hasil setiap ekspresi aritmetika di C. Pada Chapter terakhir Section ini, kita akan menelaah undefined behavior dalam ekspresi—kasus di mana compiler bebas menghasilkan apa pun karena ekspresi melanggar batasan standar.