ویژگی تصویر

کتابخانه decimal در پایتون — راهنمای کامل برای دقت عددی و محاسبات مالی

  /  پایتون   /  کتابخانه decimal در پایتون
بنر تبلیغاتی الف

کتابخانهٔ decimal در پایتون ابزاری قدرتمند برای انجام محاسبات با دقت دهدهی (decimal) فراهم می‌کند. برخلاف اعداد اعشاریِ ممیز شناور (float) که برای محاسبات دودویی بهینه‌سازی شده‌اند و خطاهای نمایش‌دهی دارند، Decimal برای کاربردهایی طراحی شده که دقت قطعی و قواعد گرد کردن مشخص لازم است — مثلاً صورت‌های مالی، محاسبهٔ مالیات، و گزارش‌های حسابداری.

چرا از decimal استفاده کنیم؟

  • دقت نمایش دقیق اعداد دهدهی بدون خطای نمایش دودویی.
  • قابلیت تنظیم دقیقِ دقت (precision) و حالت‌های گرد کردن (rounding modes).
  • پشتیبانی از قواعد مالی و حسابداری مانند ROUND_HALF_EVEN یا ROUND_DOWN.
  • روشی امن‌تر برای نمایش نتیجهٔ محاسبات حساس به گرد کردن (مثلاً تراکنش‌های ارزی).

مفاهیم پایه و نمونهٔ ساده

from decimal import Decimal, getcontext

getcontext().prec = 28  # تنظیم دقت پیش‌فرض
a = Decimal('0.1')
b = Decimal('0.2')
c = a + b
print(c)  # 0.3 به صورت دقیق

در این مثال، با استفاده از رشته‌ها Decimal ساخته شده‌اند تا از خطای تبدیلِ float جلوگیری شود. خروجی دقیقاً “0.3” خواهد بود، برخلاف جمع float که ممکن است با نمایش‌های تکراری روبه‌رو شود.

تبدیل از float — اشتباه رایج و راه‌حل

from decimal import Decimal

x = 0.1
print(Decimal(x))        # نامطلوب: Decimal('0.1000000000000000055511151231257827021181583404541015625')
print(Decimal(str(x)))   # خوب: Decimal('0.1')
print(Decimal(1) / Decimal(3))  # تقسیم دقیق اما با دقت پیش‌فرض

اگر Decimal را مستقیم از مقدار float بسازید، خطای نمایش float وارد Decimal می‌شود. بهترین روش این است که از رشته یا از عدد صحیح (در صورت امکان) استفاده کنید: Decimal(‘0.1’) یا Decimal(str(my_float)).

مدیریت دقت و گرد کردن

با getcontext() می‌توان دقت پیش‌فرض و حالت گرد کردن را تنظیم کرد. تابع quantize برای گرد کردن به تعداد ثابت اعشار به کار می‌رود.

from decimal import Decimal, getcontext, ROUND_HALF_EVEN

getcontext().prec = 10
getcontext().rounding = ROUND_HALF_EVEN

amount = Decimal('2.34567')
rounded = amount.quantize(Decimal('0.01'))  # گرد کردن به 2 رقم اعشار
print(rounded)  # خروجی: 2.35 (با ROUND_HALF_EVEN)

در اینجا quantize مقدار را به دو رقم اعشار تبدیل می‌کند. حالت ROUND_HALF_EVEN معمولاً در امور مالی برای کاهش خطای تجمعی استفاده می‌شود.

جدولِ برخی حالات گرد کردن رایج

نامتوضیح
ROUND_HALF_EVENگرد کردن به نزدیک‌ترین عدد؛ مواقع مساوی به سمت زوج‌ها
ROUND_HALF_UPمواقع مساوی به بالا گرد می‌کند
ROUND_DOWNهمیشه به سمت صفر (truncate)
ROUND_UPهمیشه به سمت بی‌نهایت بعید

مثال عملی: محاسبهٔ مالیات و جمع پرداخت

from decimal import Decimal, ROUND_HALF_UP, getcontext

getcontext().prec = 28

price = Decimal('199.99')
tax_rate = Decimal('0.075')  # 7.5%
tax = (price * tax_rate).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
total = (price + tax).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

print("Price:", price)
print("Tax:", tax)
print("Total:", total)

در این مثال مالیات را با دقت 2 رقم اعشار محاسبه می‌کنیم و از ROUND_HALF_UP استفاده می‌کنیم که در بسیاری از سیستم‌های فروش کاربرد دارد. quantize تضمین می‌کند که مقادیر نهایی قابل نمایش در فاکتور باشند.

استفاده از localcontext برای تغییر موقتی تنظیمات

from decimal import Decimal, localcontext, getcontext

getcontext().prec = 10

a = Decimal(1)
b = Decimal(7)

with localcontext() as ctx:
    ctx.prec = 50
    result_high_prec = a / b

result_default = a / b
print(result_high_prec)  # تقسیم با دقت بالا داخل بافت
print(result_default)    # تقسیم با دقت پیش‌فرض

localcontext به شما امکان می‌دهد که تنظیمات دقت و گرد کردن را برای یک بلوک کد موقتی تغییر دهید، بدون اینکه تنظیمات سراسری را دست‌خوش تغییر کند — مناسب برای محاسبات میانی که به دقت بیشتر نیاز دارند.

بهینه‌سازی عملکرد و نکات فنی

  • Decimal بهای پردازشی بیشتری نسبت به float دارد؛ برای محاسبات حجیم بررسی کنید که آیا واقعاً به دقت Decimal نیاز دارید یا خیر.
  • از تبدیل‌های متوالی بین float و Decimal پرهیز کنید و در سراسر برنامه از یک نوع دادهٔ مناسب استفاده کنید.
  • مقادیر ثابت (مانند Decimal(‘0.01’) برای quantize) را یک‌بار تعریف و دوباره استفاده کنید تا هزینهٔ ساخت اشیاء کم شود.
  • برای سری‌های عددی بزرگ، گاهی کار با integers (مثلاً نگهداری سنت‌ها به عنوان عدد صحیح) سریع‌تر و ساده‌تر است؛ سپس در پایان تبدیل به Decimal کنید.

نمونهٔ اصلاحی: جلوگیری از تبدیل مکرر

from decimal import Decimal

PENNIES = Decimal('0.01')

def format_money(value):
    d = Decimal(str(value)).quantize(PENNIES)
    return str(d)

# بهتر از ساخت مکرر Decimal('0.01') داخل توابع است

در این نسخه متغیر ثابت PENNIES یک‌بار ساخته شده و مجدداً استفاده می‌شود تا از بار اضافی جلوگیری شود. همچنین تبدیل از float با str انجام شده تا خطای نمایش float وارد Decimal نشود.

سؤالات رایج و نکات پایانی

  • آیا Decimal همیشه بهتر از float است؟ نه — برای محاسبات عددی علمی با میلیون‌ها عملیات که نیاز به سرعت دارد، float مناسب‌تر است. برای امور مالی و گزارش‌دهی Decimal مناسب‌تر است.
  • چگونه Decimal را در JSON ذخیره کنم؟ معمولاً قبل از سریالیزه کردن از str(Decimal) یا quantize استفاده کنید تا از سازگاری با سایر سیستم‌ها مطمئن شوید.
  • نکتهٔ مهم: همیشه هنگام ساخت Decimal از رشته یا مقدار صحیح استفاده کنید تا از خطاهای نمایش float جلوگیری شود.

کتابخانهٔ decimal ابزار قوی و انعطاف‌پذیری برای مدیریت دقت و قواعد گرد کردن فراهم می‌کند. با رعایت نکات تبدیل، استفاده از quantize و مدیریت مناسبِ context می‌توانید محاسبات مالی و حساس را با اطمینان انجام دهید.

آیا این مطلب برای شما مفید بود ؟

خیر
بله
موضوعات شما در انجمن: