Header <numeric> menyimpan perhitungan-perhitungan kecil yang muncul terus di kode nyata. Sebagian isinya bekerja pada rentang iterator — std::iota, std::inner_product, std::partial_sum, dan std::adjacent_difference — dan sudah kita bahas tuntas pada Chapter Algoritma Numerik <numeric> di Section Iterators dan Algorithms. Chapter ini melengkapi sisi lain dari header yang sama: fungsi yang menerima angka, bukan rentang — std::gcd dan std::lcm untuk bilangan bulat, serta std::midpoint (C++20) untuk mencari titik tengah dengan aman. Ketiganya pendek, mudah diingat, dan menyelesaikan tugas yang selama ini kita tulis manual — kadang dengan hasil yang keliru. Di akhir chapter, kita merangkum seluruh isi <numeric> dalam satu tabel agar mudah dicari kembali.

gcd dan lcm dalam Satu Panggilan #

std::gcd(a, b) mengembalikan greatest common divisor (pembagi bersama terbesar) — bilangan bulat terbesar yang membagi habis a dan b. std::lcm(a, b) mengembalikan least common multiple (kelipatan bersama terkecil) — bilangan terkecil yang habis dibagi oleh keduanya. Keduanya hidup di header <numeric>, menerima dua argumen bertipe bilangan bulat, dan memakai algoritma Euclid yang cepat di balik layar.

cpp
#include <iostream>  // std::cout
#include <iterator>  // std::begin, std::end
#include <numeric>   // std::gcd, std::lcm, std::midpoint

int main() {
    // gcd: pembagi bersama terbesar
    std::cout << "gcd(48, 18)    : " << std::gcd(48, 18) << "\n";
    std::cout << "gcd(17, 5)     : " << std::gcd(17, 5) << "\n";
    std::cout << "gcd(-12, 18)   : " << std::gcd(-12, 18) << "\n";
    std::cout << "gcd(0, 7)      : " << std::gcd(0, 7) << "\n";

    // lcm: kelipatan bersama terkecil; rantai untuk lebih dari dua angka
    std::cout << "lcm(4, 6)      : " << std::lcm(4, 6) << "\n";
    std::cout << "lcm(3, 7, 5)   : " << std::lcm(std::lcm(3, 7), 5) << "\n";

    // midpoint: titik tengah tanpa bahaya luapan
    int a = 2147483646;  // dekat batas atas int (2147483647)
    int b = 2147483647;
    std::cout << "midpoint(a, b) : " << std::midpoint(a, b) << "\n";
    std::cout << "midpoint(2, 5) : " << std::midpoint(2, 5) << "\n";
    std::cout << "midpoint(5, 2) : " << std::midpoint(5, 2) << "\n";

    // midpoint juga bekerja pada pointer: indeks tengah array
    int data[] = {10, 20, 30, 40, 50, 60};
    int* tengah = std::midpoint(std::begin(data), std::end(data));
    std::cout << "elemen tengah  : " << *tengah << "\n";
    return 0;
}

Output yang Dihasilkan:

text
gcd(48, 18)    : 6
gcd(17, 5)     : 1
gcd(-12, 18)   : 6
gcd(0, 7)      : 7
lcm(4, 6)      : 12
lcm(3, 7, 5)   : 105
midpoint(a, b) : 2147483646
midpoint(2, 5) : 3
midpoint(5, 2) : 4
elemen tengah  : 40

Bedah baris demi baris:

  1. Baris 7-10 (gcd): gcd(48, 18) menghasilkan 6 karena 6 adalah bilangan terbesar yang membagi habis keduanya. Dua bilangan yang saling prima seperti 17 dan 5 menghasilkan 1. Untuk argumen negatif, std::gcd bekerja pada nilai mutlaknya sehingga gcd(-12, 18) tetap 6, dan gcd(0, 7) menghasilkan 7 karena setiap bilangan membagi 0.
  2. Baris 13-14 (lcm): lcm(4, 6) menghasilkan 12. Untuk lebih dari dua angka, fungsi ini dirantai: std::lcm(std::lcm(3, 7), 5) menghitung kelipatan bersama terkecil dari tiga bilangan sekaligus — 105.
  3. Baris 17-21 (midpoint pada bilangan): std::midpoint(a, b) mengembalikan titik tengah a dan b. Untuk bilangan bulat, bila hasilnya pecahan, pembulatannya mengarah ke argumen pertama: midpoint(2, 5) memberi 3, sedangkan midpoint(5, 2) memberi 4. Baris 19 adalah bintangnya — dua nilai yang penuhnya dekat batas atas int tetap menghasilkan titik tengah yang benar; kita lihat mengapa pada bagian berikutnya.
  4. Baris 24-26 (midpoint pada pointer): midpoint juga menerima dua pointer ke array yang sama dan mengembalikan pointer tengahnya. *tengah mencetak 40, elemen tengah enam elemen data — berguna untuk membelah array pada pencarian biner buatan sendiri.

Mengapa midpoint Ada #

Cara lama mencari titik tengah dua bilangan adalah (a + b) / 2. Rumus ini benar untuk nilai kecil, tetapi menyimpan jebakan yang halus: penjumlahan a + b dihitung dulu sebelum dibagi, dan bila kedua nilai besar, penjumlahan itu bisa melewati batas atas int. Melewati batas tipe bilangan bulat bertanda adalah undefined behavior — hasil yang muncul tidak dijamin apa pun, dan compiler berhak menganggap kejadian itu mustahil terjadi.

cpp Bahaya: rata-rata dengan (a + b) / 2
int a = 2147483647;           // batas atas int
int b = 2147483646;
int tengah = (a + b) / 2;     // a + b meluap -> undefined behavior
int aman   = std::midpoint(a, b);  // 2147483646, tanpa luapan

std::midpoint menghitung titik tengah tanpa pernah membentuk nilai a + b yang bisa meluap, sehingga selalu aman untuk seluruh rentang tipenya. Itulah alasan baris 19 pada program kita menghasilkan 2147483646 — tepat di wilayah yang membuat rumus (a + b) / 2 gagal. Kebiasaan yang baik: setiap kali melihat kebutuhan “titik tengah dua bilangan”, pakai std::midpoint, bukan penjumlahan manual.

Praktik: Pecahan dengan gcd dan lcm #

Pasangan gcd-lcm punya use case klasik yang menghubungkan keduanya: aritmetika pecahan. Gcd menyederhanakan pecahan, dan lcm mencari penyebut bersama untuk penjumlahan. Program berikut memakai keduanya dalam satu alur.

cpp
#include <iostream>  // std::cout
#include <numeric>   // std::gcd, std::lcm

// sederhanakan pecahan: bagi pembilang dan penyebut dengan gcd-nya
void sederhanakan(int& pembilang, int& penyebut) {
    int g = std::gcd(pembilang, penyebut);
    if (g != 0) {
        pembilang /= g;
        penyebut /= g;
    }
}

int main() {
    // satu pecahan disederhanakan
    int p = 48, q = 18;
    sederhanakan(p, q);
    std::cout << "48/18       = " << p << "/" << q << "\n";

    // penjumlahan pecahan: penyebut bersama dari lcm
    int p1 = 3, q1 = 4;   // 3/4
    int p2 = 5, q2 = 6;   // 5/6
    int k = std::lcm(q1, q2);
    int hasilP = p1 * (k / q1) + p2 * (k / q2);
    int hasilQ = k;
    sederhanakan(hasilP, hasilQ);
    std::cout << "3/4 + 5/6   = " << hasilP << "/" << hasilQ << "\n";
    return 0;
}

Output yang Dihasilkan:

text
48/18       = 8/3
3/4 + 5/6   = 19/12
  1. Baris 5-11 (sederhanakan): fungsi menerima kedua bagian pecahan lewat referensi, membagi keduanya dengan gcd-nya, dan memeriksa g != 0 agar pecahan nol tidak dibagi nol. 48/18 dengan gcd 6 menjadi 8/3.
  2. Baris 22-24 (penjumlahan): penyebut bersama diambil dari std::lcm(q1, q2) — lcm(4, 6) memberi 12 — lalu tiap pembilang dikalikan faktor penguatnya: 3 * (12/4) + 5 * (12/6) = 9 + 10 = 19.
  3. Baris 25-26 (hasil akhir): hasil penjumlahan 19/12 disederhanakan sekali lagi; karena gcd(19, 12) = 1, pecahan itu sudah paling ringkas.

Ringkasan Header <numeric> #

Sekarang isi header <numeric> kita lengkapi. Tabel berikut memetakan setiap fungsi beserta pola kerjanya, agar chapter ini bisa dijadikan catatan pintas:

FungsiBekerja padaKeterangan
std::accumulate, std::reducerentang iteratoragregasi seluruh koleksi ke satu nilai
std::iotarentang iteratormengisi deret bertambah satu
std::inner_productdua rentangjumlah hasil kali pasangan elemen
std::partial_sumrentang iteratorjumlah berjalan (prefix sum)
std::adjacent_differencerentang iteratorselisih elemen bersebelahan
std::gcddua bilangan bulatpembagi bersama terbesar
std::lcmdua bilangan bulatkelipatan bersama terkecil
std::midpointdua angka / dua pointertitik tengah, aman dari luapan

Lima baris pertama telah dibahas dengan contoh penuh pada Chapter Algoritma Numerik <numeric> di Section Iterators dan Algorithms, sedangkan tiga baris terakhir adalah milik chapter ini. Pembagian tugasnya mudah diingat: algoritma rentang mengolah koleksi, fungsi titik menghitung jawaban tunggal dari beberapa angka.

Dengan <numeric> tuntas, kita kembali ke header yang lebih dikenal: <utility> dan <algorithm> menyimpan fungsi-fungsi kecil serbaguna — std::swap, std::exchange, std::min/max/clamp — yang akan kita bahas pada chapter berikutnya.

Rujukan resmi: cppreference — header <numeric> , cppreference — std::gcd dan std::lcm , cppreference — std::midpoint .