Blog'a Dön

Geliştiriciler için para birimi yuvarlama kuralları: ondalıklar, alt birimler ve güvenli dönüşüm

V
Vlado Grigirov
August 21, 2026
Currency API Exchange Rates Currency Rounding Minor Units ISO 4217 Finexly Developer Guide

Tokyo'daki bir müşteri 19,99 dolarlık bir abonelik satın alıyor. Kodunuz USD/JPY kuruyla çarpıyor, 2942,82785 sonucunu alıyor, bunu veritabanına yazıyor ve ödeme sağlayıcınıza gönderiyor. Sağlayıcı bunu reddediyor — ya da daha kötüsü, kabul edip 100 katı tutarı tahsil ediyor. Yen'in ondalık basamağı yok ve kodunuz bunu hiç sormadı.

Para birimi yuvarlama, üretime ulaşana kadar önemsiz görünen problemlerden biridir. Bu bir biçimlendirme meselesi değil, doğruluk meselesidir. Her para biriminin kendi ondalık basamak sayısı vardır, kayan noktalı aritmetik tutarları sessizce bozar ve para birimleri arasında dönüşüm yaptığınız anda bilinçli olarak verilmesi gereken bir yuvarlama kararı ortaya çıkar. Bu rehber gerçekten önemli olan kuralları ele alıyor: her para biriminin kaç ondalığı var, tutarları neden tamsayı olarak saklamalısınız, hangi yuvarlama modunu seçmelisiniz ve ay sonunda defteriniz hâlâ tutsun diye bir döviz dönüşümünü nasıl yuvarlamalısınız.

Alt birimler: her para biriminin kaç ondalık basamağı var?

Bir para biriminin alt birimi, işlem görebilen en küçük alt bölümüdür. ABD doları için bu senttir; dolayısıyla USD'nin iki ondalık basamağı vardır ve 19,99 dolar 1999 senttir. Ödeme sağlayıcılarının beklediği gösterim budur ve veritabanınızın kullanması gereken de budur.

ISO 4217 — size USD ve JPY gibi üç harfli kodları veren aynı standart — her para birimine bir alt birim üssü de atar. Çoğu geliştirici bu üssün her zaman 2 olduğunu varsayar. Değildir ve bu varsayım bu alandaki en pahalı hatadır.

Ondalıksız para birimleri

Bu para birimlerinin dolaşımda bir alt birimi yoktur; gönderdiğiniz tutar doğrudan tam birim sayısıdır:

  • JPY — Japon yeni
  • KRW — Güney Kore wonu
  • VND — Vietnam dongu
  • CLP — Şili pesosu
  • ISK — İzlanda kronu
  • XAF / XOF / XPF — CFA ve CFP frangı
  • UGX — Uganda şilini
  • PYG — Paraguay guaranisi
  • RWF, GNF, KMF, DJF, VUV — ve küçük kupürlü birkaç para birimi daha

JPY'yi iki ondalıklı bir para birimi gibi ele alıp sağlayıcıya göndermeden önce 100 ile çarparsanız, müşterinizden amaçladığınızın 100 katını tahsil etmiş olursunuz.

Üç ondalıklı para birimleri

Yedi para birimi yüzde bir yerine binde bire bölünür:

  • KWD — Kuveyt dinarı (1000 fils)
  • BHD — Bahreyn dinarı (1000 fils)
  • OMR — Umman riyali (1000 baisa)
  • JOD — Ürdün dinarı (1000 fils)
  • TND — Tunus dinarı (1000 milim)
  • IQD — Irak dinarı (1000 fils)
  • LYD — Libya dinarı (1000 dirhem)

Burada hata ters yönde ilerler: KWD'yi iki ondalıklı sayarsanız amaçladığınızın onda birini tahsil edersiniz. KWD 12,500 tutarındaki bir fatura KWD 1,250 olur.

ISO 4217'de dört ondalıklı kayıtlar bile var — Şili'nin unidad de fomento'su (CLF) ve Uruguay'ın unidad previsional'ı (UYW). Bunlar nakitten çok endekslenmiş muhasebe birimleridir; ama sisteminiz keyfi ISO kodları kabul ediyorsa bunlara da dayanmalıdır.

Standart ile sağlayıcınız ayrıştığında

Diğer her şeyi doğru yapan ekipleri yakalayan tuzak budur. Ödeme sağlayıcıları operasyonel nedenlerle bazen ISO 4217'den sapar. Örneğin Adyen, CLP, CVE, IDR ve ISK'nin API'sinde standarttakinden farklı sayıda ondalık aldığını belgeler — ISK, ISO 4217'ye göre sıfır ondalıklıdır ama Adyen'e iki ondalıkla gönderilmelidir.

Kural şu: yuvarlama tablonuz, konuştuğunuz sistemin bir özelliğidir; evrensel bir sabit değildir. Her entegrasyon için bir tablo tutun, ISO 4217'den başlatın ve sağlayıcının dokümantasyonu gerektirdiği yerlerde onun için geçersiz kılın. Asla 100 değerini sabit yazmayın.

Parayı asla float olarak saklamayın

Yuvarlama tartışmasından önce temel. İkili kayan nokta çoğu ondalık kesri tam olarak temsil edemez:

0.1 + 0.2              // 0.30000000000000004
1.005 * 100            // 100.49999999999999
19.99 * 147.2150       // 2942.8278499999997

Bu son basamaklar kozmetik değildir. Yanlış anda Math.round() içinden geçirin, bir alt birim kayan bir tutar elde edersiniz; bu da mutabakatın tutmaması için yeterlidir.

İki kural neredeyse her durumu kapsar:

  1. Tutarları alt birim cinsinden tamsayı olarak saklayın. Bir amount_minor BIGINT sütunu artı bir currency CHAR(3) sütunu. { amount_minor: 1999, currency: "USD" } belirsiz değildir ve Stripe, Adyen ile çoğu sağlayıcının zaten beklediği şeydir.
  2. Aritmetiği tamsayılarla veya bir ondalık tiple yapın. Python'da decimal.Decimal, Java'da BigDecimal, PostgreSQL'de NUMERIC veya tamsayı aritmetiğini saran bir JavaScript para kütüphanesi. Float'ları döviz kurunun kendisine ayırın; o da yalnızca çarpma noktasına kadar.

Bu katmanı sıfırdan tasarlıyorsanız, çok para birimli defter tasarımı rehberimiz şema kararlarını daha derinlemesine ele alıyor.

Dönüşüm hattı: alt birim girer, alt birim çıkar

Para birimi dönüşümünün tam olarak dört adımı vardır ve yuvarlama üçüncü adıma aittir — bir kez, en sonda.

  1. Kaynak tutarı alt birimlerden ondalık bir değere çevirin.
  2. Tam hassasiyetli döviz kuruyla çarpın.
  3. Hedef para biriminin alt birim üssüne yuvarlayın.
  4. Tekrar tamsayı alt birimlere çevirin.

İşte JavaScript hâli; işi para birimi tablosu yapıyor:

// Minor unit exponents. Seed from ISO 4217, override per payment provider.
const MINOR_UNITS = {
  USD: 2, EUR: 2, GBP: 2, CHF: 2, CAD: 2, AUD: 2, CNY: 2, INR: 2,
  JPY: 0, KRW: 0, VND: 0, CLP: 0, ISK: 0, XAF: 0, XOF: 0, XPF: 0,
  KWD: 3, BHD: 3, OMR: 3, JOD: 3, TND: 3, IQD: 3, LYD: 3,
};

function exponentFor(currency) {
  const e = MINOR_UNITS[currency];
  if (e === undefined) throw new Error(`Unknown minor unit for ${currency}`);
  return e;
}

/**
 * Convert an integer minor-unit amount from one currency to another.
 * Returns an integer in the target currency's minor units.
 */
function convertMinor(amountMinor, from, to, rate) {
  const fromExp = exponentFor(from);
  const toExp = exponentFor(to);

  const decimalAmount = amountMinor / 10 ** fromExp;   // 1999 -> 19.99
  const converted = decimalAmount * rate;              // full precision, no rounding yet
  return Math.round(converted * 10 ** toExp);          // single rounding step
}

convertMinor(1999, "USD", "JPY", 147.2150);   // 2943      (¥2,943)
convertMinor(1999, "USD", "KWD", 0.30590);    // 6115      (KWD 6.115)
convertMinor(1999, "USD", "EUR", 0.9241);     // 1847      (€18.47)

Fonksiyonun yapmadığı şeylere dikkat edin: kuru asla yuvarlamıyor, ara değeri asla yuvarlamıyor ve asla iki ondalık varsaymıyor. Buradaki Math.round, pozitiflerde yukarı yuvarlamadır — bir ödeme akışı için uygun, ama düzenlemeye tabi bir işte kullanmadan önce sonraki bölümü okuyun.

Yuvarlama modunu seçmek

"İki ondalığa yuvarla" bir şartname değildir. Eşitliği bozmanın savunulabilir en az beş yolu vardır ve finansal sistemler hangisini seçtiğinizi önemser.

Mod2,5 →3,5 →−2,5 →Tipik kullanım
Yarımı yukarı (half up)34−3Tüketici fiyatları, ödeme toplamları
Yarımı çifte (bankacı yuvarlaması)24−2Muhasebe, faiz, vergi, raporlama
Yarımı aşağı (half down)23−2Nadir; bazen eski finansal kodda
Yukarı (ceiling)34−2Asla eksik tahsil edilmemesi gereken ücretler
Aşağı (floor / kırpma)23−3Asla fazla ödenmemesi gereken hakedişler
Half up, çoğu kişinin "yuvarlama" ile kastettiği şeydir ve Math.round()'un pozitif sayılarda yaptığıdır. Sezgiseldir ve müşterinin birazdan göreceği bir fiyat için uygundur.

Half even, yani bankacı yuvarlaması, tam yarımları en yakın çift basamağa gönderir. Çok sayıda işlem boyunca half-up'ın getirdiği sistematik yukarı yönlü sapmayı ortadan kaldırır; bu yüzden muhasebe sistemlerinde, Python'un decimal modülünde ve IEEE 754'ün kendisinde varsayılandır. Binlerce dönüştürülmüş tutarı bir gelir raporunda topluyorsanız, half-up toplamı sessizce şişirir; half-even şişirmez.

Yukarı ve aşağı yuvarlama asimetrik riskler için vardır. Satıcılara ödeme yapan bir pazar yeri, elindekinden fazlasını asla dağıtmamak için her ödemeyi aşağı yuvarlayabilir; fark bir yuvarlama hesabına gider.

Python bu seçimi açık hâle getirir; doğru ergonomi budur:

from decimal import Decimal, ROUND_HALF_EVEN, ROUND_HALF_UP

MINOR_UNITS = {"USD": 2, "EUR": 2, "JPY": 0, "KWD": 3}

def convert_minor(amount_minor: int, src: str, dst: str,
                  rate: str, mode=ROUND_HALF_EVEN) -> int:
    """Convert integer minor units to integer minor units, exactly once."""
    src_exp, dst_exp = MINOR_UNITS[src], MINOR_UNITS[dst]

    amount = Decimal(amount_minor) / (Decimal(10) ** src_exp)
    converted = amount * Decimal(rate)          # rate passed as a string, not a float

    quantum = Decimal(1).scaleb(-dst_exp)       # 0.01, 1, or 0.001
    rounded = converted.quantize(quantum, rounding=mode)
    return int(rounded.scaleb(dst_exp))

convert_minor(1999, "USD", "JPY", "147.2150")   # 2943
convert_minor(1999, "USD", "KWD", "0.30590")    # 6115

Kuru Decimal'e metin olarak geçirmek önemlidir. Decimal(0.9241) float'ın hatasını devralır; Decimal("0.9241") devralmaz.

Gerçek paraya mal olan üç yuvarlama hatası

1. Çarpmadan önce kuru yuvarlamak

Döviz kurları genelde dört ila altı anlamlı ondalık taşır ve bunları kırpmak zararsız değildir. USD/JPY 147,2150 ve 10.000 dolarlık bir transfer düşünün:

  • Tam kur: 10000 × 147.2150 = ¥1.472.150
  • İki ondalığa yuvarlanmış kur (147,21): 10000 × 147.21 = ¥1.472.100

Tek bir işlemde ¥50 fark — sırf kur kullanılmadan önce biçimlendirildiği için. Kuru sağlayıcınızın döndürdüğü hassasiyetle saklayın, yalnızca sonuç tutarını yuvarlayın ve denetim için kullandığınız tam kuru işlemle birlikte kalıcı hâle getirin. Döviz kuru API'leri verilerini nereden alır rehberimiz bu hassasiyetin neden en baştan anlamlı olduğunu açıklıyor.

2. Çok bacaklı dönüşümde iki kez yuvarlamak

USD → EUR → JPY rotasını izleyip EUR adımında yuvarlarsanız, ikinci çarpmanın büyüteceği hassasiyeti atmış olursunuz. USD/EUR 0,9241 ve EUR/JPY 159,3063 ile 12,34 doları dönüştürelim:

  • Doğrudan: 12.34 × 147.2150 = 1816.63¥1.817
  • Yuvarlanmış EUR bacağı üzerinden: 12.34 × 0.9241 = 11.4034 → 11,40 €'ya yuvarlanır → 11.40 × 159.3063 = 1816.09¥1.816

Gereksiz bir yuvarlama adımı yüzünden bir yen. Elli bin işlemlik bir ödeme partisinde bu bir mutabakat kaydı demektir. Doğrudan parite varsa onu kullanın; üçgenlemek zorundaysanız ara değeri tam hassasiyette tutun. Mekanik için çapraz kurlar açıklaması sayfasına bakın.

3. Toplamı tutmayan kalemler

Bir faturanın her satırını bağımsız yuvarlarsanız, parçalar her zaman yuvarlanmış toplamı vermez. Klasik örnek bir bölüşümdür:

$10.00 split three ways
  10.00 / 3 = 3.3333...
  → 3.33 + 3.33 + 3.33 = 9.99   ✗ one cent missing

Çözüm yuvarlama değil, dağıtımdır. Toplamı bir kez yuvarlayın, sonra kalanı birer alt birim hâlinde dağıtarak parçalara paylaştırın:

/**
 * Split an integer minor-unit total into `n` parts whose sum is exactly the total.
 * Remainder units are distributed to the earliest parts (largest-remainder method).
 */
function allocate(totalMinor, ratios) {
  const sum = ratios.reduce((a, b) => a + b, 0);
  const shares = ratios.map(r => Math.floor((totalMinor * r) / sum));
  let remainder = totalMinor - shares.reduce((a, b) => a + b, 0);

  for (let i = 0; remainder > 0; i = (i + 1) % shares.length, remainder--) {
    shares[i] += 1;
  }
  return shares;
}

allocate(1000, [1, 1, 1]);   // [334, 333, 333]        → sums to exactly 1000
allocate(9247, [3, 2, 1]);   // [4624, 3082, 1541]     → sums to exactly 9247

Aynı deseni döviz dönüşümünden sonra da uygulayın: fatura toplamını dönüştürüp yuvarlayın, sonra bu toplamı satırlara dağıtın. Satırlar her zaman tutar, çünkü bağımsız hesaplanmak yerine toplamdan türetilmiştir. Bu en çok çok para birimli faturalamada ve orantılı hesaplamalı SaaS faturalamada önemlidir; bir kuruşluk sapma müşterinin gördüğü PDF'te belirir.

Nakit yuvarlama ayrı bir kuraldır

Bir para biriminin alt birimi, kaydedilebilecek en küçük tutarı söyler. Nakit olarak ödenebilecek en küçük tutarı her zaman söylemez. Birkaç ülke en küçük madeni paralarını tedavülden kaldırdı ve kasada nakit ödemeleri yuvarlıyor:

  • İsviçre — nakit en yakın 0,05 CHF'ye yuvarlanır
  • Kanada — bir sentlik madeni para 2013'te kaldırıldı; nakit en yakın 5 sente yuvarlanır
  • İsveç — nakit en yakın tam krona yuvarlanır
  • Hollanda — nakit en yakın 5 sente yuvarlanır

Kritik nokta: bu, faturaya değil nakit ödemeye uygulanır. 12,32 CHF'lik bir İsviçre faturası hâlâ 12,32 olarak kaydedilir; yalnızca nakit tahsilat 12,30'a yuvarlanır ve 0,02'lik fark yuvarlama düzeltmesi olarak kaydedilir. Satış noktası yazılımı geliştiriyorsanız, nakit yuvarlamayı ödemeye uygulanan ayrı ve sonraki bir adım olarak modelleyin — asla saklanan tutara gömmeyin, yoksa elektronik ve nakit işlemleriniz birbirini tutmaz.

Biçimlendirme son adımdır, hesap değil

Aritmetik bittiğinde gösterimi yerel ayarları bilen bir biçimlendiriciye devredin. Intl.NumberFormat her para biriminin ondalık sayısını, sembol konumunu ve ayırıcılarını zaten bilir:

function formatMinor(amountMinor, currency, locale = "en-US") {
  const exp = exponentFor(currency);
  return new Intl.NumberFormat(locale, {
    style: "currency",
    currency,
  }).format(amountMinor / 10 ** exp);
}

formatMinor(1999, "USD");            // "$19.99"
formatMinor(2943, "JPY", "ja-JP");   // "¥2,943"
formatMinor(6115, "KWD");            // "KWD 6.115"
formatMinor(1847, "EUR", "de-DE");   // "18,47 €"

İki pratik not. Birincisi, Intl.NumberFormat örnekleri oluşturması pahalıdır — her satır için bir tane üretmek yerine her yerel ayar-para birimi çifti için bir tanesini önbelleğe alın. İkincisi, son satırdaki 10 ** exp bölmesi, bir float'ın parasal bir değere dokunması gereken tek yerdir ve bu da yalnızca sonucun hemen metne dönüşmesi nedeniyledir.

Finexly API ile hepsini bir araya getirmek

Kuru tam hassasiyetle çekin, bir kez dönüştürün, bir kez yuvarlayın ve kullandığınız kuru saklayın:

curl "https://api.finexly.com/v1/latest?base=USD&symbols=JPY,KWD,EUR" \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "success": true,
  "base": "USD",
  "timestamp": 1755244800,
  "rates": {
    "JPY": 147.2150,
    "KWD": 0.30590,
    "EUR": 0.9241
  }
}
async function quote(amountMinor, from, to) {
  const res = await fetch(
    `https://api.finexly.com/v1/latest?base=${from}&symbols=${to}`,
    { headers: { Authorization: `Bearer ${process.env.FINEXLY_API_KEY}` } }
  );
  const data = await res.json();
  const rate = data.rates[to];

  return {
    amount_minor: convertMinor(amountMinor, from, to, rate),
    currency: to,
    rate,                              // persist the exact rate used
    rate_timestamp: data.timestamp,    // and when it was captured
  };
}

await quote(1999, "USD", "JPY");
// { amount_minor: 2943, currency: "JPY", rate: 147.215, rate_timestamp: 1755244800 }

İşlem satırında rate ve rate_timestamp saklamak, altı ay sonraki bir itirazın yanıtlanabilir olmasını sağlayan şeydir. Uç nokta ve parametrelerin tüm ayrıntıları Finexly API dokümantasyonunda; istekler arasında kurları önbelleğe alıyorsanız önbellekleme ve hata yönetimi notlarımız tazelik ödünleşimlerini ele alıyor.

Bir test kontrol listesi

Para hataları kimsenin test yazmadığı durumlarda saklanır. En azından şunları kapsayın:

  1. Ondalıksız bir hedef — JPY veya KRW'ye dönüştürüp sonucun kesirli kısmı olmadığını doğrulayın.
  2. Üç ondalıklı bir hedef — KWD veya BHD'ye dönüştürüp üç ondalığın korunduğunu doğrulayın.
  3. Tam yarımlar — seçtiğiniz yuvarlama modunu negatifler dâhil iki yönde de doğrulayın.
  4. Gidiş-dönüş sapması — USD → EUR → USD dönüştürüp sonucun eşit değil, bir alt birim içinde olduğunu doğrulayın.
  5. Dağıtım değişmezi — bölünen parçaların 1'den 100'e kadar her durumda tam olarak toplamı verdiğini doğrulayın.
  6. Bilinmeyen para birimi kodları — kodun sessizce iki ondalığa düşmek yerine hata fırlattığını doğrulayın.
  7. Çok büyük tutarlar — JavaScript'te Number.MAX_SAFE_INTEGER ötesinde hassasiyet kaybı olmadığını doğrulayın; IDR veya VND'yi ölçekli işliyorsanız BigInt kullanın.

Sık sorulan sorular

Her para biriminin kaç ondalık basamağı var?

Çoğunun iki. Yaklaşık yirmi küsurunun hiç yok — JPY, KRW, VND, CLP ve ISK dâhil — yedisinin ise üç var: KWD, BHD, OMR, JOD, TND, IQD ve LYD. ISO 4217 yetkili kaynaktır, ama ödeme sağlayıcınızın tablosuna da bakın; bazıları operasyonel nedenlerle sapar.

Döviz kurlarını mı yoksa dönüştürülmüş tutarları mı yuvarlamalıyım?

Yalnızca dönüştürülmüş tutarları. Kuru sağlayıcınızın döndürdüğü tam hassasiyette tutun, çarpın, sonra sonucu hedef para biriminin alt birimine bir kez yuvarlayın. Çarpmadan önce kuru yuvarlamak, işlem büyüklüğüyle orantılı bir hata getirir.

Half-up ile bankacı yuvarlaması arasındaki fark nedir?

Half-up tam bir yarımı her zaman sıfırdan uzağa gönderir (2,5 → 3). Bankacı yuvarlaması — yarımı çifte — onu en yakın çift basamağa gönderir (2,5 → 2, 3,5 → 4) ve çok sayıda tutarı topladığınızda sistematik yukarı sapmayı ortadan kaldırır. Müşteriye gösterilen fiyatlarda half-up, muhasebe ve raporlamada half-even kullanın.

Dönüştürülmüş kalemlerim neden dönüştürülmüş toplamı vermiyor?

Çünkü her satır bağımsız yuvarlandı ve hatalar birikti. Toplamı bir kez yuvarlayın, sonra en büyük kalan yöntemiyle satırlara dağıtın. Parçalar o zaman yapısı gereği bütünü verir.

Parayı iki ondalıklı float olarak saklayamaz mıyım?

Hayır. İkili kayan nokta 0,1 gibi değerleri tam temsil edemez; bu yüzden hatalar toplama ve çarpmalarda birikir ve sonunda bir yuvarlama kararını ters çevirir. Alt birim cinsinden tamsayı saklayın veya tam bir ondalık tip kullanın. Bu teorik bir kaygı değil — bir kuruşluk mutabakat hatalarının en yaygın kök nedenidir.

Tam hassasiyetli kurlar alın

Doğru yuvarlama, güvenebileceğiniz bir kur ve atmadığınız hassasiyetle başlar. Ücretsiz Finexly API anahtarınızı alın — kredi kartı gerekmez. 170'ten fazla para birimi için ayda 1.000 ücretsiz istekle başlayın, hesaplarınızı para birimi çeviricisiyle kontrol edin ve hacminiz büyüdüğünde fiyat planlarını inceleyin.

Vlado Grigirov

Senior Currency Markets Analyst & Financial Strategist

Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...

View full profile →