Nega 0.1 + 0.2 ≠ 0.3: suzuvchi nuqtali sonlar va pul kodda
Amaliyot3 daqInnovialabs tahririyati
Qisqacha
Float sonlarni ikkilik tizimda taxminan saqlaydi, shuning uchun pul eng kichik birlikdagi butun son (tiyin, sent) yoki Decimal va NUMERIC kabi o‘nlik turlarda saqlanadi.
Qisqa javob
Ko‘pchilik dasturlash tillari kasr sonlarni IEEE 754 standarti bo‘yicha — ikkilik tizimda saqlaydi. Ikkilik tizimda 0.1 cheksiz kasr, xuddi o‘nlik tizimdagi 1/3 kabi. Kompyuter uni kesib tashlaydi va xotirada juda yaqin, lekin aniq bo‘lmagan qiymat qoladi.
0.1 + 0.2 // 0.30000000000000004
0.1 + 0.2 === 0.3 // false
Ilmiy hisob-kitoblar uchun bunday xatolik odatda ahamiyatsiz. Pul uchun esa muhim: bitta operatsiyada yo‘qolgan tiyin hisobotdagi tafovutga aylanadi, to‘lov summasi esa chekdagi summaga mos kelmay qoladi.
Qoida: pulni float va double da saqlamang va hisoblamang.
Float qanday tuzilgan
IEEE 754 formatidagi son uch qismdan iborat: ishora, mantissa (ahamiyatli raqamlar) va eksponenta (nuqtani qayerga surish). double turi taxminan 15–17 ta ahamiyatli o‘nlik raqam beradi.
Bundan ikki xil muammo kelib chiqadi:
- Ifodalab bo‘lmaydigan kasrlar. 0.1, 0.2, 0.7 ni ikkilik tizimda aniq yozib bo‘lmaydi.
- Katta sonlarda aniqlik yo‘qolishi. Son juda katta bo‘lsa, kichik razryadlar mantissaga sig‘maydi.
Xatolar yig‘iladi: 0.1 ni ming marta qo‘shing — natija 100 dan sezilarli farq qiladi.
1-usul: eng kichik birlikdagi butun sonlar
Summani valyutaning eng kichik birligida butun son sifatida saqlang: dollar uchun sent, so‘m uchun tiyin.
- 15 000,50 so‘m →
1500050tiyin - $19.99 →
1999sent
Butun sonlar aniq qo‘shiladi va ayiriladi. Odam o‘qiydigan ko‘rinishga faqat ekranga chiqarishda o‘tkaziladi.
price_tiyin = 1_500_050
qty = 3
total_tiyin = price_tiyin * qty # 4 500 150, aniq
print(f"{total_tiyin / 100:,.2f} so‘m") # faqat ko‘rsatish uchun
Yodda tuting: valyutalarda verguldan keyingi xonalar soni har xil. ISO 4217 bo‘yicha o‘zbek so‘mida ular ikkita, garchi tiyinlar naqd muomalada deyarli uchramasa ham. Ko‘p tizimlar so‘mni to‘g‘ridan-to‘g‘ri butun so‘mlarda hisoblaydi — bu talablarda qayd etilgan bo‘lsa, maqbul.
2-usul: o‘nlik (decimal) turlar
O‘nlik turlar sonni o‘nlik tizimda saqlaydi va ikkilik xatolarsiz hisoblaydi.
| Muhit | Tur |
|---|---|
| Python | decimal.Decimal |
| Java | BigDecimal |
| C# | decimal |
| PostgreSQL, MySQL | NUMERIC(p, s) / DECIMAL(p, s) |
| JavaScript | o‘nlik arifmetika uchun tashqi kutubxonalar |
from decimal import Decimal, ROUND_HALF_UP
a = Decimal("0.1") + Decimal("0.2") # natija: aniq 0.3
vat = (Decimal("15000.50") * Decimal("0.12")).quantize(
Decimal("0.01"), rounding=ROUND_HALF_UP
)
Decimal ni float dan emas, satrdan yarating: Decimal(0.1) o‘sha xatolikni meros qilib oladi.
Yaxlitlash — biznes qoidasi
Aniq turlar bilan ham pulni yaxlitlashga to‘g‘ri keladi: soliqlar, chegirmalar, valyuta kurslari. Oldindan belgilab oling:
- Yaxlitlash rejimi. Matematik «yarmi yuqoriga» yoki bank usuli (juftga qarab). Masalan, Python dagi o‘rnatilgan
round()bank usulidan foydalanadi. - Yaxlitlash payti. Chekning har bir qatorida yoki faqat yakuniy summada — natijalar farq qilishi mumkin.
- Summani bo‘lish. 100 uchga teng bo‘linmaydi: 33.33 + 33.33 + 33.34. Qoldiq aniq tarzda qismlardan biriga beriladi.
Loyiha uchun chek-list
- Ma’lumotlar bazasida —
NUMERICyoki eng kichik birlikdagi butun son, hech qachonFLOATyokiREALemas. - API da — summalar satr (
"15000.50") yoki eng kichik birlikdagi butun son ko‘rinishida, yonida valyuta kodi bilan. - Valyuta summa bilan birga saqlanadi, taxmin qilinmaydi.
- Yaxlitlash qoidalari talablarda yozilgan va testlar bilan qoplangan.
- Valyuta kurslari yetarli aniqlik va sana bilan saqlanadi.
- Frontend summalarni faqat ko‘rsatadi, yakuniy hisobni server qiladi.
Ko‘p uchraydigan xatolar
- Float larni
==orqali solishtirish. - Formadan kelgan summalarga
parseFloatqo‘llab, keyin arifmetika qilish. - Aniqlik bo‘yicha kelishuvsiz JSON da
moneyturi yoki float. - Qoidasiz har bir oraliq bosqichda yaxlitlash.