قد يكون استخدام الأعداد العشرية لإجراء حسابات دقيقة في بايثون أمرًا خطيرًا. هنا نوضح السبب ونعرض لك حلاً بديلاً.
عندما تحاول معرفة مقدار 2.32 × 3
، تخبرك بايثون أنها 6.959999999999999
. بالنسبة لبعض الحسابات، هذا جيد. ولكن إذا كنت تحسب معاملة تنطوي على أموال، فهذا ليس ما تريد رؤيته. بالتأكيد، يمكنك تقريبها، لكن هذا مبتذل بعض الشيء.
نوضح لك في هذه المقالة كيفية الحصول على حسابات دقيقة عن طريق استيراد الوحدة العشرية. لمعرفة سبب فائدة ذلك، نحتاج أولاً إلى تغطية بعض المعلومات الأساسية حول كيفية تمثيل الأرقام في بايثون.
الأرقام العشرية والثنائية
رقم الفاصلة العائمة هو ببساطة رقم يحتوي على علامة عشرية. في لغة بايثون، يمكنك التحقق مما إذا كان الرقم عائمًا باستخدام دالة type()
:
>>> type(1)
<class 'int'>
>>> type(1.0)
<class 'float'>
يمكن للدوال المضمنة float()
و int()
تحويل أنواع البيانات الأخرى، بما في ذلك السلاسل النصية، إلى أرقام الفاصلة العائمة والأعداد الصحيحة على التوالي:
print(float(1))
print(float('1.10'))
print(int(1.1))
عندما تقوم بإدخال رقم الفاصلة العائمة في بايثون، يتم التعبير عنه بنظام الأرقام العشري (الأساس 10). خذ الكسر العشري 0.1234
، والذي يمكن تمثيله على النحو التالي: 1/101 + 2/102 + 3/103 + 4/104
. ومع ذلك، تقوم أجهزة الكمبيوتر بتخزين الأرقام في الأساس 2 (ثنائي). على سبيل المثال، يمكن التعبير عن الكسر الثنائي 0.1011
(مع المكافئ العشري 0.6875
) بالصيغة 1/21 + 0/22 + 1/23 + 1/24
.
المشكلة هي أن معظم الكسور العشرية لا يمكن تمثيلها تمامًا ككسور ثنائية. هذا يعني أن معظم أرقام الفاصلة العائمة التي تدخلها في Python لا يمكن تقريبها إلا من خلال رقم الفاصلة العائمة الثنائي المخزن على الجهاز.
خذ بعين الاعتبار العدد العشري 0.1
. ككسر عشري، يمكن تمثيل ذلك بشكل مثالي بـ 1/101
، لكن أقرب تمثيل كسري ثنائي هو 3602879701896397/255
. عند إدخال هذا في بايثون، يتم تقريب القيمة إلى 0.1
لعرضها، وهو ما قد يكون مضللاً، لأن القيمة المخزنة على الجهاز هي في الواقع: 0.1000000000000000055511151231257827021181583404541015625
حساب الأموال في بايثون
ربما يمكنك أن تبدأ في رؤية كيف يمكن أن يصبح هذا مشكلة. خذ بعين الاعتبار السيناريو التالي فيما يتعلق بحساب الأموال في بايثون:
unit_price = 2.32
number_sold = 3
money_received = 6.96
if unit_price * number_sold == money_received:
print('Accounts balanced')
else:
raise ValueError('Accounts not balanced')
هنا، Unit_price و money_received عبارة عن أعداد العشرية، و number_sold عبارة عن عدد صحيح. الكتب متوازنة بالفعل في هذا المثال. ومع ذلك، يؤدي هذا إلى حدوث خطأ بسبب كيفية تمثيل الأعداد العشرية وتخزينها على جهازك، مما يشير إلى وجود تناقض بين الأموال المستلمة ومبلغ البيع.
هذا هو المكان الذي تكون فيه الوحدة العشرية مفيدة. يأتي مع تثبيت بايثون الافتراضيا ويحتوي على عدة فئات. الفئة ذات الصلة بمشكلتنا هي فئة Decimal()
. ميزة استخدام هذه الوحدة هي أنه يمكن تمثيل الأرقام العشرية تمامًا في بايثون. تنطبق هذه الدقة أيضًا على نتائج الحساب.
لحل المشكلة المذكورة أعلاه، يمكن استيراد هذه الفئة واستخدامها على النحو التالي:
from decimal import Decimal
unit_price = Decimal('2.32')
number_sold = 3
money_received = Decimal('6.96')
if unit_price * number_sold == money_received:
print('Accounts balanced')
else:
raise ValueError('Accounts not balanced')
لاحظ هنا أن قيمة الإدخال إلى Decimal()
عبارة عن سلسلة، ولكن يمكن أيضًا استخدام أنواع البيانات الأخرى كمدخل. في المثال أعلاه، إذا قمت بتعريف المتغير على أنه Unit_price = Decimal(2.32)
، فسينتهي بك الأمر بتمثيل غير دقيق للقيمة 2.32، حيث يتم تحويل رقم الفاصلة العائمة إلى مكافئه العشري الثنائي.
خذ العدد العشري 0.1
كمدخل. يؤدي إدخال Decimal(0.1)
إلى إرجاع Decimal('0.1000000000055511151231257827021181583404541015625')
. لقد صادفنا هذا التمثيل سابقًا. لذلك، لحساب الأموال بدقة في بايثون، من الأفضل والأكثر بديهية استخدام السلاسل كمدخل.
أصبحت المتغيرات Unit_price
وmoney_received
الآن كائنات عشرية، مع نوع بيانات جديد decimal.Decimal
. تحقق بنفسك باستخدام الدالة المضمنة type()
كما فعلنا أعلاه.
تحتوي هذه الكائنات العشرية على العديد من نفس خصائص أنواع البيانات الأخرى مثل الأعداد العشرية والأعداد الصحيحة. تنطبق العمليات الحسابية العادية، ونتائج هذه العمليات لها تمثيل دقيق، وهو ما أصلح مشكلة موازنة كتبنا. ومثل أنواع البيانات الأخرى، يمكن استخدام الكائنات العشرية في قائمة أو قاموس، ويمكن نسخها أو طباعتها أو فرزها أو تحويلها إلى أنواع بيانات أخرى.
الدقة
بالنسبة للعديد من التطبيقات التي تتضمن عد الأموال بدقة في بايثون، من الجيد أن تكون قادرًا على ضبط دقة الأرقام. توفر وحدة decimal
للمستخدم القدرة على القيام بذلك، بقيمة افتراضية تبلغ 28 رقمًا عشريًا. من ناحية أخرى، فإن دقة أرقام الفاصلة العائمة لا تتجاوز عادة حوالي 15 رقمًا. ضبط الدقة أمر بسيط:
from decimal import *
getcontext().prec = 10
تتضمن الوحدة مفهوم الأماكن المهمة، وبالتالي فإن نتيجة Decimal('1.10') + Decimal('20.20')
هي Decimal('21.30')
، مع الاحتفاظ بالصفر الزائد لتحقيق الاتساق. بالإضافة إلى ذلك، تتضمن الوحدة القدرة على تحديد كيفية تقريب الأرقام. ويتم التعامل مع هذا أيضًا باستخدام getcontext()
. يمكنك قراءة المزيد في الوثائق الرسمية.
كائنات وتوابع الأعداد العشرية
تحتوي الكائنات العشرية على عدة توابع تساعد في إجراء المزيد من العمليات الحسابية والتحويلات والفحوصات. على سبيل المثال، هناك توابع لحساب الجذر التربيعي والدالة الأسية واللوغاريتم. يمكنك أيضًا العثور على القيم القصوى والدنيا، واستخدام العمليات المنطقية، وإجراء عمليات فحص للقيم المحدودة وقيم NaN، من بين أشياء أخرى.
سيكون الكثير منها مألوفًا لمستخدمي حزم Python الأخرى مثل numpy (رائعة للعمل مع المصفوفات)، أو math (رائعة للعمل مع الكميات العددية)، أو pandas (رائعة لتنظيف البيانات وتحليلها). لكن استخدام الوحدة العشرية يوفر جميع المزايا التي تمت مناقشتها أعلاه.
تابعان يساعدان في التعامل مع التنسيق هما Normalize()
و quantize().
يقوم الأول بإزالة الأصفار الزائدة في أقصى اليمين ويوحد تنسيق الكائن. على سبيل المثال، يقوم كل من Decimal('1.10').normalize()
وDecimal('0.11e1').normalize()
بإرجاع Decimal('1.1')
.
من ناحية أخرى، يتم استخدام الدالة الأخيرة، quantize()
، لتقريب القيم لمطابقة أس الوسيطة، بحيث يكون للقيمتين نفس عدد المنازل العشرية، دون الحاجة إلى معرفة عددها مقدمًا.
>>> value1 = Decimal('1.01')
>>> value2 = Decimal('10.001')
>>> value1.quantize(value2)
Decimal('1.010')
>>> value2.quantize(value1)
Decimal('10.00')
حساب الأموال في بايثون: إلى أين الآن؟
لقد قمنا بمراجعة استخدام وحدة decimal
لحساب الأموال في بايثون، ولكن هناك أيضًا وحدات أخرى تستحق الذكر. على سبيل المثال، تدعم FRACTIONS الحسابات الدقيقة من خلال تنفيذ العمليات الحسابية القائمة على الأرقام المنطقية.
توفر وحدات بايثون MONEY وPRICES وPY-MONEYED دوال إضافية لإدارة الأموال استنادًا إلى وحدة decimal
. جميعها تدعم التعامل بعملات مختلفة، لذلك يمكنك تجنب إضافة اليورو إلى الدولار عن طريق الخطأ دون مراعاة سعر الصرف. التحقق منها لمعرفة أي واحد يناسب احتياجاتك. نظرًا للكيفية التي أصبح بها التمويل الآلي، فإن وجود وحدة decimal
، مع نوع البيانات decimal.Decimal
، والدوال الإضافية المضمنة في Python، ميزة كبيرة للعديد من المشاريع.
اكتشاف المزيد من بايثون العربي
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.