چرا مثال مالی قرارداد جدا می‌خواهد؟

برای یک سبد خرید، دانستن قیمت و تعداد کافی نیست؛ واحد پول، مقیاس اعشار و زمان گردکردن هم بخشی از نتیجه‌اند. این آموزش یک سیاست آموزشی مشخص را پیاده می‌کند و دربارهٔ نرخ قانونی یا محاسبات مالیاتی ایران ادعایی ندارد. نرخ از ورودی می‌آید و همهٔ کالاها مشمول همان نرخ فرض می‌شوند. در محصول واقعی، قواعد حسابداری و الزامات قانونی باید جداگانه تعیین شوند.

Decimal را از رشته بسازید

عدد اعشاری رایانه‌ای از نوع float معمولاً نمایش دودویی است و همهٔ کسرهای ده‌دهی را دقیق نگه نمی‌دارد. Decimal وقتی از رشته ساخته شود مقدار ده‌دهی نوشته‌شده را حفظ می‌کند. ساخت Decimal از یک float، تقریب قبلی را با خود می‌آورد و مشکل را به گذشته برنمی‌گرداند. به همین دلیل قرارداد پروژه قیمت و نرخ را رشته قرار می‌دهد؛ مقدار صحیح تعداد همچنان int باقی می‌ماند.

سه مرحلهٔ مستقل محاسبه

ابتدا مبلغ هر ردیف را از قیمت ضرب در تعداد بسازید و سپس جمع سبد را به دست آورید. در مرحلهٔ دوم، نرخ را روی همین جمع اعمال کنید و فقط مالیات نهایی را به دو رقم اعشار گرد کنید. در پایان، جمع سبد و مالیات گرد‌شده را اضافه کنید. این ترتیب با محاسبه و گردکردن مالیات تک‌تک ردیف‌ها الزاماً برابر نیست و نباید بدون اعلام سیاست عوض شود.

معنای دقیق ROUND_HALF_UP

این روش به نزدیک‌ترین مقدار گرد می‌کند و در حالت دقیقاً نیمه، از صفر دور می‌شود. مثال ۰٫۰۰۵ با مقیاس ۰٫۰۱ به ۰٫۰۱ می‌رسد. این رفتار با گردکردن به زوج که در بسیاری از کاربردها پیش‌فرض است تفاوت دارد. برای مثال مالی، نام روش را در کد صریح بنویسید تا تغییر تنظیم عمومی Decimal نتیجه را ناخواسته عوض نکند. سیاست گردکردن بخشی از قرارداد است، نه جزئیات ظاهری.

چرا خروجی مالی را رشته می‌کنیم؟

JSON نوع اختصاصی Decimal ندارد. تبدیل نتیجه به float می‌تواند دقت نمایشی و صفرهای انتهایی را از بین ببرد؛ برای نمونه ۲۵۰٫۰۰ ممکن است فقط ۲۵۰ نمایش داده شود. در این پروژه مبلغ‌ها به شکل رشتهٔ دو رقم اعشاری برمی‌گردند. مصرف‌کننده باید بداند این فیلد مبلغ است و برای محاسبه دوباره آن را به عدد ده‌دهی مناسب تبدیل کند، نه اینکه رشته‌ها را به هم بچسباند.

تست مرز نیم‌واحد

سبد خالی، تعداد صفر، نرخ صفر و مالیات دقیقاً نیم‌سنت را جدا آزمایش کنید. برای نشان‌دادن اختلاف سیاست‌ها، دو کالای ارزان انتخاب کنید که مالیات هرکدام به نیم‌واحد نزدیک باشد؛ نتیجهٔ گردکردن ردیفی را با گردکردن کل مقایسه کنید. تست باید خروجی رشته‌ای و تعداد رقم‌های اعشار را هم بررسی کند. یک عدد درست با نوع یا قالب اشتباه هنوز با قرارداد پروژه برابر نیست.

محدودیت و مسیر توسعه

این تمرین تخفیف چندمرحله‌ای، نرخ‌های متفاوت، بازپرداخت یا تبدیل واحد را پوشش نمی‌دهد. اضافه‌کردن آن‌ها فقط افزایش چند شرط نیست؛ ترتیب عملیات و مبنای مالیات باید دوباره تعریف شود. برای عددهای بسیار بزرگ نیز دقت context Decimal اهمیت دارد. در ادامه، یک تست مستقل برای تغییر مقیاس پول بنویسید و پیش از اتصال به فاکتور واقعی، سیاست موردنیاز را با مسئول مالی روشن کنید.

نمونه‌کد و قرارداد پروژه

items: فهرست price به‌صورت رشتهٔ اعشاری غیرمنفی با حداکثر دو رقم اعشار و quantity صحیح غیرمنفی؛ tax_rate رشتهٔ اعشاری بین صفر و یک. نرخ صرفاً دادهٔ تمرین است، نه نرخ قانونی.

subtotal, tax, total همگی رشتهٔ دو رقم اعشاری؛ مالیات فقط یک بار بر جمع سبد محاسبه و با ROUND_HALF_UP به ۰٫۰۱ گرد می‌شود.

from decimal import Decimal, ROUND_HALF_UP


def solve(data):
    subtotal = sum((Decimal(item['price']) * item['quantity']
                    for item in data['items']), Decimal('0'))
    tax = (subtotal * Decimal(data['tax_rate'])).quantize(
        Decimal('0.01'), rounding=ROUND_HALF_UP)
    return {'subtotal': format(subtotal, '.2f'), 'tax': format(tax, '.2f'),
            'total': format(subtotal + tax, '.2f')}

print(solve({'items': [{'price': '100.00', 'quantity': 2}, {'price': '50.00', 'quantity': 1}], 'tax_rate': '0.10'}))

خروجی مورد انتظار

{
  "subtotal": "250.00",
  "tax": "25.00",
  "total": "275.00"
}

پرسش‌های متداول

آیا ROUND_HALF_UP همیشه الزام قانونی است؟

خیر. این فقط سیاست صریح همین تمرین است؛ قواعد واقعی بسته به قرارداد، واحد پول و مقررات باید بررسی شوند.

آیا Decimal(str(float)) همیشه مقدار اصلی را برمی‌گرداند؟

نه. این کار فقط نمایش متنی فعلی float را تبدیل می‌کند و دقت ازدست‌رفتهٔ ورودی را بازیابی نمی‌کند.

تمرین عملی و قدم بعدی

پروژهٔ «محاسبهٔ سبد خرید با مالیات» همین قرارداد را در میزکار ارزیابی می‌کند. ابتدا خروجی نمونه را دستی محاسبه کنید، سپس تابع را اجرا کنید و یک ورودی مرزی متفاوت بسازید. نمونه‌کد راهی برای توضیح منطق است؛ برای یادگیری، علت هر شرط و انتخاب ساختار داده را بدون نگاه‌کردن به کد توضیح دهید. اجرای موفق با قبولی ارزیابی متفاوت است؛ نتیجه باید برای تمام حالت‌های تعریف‌شده درست بماند.

منابع و مطالعهٔ بیشتر

برای بررسی جزئیات و ادامهٔ مطالعه؛ متن این راهنما مستقلاً برای نوونتیکس نوشته شده است.