کتابخانه 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 میتوانید محاسبات مالی و حساس را با اطمینان انجام دهید.
آیا این مطلب برای شما مفید بود ؟




