15.2. خطأ التمثيل #
يشرح هذا القسم مثال “0.1” بالتفصيل، ويوضح كيفية إجراء تحليل دقيق لحالات كهذه بنفسك. يُفترض إلمام أساسي بتمثيل الفاصلة العائمة الثنائية.
يشير خطأ التمثيل إلى أن بعض (معظم، في الواقع) الكسور العشرية لا يمكن تمثيلها بدقة ككسور ثنائية (الأساس 2). هذا هو السبب الرئيسي لعدم عرض بايثون (أو بيرل، سي، سي++، جافا، فورتران، وغيرها الكثير) للعدد العشري المتوقع.
لماذا؟ لا يمكن تمثيل 1/10 بدقة ككسر ثنائي. تستخدم جميع الأجهزة تقريبًا اليوم (نوفمبر 2000) حسابات الفاصلة العائمة IEEE-754، وتُطابق جميع المنصات تقريبًا الأعداد العشرية في بايثون بدقة IEEE-754 المزدوجة. تحتوي الأعداد العشرية 754 على 53 بت من الدقة، لذا عند الإدخال، يسعى الحاسوب جاهدًا لتحويل 0.1 إلى أقرب كسر ممكن من الصيغة J/2**N حيث J عدد صحيح يحتوي على 53 بت بالضبط. بإعادة كتابة
1 / 10 ~= J / (2**N)
على النحو التالي
J ~= 2**N / 10
ومع الأخذ في الاعتبار أن J يحتوي على 53 بت بالضبط (يكون >= 2**52 لكن < 2**53)، فإن أفضل قيمة لـ N هي 56:
>>> 2**52 <= 2**56 // 10 < 2**53
True
أي أن 56 هي القيمة الوحيدة لـ N التي تجعل J يحتوي على 53 بت بالضبط. أفضل قيمة ممكنة لـ J هي ناتج القسمة المقرب:
>>> q, r = divmod(2**56, 10)
>>> r
6
بما أن الباقي أكبر من نصف 10، فإن أفضل تقريب يُحصل عليه بالتقريب لأعلى:
>>> q+1
7205759403792794
لذلك، فإن أفضل تقريب ممكن لـ 1/10 بدقة مضاعفة 754 هو:
7205759403792794 / 2 ** 56
بقسمة كل من البسط والمقام على اثنين، يصبح الكسر:
3602879701896397 / 2 ** 55
لاحظ أنه نظرًا لأننا قربنا لأعلى، فإن هذا التقريب أكبر بقليل من 1/10؛ لو لم نُقرّب لأعلى، لكان حاصل القسمة أصغر بقليل من ١/١٠. ولكن لا يُمكن بأي حال أن يكون ١/١٠ بالضبط! لذا، لا يرى الحاسوب أبدًا ١/١٠: ما يراه هو الكسر الدقيق المذكور أعلاه، وهو أفضل تقريب مزدوج ٧٥٤ يمكن الحصول عليه:
>>> 0.1 * 2 ** 55
3602879701896397.0
إذا ضربنا هذا الكسر في ١٠ ** ٥٥، يمكننا رؤية القيمة حتى ٥٥ رقمًا عشريًا:
>>> 3602879701896397 * 10 ** 55 // 2 ** 55
1000000000000000055511151231257827021181583404541015625
مما يعني أن الرقم الدقيق المُخزّن في الحاسوب يساوي القيمة العشرية 0.1000000000000000055511151231257827021181583404541015625. بدلاً من عرض القيمة العشرية الكاملة، تستخدم العديد من اللغات (بما في ذلك الإصدارات القديمة من بايثون)، حوّل النتيجة إلى ١٧ رقمًا معنويًا:
>>> format(0.1, '.17f')
'0.10000000000000001'
وحدات الكسور والأعداد العشرية تُسهّل هذه الحسابات:
>>> from decimal import Decimal
>>> from fractions import Fraction
>>> Fraction.from_float(0.1)
Fraction(3602879701896397, 36028797018963968)
>>> (0.1).as_integer_ratio()
(3602879701896397, 36028797018963968)
>>> Decimal.from_float(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')
>>> format(Decimal.from_float(0.1), '.17')
'0.10000000000000001'