Bir tutarın ekranda doğru görünmesi, hesaplamaya doğru girdiği anlamına gelmez. “1.234,50” değerini makinenin kültürüne bırakırsam farklı bir ortamda farklı yorum veya parse hatası alabilirim. Rapor ve servis dönüşümlerinde önce sayının tel biçimini, sonra hesaplama tipini, en son yuvarlama kuralını belirlerim.
Metin biçimi ile sayısal değeri ayırmak
Örnekteki giriş açıkça tr-TR biçiminde: nokta binlik, virgül ondalık ayırıcı. decimal.Parse bu sözleşmeyle 1234.50 değerini elde ediyor. 1.25 katsayısı yalnız hesabı izlemek için seçilmiş örnek bir oran; gerçek veya güncel döviz kuru iddiası yok. Kod .NET 10 konsolunda çalışır ve 1543.13 yazar.
using System.Globalization;
var tr = CultureInfo.GetCultureInfo("tr-TR");
var amount = decimal.Parse("1.234,50", NumberStyles.Number, tr);
var rate = 1.25m;
var converted = Math.Round(amount*rate,2,MidpointRounding.AwayFromZero);
Console.WriteLine(converted.ToString("0.00", CultureInfo.InvariantCulture));
InvariantCulture yalnız çıktının 1543.13 biçiminde, makineden bağımsız metin olması için kullanılıyor. Türkçe kullanıcı ekranında bu değeri farklı biçimlendirebilirim; aynı sayının gösterimi değişir. Kullanıcı arayüzü metnini doğrudan API veya veri deposu sözleşmesi saymak ise hesaplamayı görsel tercihlere bağlar.
decimal seçimi neyi düzeltir?
Ondalık iş değerlerinde decimal, 0.1 gibi birçok ondalık kesri ikili kayan noktalı türlerin temsil farkı olmadan tutabilir. Yine de sınırsız hassasiyet sunmaz; aralık ve basamak sınırları vardır. Kaynak veriyi önce double'a çevirip sonra decimal yapmak, aradaki temsil kaybını geri almaz. Ondalık sabitlerde m son eki de bu yüzden örneğin başından sonuna kadar korunuyor.
Parse başarılı olması iş açısından kabul için yeterli değildir. Tutar negatif olabilir mi, kaç ondalık basamak kabul edilir, oran sıfır veya negatif olabilir mi? Bunları ayrıca doğrularım. Dış girdide Parse yerine TryParse ile anlaşılır alan hatası üretmek uygundur; burada sabit, kontrol edilen metin kısa örneği okunur tutuyor.
Yuvarlama noktası sonuç kadar önemlidir
1234.50 × 1.25 işlemi 1543.125 eder. İki basamak ve AwayFromZero seçimi bunu 1543.13 yapar. ToEven başka bir bağ kuralıdır; varsayılan metoda güvenip hangi kuralın seçildiğini görünmez bırakmam. AwayFromZero'nun negatif yarım değerlerde de sıfırdan uzaklaşacağını hatırlamak gerekir.
Console.WriteLine(Math.Round(1.005m, 2, MidpointRounding.ToEven));
Console.WriteLine(Math.Round(1.005m, 2, MidpointRounding.AwayFromZero));
Console.WriteLine(Math.Round(-1.005m, 2, MidpointRounding.AwayFromZero));
Beklenen sayısal sonuçlar 1.00, 1.01 ve -1.01. Gösterilen basamak sayısı ayrıca biçimlendirmeye bağlıdır. Hangi yuvarlama kuralının doğru olduğu iş sözleşmesine aittir; bu örnek bir muhasebe veya mevzuat kuralı önermiyor.
Satır toplamı ile belge toplamı neden ayrışır?
İki satırın her biri 0.015 olsun. Her satırı iki basamağa yuvarlayıp toplarsam AwayFromZero ile 0.04 çıkar. Önce 0.030 olarak toplayıp en sonda yuvarlarsam 0.03 çıkar. İki hesap da kendi işlem sırasına göre tutarlıdır; asıl karar raporun hangi sırayı sözleşme kabul ettiğidir. Bu farkı “küsurat hatası” diye gizlemek yerine satır ve toplam politikasını aynı yerde tanımlarım.
Kalıcı sütunun precision/scale değeri de uygulamayla uyumlu olmalı. Uygulama dört basamak taşıyıp veritabanı iki basamağa indiriyorsa gerçek yuvarlama noktası kodda görünen yer olmayabilir. API, veritabanı ve rapor aynı örnek değerlerle birlikte denenmeli.
Sınır değerleri görünür tutmak
Kontrolde Türkçe ve invariant metni, boş girdi, yanlış ayraç, negatif tutar, yarım değerler ve aralık dışı sayılar bulunmalı. Testlerde sonucu önce decimal olarak karşılaştırır, ardından gösterim metnini ayrı sınarım. Böylece hesap doğruyken biçim hatasını, biçim doğruyken hesap hatasını birbirine karıştırmam.