Skip to content
العودة إلى المدوّنة
دروس تعليمية

شرح الفاصلة الثابتة Q15: التحويل والتقريب والفيض

القيمة 0.1 تُخزَّن في Q15 كـ 3277، و1.0 تنقلب إلى -1.0 دون تشبُّع. اقرأ ترميز Q واحسب التحويل يدويًا وتحقّق منه عبر الإنترنت مجانًا.

15 دقائق للقراءة

شرح الفاصلة الثابتة Q15: التحويل والتقريب والفيض

تخزّن الفاصلة الثابتة Q15 الكسرَ في صورة عدد صحيح عادي بإشارة بطول 16 بت، مع مقياس ثنائي ضمني مقداره 2^15 = 32768. والترميز عملية ضرب واحدة تليها خطوة تقريب واحدة:

raw = round(value × 32768)

وفكّ الترميز قسمة واحدة:

value = raw ÷ 32768

هذا هو الحساب كله. 0.5 × 32768 = 16384، فتُخزَّن 0.5 في الذاكرة بوصفها العدد الصحيح 16384، ويعيد 16384 ÷ 32768 القيمة 0.5 بالضبط. وليس هنا حقل أُس ولا بت مخفي. فالفاصلة الثنائية اتفاق بينك وبين من سيقرأ الكلمة؛ أما العتاد فلا يرى سوى عدد من نوع int16.

ولهذه الصيغة القصيرة أثران مباشران، وكلاهما موضع يضيّع فيه الناس أمسية كاملة.

0.1 × 32768 = 3276.8، وهذا ليس عدداً صحيحاً. فتحتفظ Q15 بالقيمة 3277، والقيمة التي تقرأها ثانيةً هي 0.100006103515625 لا 0.1.

1.0 × 32768 = 32768، وهذا يتجاوز أكبر عدد صحيح بإشارة بطول 16 بت بمقدار واحد. فلا ترميز لـ 1.0 في Q15 إطلاقاً، وما تفعله شفرتك حيال ذلك يتوقف على سياسة لا تدوّنها معظم قواعد الشفرة. أشبِع القيمة تحصل على 0.999969482421875، والتفّ بها تحصل على -1.0.

ومعظم أخطاء الفاصلة الثابتة تعود إلى واحد من هذين، أو إلى ترميز Q قرأه اثنان قراءتين مختلفتين، أو إلى قاعدة تقريب وقاعدة فيض اختارتهما سلسلة أدواتك بصمت.

Q15 وQ1.15 وQm.n: هل تصف التخطيط نفسه؟

اسأل ثلاثة مراجع عن معنى Q15 وقد تخرج بثلاث إجابات. والمشكلة ليست في قراءتك. فالترميز لم يُوحَّد قط في الواقع، والخلاف كله يدور حول بت واحد.

كيف تقرأ Qm.n

الصيغة ذات الرقمين لا لبس فيها. ففي Qm.n، الحرف n هو عدد بتات الكسر وm هو عدد بتات العدد الصحيح. وهذه المقالة تعدّ موضع الإشارة ضمن m، وهي القراءة التي تجعل Q16.16 كلمة بطول 32 بت: ستّ عشرة بت صحيحة تشمل الإشارة، وستّ عشرة بت كسرية، والمقياس 2^16 = 65536. وترميز 1.5 بصيغة Q16.16 يعطي 1.5 × 65536 = 98304، وبالست عشري 0x00018000، دون أي خطأ تقريب على الإطلاق.

ويبدأ الاضطراب عند بت الإشارة. فبعض المؤلفين يعدّونه ضمن m، وبعضهم يضيفه فوقه. وQ1.15 وفق الاصطلاح الأول كلمة بطول 16 بت: موضع واحد للإشارة أو للعدد الصحيح زائد 15 بت كسرية. أما وفق الاصطلاح الثاني فاللافتة نفسها تصف 17 بت، وهو ما لا تملكه أي آلة.

لماذا تدل لافتة Q15 نفسها على عروض مختلفة في وثائق مختلفة

تُسقط الصيغة ذات الرقم الواحد Q15 الحرف m كلياً وتترك العرض ضمنياً. وفي ممارسة معالجة الإشارات الرقمية (DSP) تعني دائماً تقريباً كلمة بإشارة بطول 16 بت بالمتمم الثنائي وبخمس عشرة بت كسرية، وهذا هو المعنى الذي استقرت عليه منظومتا TI وARM منذ عقود.

لكنك ستصادف شروحاً منسوخة على نطاق واسع تقول شيئاً من قبيل: «Q15 تعني 15 بت كسرية، فإن عرّفنا عدداً بطول 32 بت كان فيه بت إشارة واحد و16 بت صحيحة». واقرأها بتمعّن تجدها تعدّ بت الإشارة فوق m لا ضمنه، أي بالاصطلاح الآخر غير المعتمد هنا. والتخطيط الذي تصفه حقيقي، ويُكتب عادةً Q16.15. وإنما الخطأ في اللافتة الملصقة عليه: فكلمة بطول 32 بت فيها 16 بت صحيحة ليست Q15 في أي من الاصطلاحين. وقد أُعيد نشر الفقرة نفسها في ما يكفي من المدونات حتى صارت اليوم تتفوّق على التعريف الصحيح في بعض عمليات البحث.

فتعامل مع Q15 مجردةً في وثيقة غير مألوفة بوصفها فرضية لا حقيقة. وتحقّق منها بشيء تستطيع قياسه: عرض المسجّل في خريطة الذاكرة، أو نوع لغة C في ترويسة المشغّل، أو قيمة عيّنة معروفة.

الوصف الوحيد الذي يصمد أمام شفرة كتبها غيرك

ثلاثة أشياء إن دوّنتها زال الالتباس:

  • وجود الإشارة — بإشارة بالمتمم الثنائي، أم بلا إشارة
  • W — إجمالي بتات الكلمة
  • F — بتات الكسر

لا يمكن إساءة قراءة signed, W=16, F=15، ولا unsigned, W=32, F=16. وكل ما عداهما مشتقّ: المقياس هو 2^F، والاستبانة هي 2^-F، والمدى هو مدى الأعداد الصحيحة للكلمة مقسوماً على 2^F. ضع هذه القيم الثلاث في وثيقة البروتوكول وفي تعليق البنية ولن تخوض هذا الجدال مرة أخرى.

ويطبع محوّل الأعداد الثابتة بتنسيق Q وجودَ الإشارة وW وF والمقياس بجانب كل نتيجة للسبب نفسه. فحين تقول ورقة بيانات «Q15» ويقول محلّل زميلك شيئاً آخر، يحسم فكُّ ترميز كلمة واحدة معروفة الأمرَ في نحو عشر ثوانٍ.

صيغة التحويل، محسوبة يدوياً

كلا الاتجاهين قصير بما يكفي لتحسبه على الورق، وهذا ما يهمّ حين تحدّق في تفريغ ست عشري على شاشة راسم إشارة.

من العائم إلى الثابت: round(x × 2^F)

خذ 0.5 إلى Q15.

  1. القياس: 0.5 × 32768 = 16384
  2. التقريب: القيمة عدد صحيح أصلاً، فتبقى 16384 كما هي
  3. فحص المدى: تحمل الكلمة ذات الإشارة بطول 16 بت من -32768 إلى 32767، و16384 يسع فيها
  4. التخزين: 16384، وبالست عشري 0x4000، وبالثنائي 0100000000000000

الخطوة 2 هي وحدها التي تضيّع معلومات، والخطوة 3 هي وحدها التي قد تفشل. وكل مشكلات الفاصلة الثابتة تقريباً تقع في أحد هذين الموضعين.

من الثابت إلى العائم: raw ÷ 2^F

والآن اسلك الاتجاه المعاكس، بدءاً من التقاط مسجّل يقرأ 0xC000 في حقل Q15 بإشارة.

  1. حلّل النص بوصفه رمزاً بلا إشارة بطول 16 بت: 0xC000 = 49152
  2. الصيغة ذات إشارة والبت الأعلى مضبوط، فاطرح 2^16: 49152 - 65536 = -16384
  3. اقسم على المقياس: -16384 ÷ 32768 = -0.5

والخطوة 2 هي التي يقفز عنها الناس. فبدون تصحيح المتمم الثنائي تُقرأ 0xC000 بوصفها +1.5، وهي أصلاً خارج مدى Q15، وتلك علامة إنذار مفيدة. فإن وقعت قيمة مفكوكة خارج مدى الصيغة، فأنت على الأرجح نسيت خطوة الإشارة.

from fractions import Fraction


def encode_q15(x):
    """Decimal value -> signed Q15 stored integer (ties to even)."""
    return round(Fraction(str(x)) * 32768)


def decode_q15(word):
    """Unsigned 16-bit code -> exact Q15 value."""
    if word & 0x8000:
        word -= 0x10000
    return Fraction(word, 32768)


print(encode_q15(0.5))              # 16384
print(encode_q15(0.1))              # 3277
print(float(decode_q15(0xC000)))    # -0.5
print(float(decode_q15(0x0CCD)))    # 0.100006103515625

والصنف Fraction يؤدي عملاً حقيقياً هنا. فالقياس عبر float أولاً سيعيد إدخال التقريب الثنائي في اللحظة نفسها التي تحاول قياسه فيها، بينما Fraction(str(x)) يقرأ الحرفية العشرية التي كتبتها بدل أقرب قيمة double إليها.

قراءة الكلمة الست عشرية وكتابتها

الست عشري هو الشكل الذي تظهر به هذه القيم فعلاً في عروض المسجّلات، والتحويل آلي: 3277 بالست عشري هو 0xCCD، ويُبطَّن إلى W/4 خانات فيصير 0x0CCD. بطّن دائماً. فكلمة Q15 أربع خانات ست عشرية وكلمة Q31 ثماني خانات؛ وإسقاط الصفر البادئ هو الطريق المختصر إلى قيمة مزاحة عن موضعها داخل فاكّ ترميز دفعي.

وأبقِ الرموز الخام بلا إشارة في الست عشري كذلك. فـ 0x8000 هي أشدّ كلمات Q15 سلبية، لا +32768، وكتابتها -0x8000 لا تفيد أحداً. أما ترتيب البايتات فمسألة منفصلة، لأن تنسيق Q يحدّد القياس العددي ولا يقول شيئاً عن ترتيب البايتات (endianness). فتفريغ 0x0CCD بترتيب البايت الأصغر أولاً يصل في صورة البايتين CD 0C. ولتغييرات الأساس المجردة أثناء قراءة تفريغ، يتولّى محوّل الأنظمة العددية الثنائي والثماني والست عشري دون مقياس ولا عرض بإشارة ملحق بهما.

Q7 وQ15 وQ31 وQ16.16: المدى والاستبانة

كل رقم في هذا الجدول هو -2^(W-1) مقسوماً على 2^F عند طرف، و(2^(W-1) - 1) مقسوماً على 2^F عند الطرف الآخر. والقيم دقيقة، غير مقرَّبة لأجل العرض.

الصيغةالعرضبتات الكسر Fالمقياس 2^Fالأدنىالأقصى (بالضبط)الاستبانة
Q787128-1127/128 = 0.99218751/128 = 0.0078125
Q15161532768-132767/32768 = 0.9999694824218751/32768 = 0.000030517578125
Q3132312147483648-1(2^31−1)/2^31 = 0.99999999953433871269226074218751/2^31 ≈ 4.6566128730773926e-10
Q16.16321665536-32768(2^31−1)/65536 = 32767.99998474121093751/65536 = 0.0000152587890625

الصق أياً من هذه القيم الحدّية في محوّل الأعداد الثابتة بتنسيق Q فيعيد إليك العدد الصحيح المخزَّن والكلمة الست عشرية والعدد العشري الدقيق، وهذه أسرع طريقة للتأكد من ثابت في البرنامج الثابت قبل شحنه.

لماذا يقف سقف مدى صيغة Q15 عند 0.999969482421875

مدى صيغة Q15 غير متماثل، ومصدر اللاتماثل هو المتمم الثنائي لا أي شيء يخصّ الفاصلة الثابتة. فالكلمة ذات الإشارة بطول 16 بت تغطي الأعداد الصحيحة من -32768 إلى 32767. واقسم الطرفين على 32768 فيصير المدى من -32768/32768 إلى 32767/32768، أي من -1 إلى 0.999969482421875.

فالقيمة 1.0 غائبة بمقدار بت واحد أقل أهمية (LSB) بالضبط. والأقصى ليس «قريباً من 1.0» ولا «1.0 بعد التقريب»: القيمة بلا ترميز أصلاً، وأي محوّل يبلّغ عن 1.0 في Q15 دون أن يذكر أنه أشبعها فهو يكذب عليك.

لماذا يدخل -1 في المدى

كثير من المراجع يكتب مدى Q15 هكذا: -1 < X < 0.9999695، بقوس مفتوح على الطرفين. والحدّ الأدنى خطأ. فـ -32768 ÷ 32768 = -1 بالضبط، أي أن -1 قابلة للتمثيل، وطرفا [-1, 0.999969482421875] كلاهما قيمة قابلة للبلوغ. وستصادف المدى مكتوباً كذلك هكذا: [-1, 1)، وهي العبارة نفسها منسوبةً إلى كامل النطاق: القيمة 1.0 نفسها بعيدة المنال، أما أكبر قيمة في المتناول فهي 0.999969482421875 بالضبط، لا قيمةٌ تنقص عنها.

وهذا أهمّ من مماحكة في الترميز. فـ -1 هي القيمة التي تكسر الضرب، لأن -1 × -1 = 1 و1 خارج المدى. ومن يفترض أن -1 غير قابلة للبلوغ لن يكتب فرع التشبُّع الذي يلتقطها.

أما الحدّ الأعلى المبتور 0.9999695 في تلك المراجع نفسها فأثرُ عرضٍ لا أكثر. لا شيء دوري في القيمة: إنها 32767/32768، و32768 قوة للعدد 2، فينتهي التوسّع العشري بعد 15 خانة عند 0.999969482421875.

القيمة 0.1 لا تسع في Q15

تظهر المشكلة فور ضربها في المقياس: 0.1 × 32768 = 3276.8. والفاصلة الثابتة لا تخزّن إلا أعداداً صحيحة، فلا بد أن يتنازل طرف.

ومع التقريب إلى الأقرب، تكون الكلمة المخزَّنة 3277، وبالست عشري 0x0CCD. والقيمة التي تمثّلها تلك الكلمة هي:

3277 ÷ 32768 = 0.100006103515625

وخطأ التكميم هو الفرق بين ما طلبته وما استطاعت الشبكة أن تمنحك إياه:

0.100006103515625 - 0.1 = +0.000006103515625

وهذا نحو ستة أجزاء من المليون، أي خُمس بت أقل أهمية. لا يضرّ في متحكّم مستوى الصوت، لكنه يضرّ في مُكامِل يعيد إضافته ألف مرة في الثانية، حيث يصير انجرافاً ثابتاً بنحو 0.006 في الثانية داخل القيمة المتراكمة.

خطأ الفاصلة الثابتة منتظم، وخطأ الفاصلة العائمة ليس كذلك

ترسم Q15 شبكة من 65536 نقطة تفصل بينها مسافة 1/32768 بالضبط عبر المجال [-1, 0.999969482421875]. والتباعد قرب 0.9 هو نفسه التباعد قرب 0.0001، فيكون أسوأ خطأ مطلق نصفَ بت أقل أهمية في كل مكان. وهذا يجعل تحليل الخطأ مملاً بأفضل معنى: تستطيع أن تحدّ أرضية الضجيج في مرشّح بحساب يقدر مهندس مبتدئ على مراجعته.

ويفعل IEEE 754 العكس. فهو يبقي عدد البتات المعنوية ثابتاً ويحرّك الأُس، فتتّسع الفجوة المطلقة بين قيمتين متجاورتين من نوع double كلما كبر المقدار، بينما يبقى الخطأ النسبي شبه ثابت. فقرب 1.0 تبلغ تلك الفجوة نحو 2.2e-16، وقرب 1e12 تبلغ نحو 0.0001220703125.

وهو الإخفاق نفسه في صورة مختلفة. فالنوع double لا يستطيع هو الآخر حمل 0.1. إنه يخزّن 0.1000000000000000055511151231257827021181583404541015625، ولهذا يعيد 0.1 + 0.2 القيمة 0.30000000000000004. وتلك الحالة مفصّلة في المقالة المرافقة عن دقة الفاصلة العائمة. فالفاصلة الثابتة لا تحلّ مشكلة الكسور العشرية في الثنائي؛ كل ما تفعله أنها تجعل حجم الخطأ متوقعاً.

ثلاثة أنماط للتقريب، بينها بت واحد أقل أهمية

قاعدة التقريب جزء من عقد البيانات، وليست تفصيلة متروكة لمن ينفّذ. فتنفيذان صحيحان يختلفان في التقريب ينتجان متجهات اختبار تتباين في البت الأخير إلى الأبد، وتعقّب ذلك عمل بائس.

النمطالقاعدة10911.744-3276.8
التقريب إلى الأقرب، والتعادل إلى الزوجيأقرب نقطة في الشبكة؛ والأنصاف الدقيقة تذهب إلى العدد الزوجي10912-3277
البتر نحو الصفرأسقِط الكسر، فلا يتقلّص إلا المقدار10911-3276
الأرضية نحو سالب ما لا نهايةإلى أسفل خط الأعداد دائماً10911-3277

ويبيّن العمودان لماذا لا يكفي مثال واحد أبداً. فمع قيمة موجبة يتفق البتر والأرضية. ومع قيمة سالبة ينفصلان بمقدار بت كامل أقل أهمية، لأن البتر يسحب -3276.8 صعوداً نحو الصفر بينما تدفعها الأرضية هبوطاً.

المثال المحلول الذي يخطئ فيه الجميع

القيمة 0.333 في Q15 حالة اختبار جيدة لأنها تقع قرب حدّ. 0.333 × 32768 = 10911.744.

  • البتر: 10911، وتُقرأ ثانيةً 0.332977294921875
  • التقريب إلى الأقرب: 10912، وتُقرأ ثانيةً 0.3330078125

تطبع الشروح القديمة 10911 وتمضي دون أن تقول أي قاعدة أنتجته. وإن أخذت ذلك الرقم إلى قاعدة شفرة يقرّب مرمِّزها، فشلت متجهاتك الذهبية من أول تشغيل، بفارق مقداره واحد يبدو خطأً مطبعياً لا تضارباً في السياسة.

وعند السوالب تنفصل القواعد الثلاث. فـ -0.1 × 32768 = -3276.8 تعطي -3276 بالبتر (القيمة -0.0999755859375) و-3277 بالأرضية أو بالتقريب إلى الأقرب (القيمة -0.100006103515625). ويعامل كلٌّ من البتر والتقريب إلى الأقرب الإشارتين على السواء، ±3276 و±3277 على الترتيب، فيبقى زوج المعاملات المتماثل متماثلاً. أما الأرضية فلا تفعل: تحوّل +0.1 إلى 3276 بينما تحوّل -0.1 إلى -3277، فيخرج الزوج مائلاً بمقدار وحدة واحدة.

ما تفعله لغتك افتراضياً

لا خطأ في أي من هذه الافتراضات، لكنها مختلفة، ولا واحد منها يعلن عن نفسه.

  • C/C++: التحويل من الفاصلة العائمة إلى نوع صحيح يبتر نحو الصفر. فـ (int16_t)(0.333f * 32768) تعطي 10911.
  • Python: الدالة المدمجة round() تستخدم التعادل إلى الزوجي، فتكون round(3276.8) هي 3277 وround(2.5) هي 2.
  • JavaScript: الدالة Math.round تحسم التعادل نحو موجب ما لا نهاية، وهذا غير متماثل. فـ Math.round(2.5) هي 3 بينما Math.round(-2.5) هي -2.
  • العتاد: كثير من مسارات الضرب والتراكم في DSP تقرّب عند الإزاحة وتتيح التقريب إلى الأقرب-الزوجي كبت نمط، ولهذا قد يتناقض نموذج C المرجعي مع السيليكون إلى أن يقرأ أحدهم مسجّل النمط.

اختر قاعدة واحدة، وسمِّها في مواصفة الصيغة بجانب W وF، واجعل متجهات الاختبار تحملها معها.

الفيض: التشبُّع مقابل الالتفاف

يفعل فيض Q15 واحداً من ثلاثة: يرفض القيمة، أو يقصّها عند 0.999969482421875، أو يلتفّ بها إلى -1.0. وأيُّها تحصل عليه سياسةٌ اختارتها سلسلة أدواتك، والخيار الثالث يعكس الإشارة بصمت.

خذ 1.0. القياس يعطي 1.0 × 32768 = 32768، وأكبر عدد صحيح بإشارة بطول 16 بت هو 32767. فالقيمة خارج المدى بمقدار واحد بالضبط.

السياسةالكلمة المخزَّنةالقيمة المقروءة ثانيةًكيف تبدو في المراحل التالية
الخطألا شيءالتحويل مرفوضصاخب وقابل للالتقاط، وهو الصواب للأدوات غالباً
التشبُّع0x7FFF = 327670.999969482421875لا تميّزه أذن ولا عين عن 1.0
الالتفاف0x8000 = -32768-1.0انقلاب إشارة بكامل النطاق

يضيّع صف التشبُّع القيمة 0.000030517578125 ولا يلحظ ذلك أحد. أما صف الالتفاف فيحوّل عيّنة موجبة بكامل النطاق إلى عيّنة سالبة بكامل النطاق، وفي مسار صوتي تلك نقرة تُسمع في أرجاء الغرفة. وفي حلقة تحكّم هي أمرٌ بكامل النطاق في الاتجاه الخطأ.

والخاصية الخطرة في الالتفاف أنه ينتج كلمة تبدو سليمة تماماً. فـ 0x8000 ترميز Q15 مشروع للقيمة -1.0. ولا شيء في المراحل التالية يقدر على تمييزها عن عيّنة -1.0 حقيقية، فلا توقيع تبحث عنه بعد وقوع الأمر؛ ولا سبيل إلى اكتشافها إلا بمراقبة نقطة حدوث الفيض نفسها.

لماذا تشحن رقائق DSP تعليمات مُشبِعة

التشبُّع هو السلوك الذي تريده معالجة الإشارات، فتنفّذه المعالجات بدل تركه لفرع شرطي. فلدى ARM التعليمتان QADD/QSUB وتعليمتا الإزاحة المُشبِعة SSAT/USAT، ولدى NEON التعليمة VQADD وأخواتها، ولدى x86 SSE عمليات جمع مُشبِعة محزومة مثل paddsw. وتُتيح عائلتا C6000 وC55x من TI التشبُّعَ كبت نمط على مسار المُراكِم.

في مرشّح أو خالط، العيّنة المقصوصة العابرة تشويه محلي صغير. أما العيّنة الملتفّة فانقطاع تتوزّع طاقته على الطيف كله. فيختار العتاد الخطأ الطفيف على الخطأ الكارثي افتراضياً، لكن فقط إن فعّلته أنت؛ وحساب الأعداد الصحيحة العادي بلغة C على الرقاقة نفسها ما زال يلتفّ.

لماذا يحتاج ضرب Q15 إلى إزاحة يمنى بمقدار 15 بت

اضرب كلمتي Q15 بوصفهما عددين صحيحين تكن النتيجة صحيحة، لكنها لم تعد Q15. فبتات الكسر تُجمَع أثناء الضرب: Q15 × Q15 تعطي Q30.

اعمل على 0.5 × 0.5، حيث الجواب الصحيح 0.25 بداهةً:

16384 × 16384 = 268435456          ← this is Q30, not Q15
268435456 ÷ 2^30 = 0.25            ← read as Q30, correct
268435456 >> 15 = 8192             ← realign to Q15
8192 ÷ 32768 = 0.25                ← same answer, back in Q15

وإن فسّرت 268435456 بوصفها Q15 قرأتها 8192.0، وهي بعيدة بعامل مقداره 32768. ذلك العامل هو العلة كلها، وهو يفسّر لماذا تخطئ مرشّحات الفاصلة الثابتة التي «تكاد تعمل» بمقدار قوة للعدد 2 في الغالب.

ويحتاج الناتج إلى فسحة أيضاً. فقيمتان بطول 16 بت تتضاعفان إلى 32 بت كحدّ أقصى، فيجب أن يكون الوسيط من نوع int32_t. وتراكم حاصلات ضرب كثيرة يحتاج فسحة أكبر، ولهذا تكون مُراكِمات DSP بطول 40 بت في قطع مثل C55x.

قرّب عند الإزاحة، لا تسقط البتات وحسب

الإزاحة المجردة >> 15 ترمي البتات الخمس عشرة الدنيا، وهذا بترٌ نحو سالب ما لا نهاية بالنسبة للقيم ذات الإشارة. وإضافة نصف بت أقل أهمية من الدقة الخارجة أولاً تحوّلها إلى تقريب إلى الأقرب:

#include <stdint.h>
#include <stdio.h>

static int16_t sat_q15(int32_t v) {
    if (v >  32767) return  32767;
    if (v < -32768) return -32768;
    return (int16_t)v;
}

static int16_t mul_q15(int16_t a, int16_t b) {
    int32_t prod = (int32_t)a * (int32_t)b;   /* Q30 */
    int32_t back = (prod + (1 << 14)) >> 15;  /* round, then Q30 -> Q15 */
    return sat_q15(back);
}

int main(void) {
    printf("0.5*0.5   -> %d\n", mul_q15(16384, 16384));
    printf("0.1*0.1   -> %d\n", mul_q15(3277, 3277));
    printf("-1*-1     -> %d\n", mul_q15(-32768, -32768));
    printf("no-round  -> %d\n", (int)(((int32_t)3277 * 3277) >> 15));
    return 0;
}

وبترجمته بالأمر cc -std=c11 -Wall -o q15 q15.c && ./q15، يطبع البرنامج:

0.5*0.5   -> 8192
0.1*0.1   -> 328
-1*-1     -> 32767
no-round  -> 327

السطر الثالث هو حالة -1 السابقة. -32768 × -32768 = 1073741824، وهي 1.0 في Q30 وخارج مدى Q15، فتقصّها الدالة sat_q15 إلى 32767. أزل القصّ يلتفّ بها التحويل إلى int16_t فتصير -32768، وتنقلب -1 × -1 إلى -1.

والسطران الأخيران هما فارق التقريب. 3277 × 3277 = 10738729، والإزاحة المجردة تعطي 327 (0.009979248046875) بينما تعطي الإزاحة المقرَّبة 328 (0.010009765625). والناتج الحقيقي 0.01، فالنسخة المقرَّبة أقرب إليه بأكثر من الضعف. وثمن ذلك عملية جمع واحدة.

وتحفّظ واحد على الإزاحة نفسها: إزاحة عدد صحيح سالب ذي إشارة إلى اليمين سلوكٌ محدَّد بالتنفيذ في C قبل C23، وإن كان كل مترجم ستصادفه ينفّذ إزاحة حسابية. فإن أزعجك ذلك، اقسم على 32768 ودع المترجم يصدر الإزاحة، أو نفّذ الإزاحة على نوع بلا إشارة بعد إضافة انحياز. أما الآليات الأوسع للإزاحات والأقنعة فيغطيها دليل عمليات البت.

الجمع يحتاج أولاً إلى قيم Q متطابقة

يغيّر الضرب قيمة Q بصورة متوقعة. أما الجمع فلا يحتمل أي تفاوت: جمع كلمة Q7 إلى كلمة Q15 ينتج هراءً، لأن المعاملين لا يتشاركان مقياساً.

صُفَّهما بإزاحة أولاً. فـ 0.5 في Q7 هي 64، و64 << 8 هي 16384، أي 0.5 في Q15. ومقدار الإزاحة هو الفارق في بتات الكسر، 15 - 7 = 8.

الإزاحة صعوداً دقيقة لكنها تكلّف فسحة، لأن قيمة Q7 المرقّاة إلى Q15 تحتاج الوعاء الأوسع. والإزاحة هبوطاً مُفقِدة وتحتاج قرار التقريب نفسه الذي يحتاجه الضرب. وفي الحالتين، اكتب قيمة Q لكل وسيط في تعليق. فشفرة الفاصلة الثابتة التي تعيش مقاييسها في رأس كاتبها وحده تصير غير قابلة للصيانة خلال شهر.

الفاصلة الثابتة Q15 أم الفاصلة العائمة IEEE 754: كيف تختار

كلاهما نظام موضعي ثنائي، فلا أفضلية دقة لأيهما من حيث المبدأ. والاختيار يعود إلى ما يفرضه عليك العتاد المستهدف من كلفة وإلى الضمانات التي تحتاجها.

السؤاليرجّح الفاصلة الثابتةيرجّح الفاصلة العائمة
هل توجد وحدة FPU عتادية؟لا FPU، أو مكتبة فاصلة عائمة برمجيةFPU عتادية بعمليات في دورة واحدة
ما اتساع المدى الديناميكي؟معروف ومحدود، كالصوت المُطبَّعيمتد على عدة مراتب من المقدار
هل تحدّد صيغة النقل مقياساً؟البروتوكول أو المسجّل يثبّت المقياس الثنائيالحقل عدد عائم حقيقي
هل يجب أن تتطابق النتائج بتاً ببت عبر عمليات البناء؟نعم، فالأعداد الصحيحة تتكرر في كل مكانيُحتمل تباينها مع FMA والتحسين
هل الذاكرة أو عرض النطاق ضيّق؟عيّنات 16 بت تنصّف بصمة الأعداد العائمة بطول 32 بتليست قيداً
من يصون الشفرة؟فريق يتقن ترميز Q أصلاًفريق مختلط، وأخطاء القياس هي الخطر الأكبر

والصف الأخير ليس مزحة. فالفاصلة الثابتة تنقل الخطأ من زمن التشغيل إلى مرحلة التصميم، وهذه صفقة رابحة فقط حين يقوم أحدهم بعمل التصميم فعلاً. وعلى Cortex-M4F بوحدة FPU عتادية، يكون العدد العائم أحادي الدقة غالباً الخيار الأسرع والأكثر أماناً معاً، وردّة الفعل التقليدية بالقفز إلى Q15 عادةٌ موروثة عن قطع لم تعد هي المهيمنة.

أين يتولّى IEEE 754 الأمر

اقفز إلى الفاصلة العائمة حين تحمل الكلمة نفسها إشارة وأُساً ومعنويةً بدل مقياس ثابت. تلك هي اللحظة التي يتوقف فيها تنسيق Q عن الانطباق: فلا يوجد 2^F واحد تقسم عليه، لأن الأُس يتغيّر مع كل قيمة.

ويلتقي التمثيلان باستمرار في الممارسة. فبيانات الحسّاس تصل بوصفها كلمات مسجّلات Q15، ثم تُرقّى إلى عدد عائم لحساب طويل، ثم تعود Q15 من أجل المحوّل الرقمي التماثلي (DAC). ولفحص الشقّ العائم من ذلك المسار بتاً ببت، يفكّك محوّل IEEE 754 القيمة إلى إشارة وأُس ومانتيسا ويطبع العدد العشري المخزَّن الدقيق، وهو العمل نفسه الذي يؤديه محوّل تنسيق Q لمقياس ثابت.

كلاهما يقوم على الأساس نفسه

تنسيق Q وIEEE 754 والأعداد الصحيحة العادية كلها تقرأ البتات نفسها بقواعد مختلفة حول موضع الفاصلة وحول قابليتها للحركة. فإن بدا لك جانب القيمة الموضعية هشاً، أو أردت أن تصير أسرع في قراءة 0x0CCD بوصفها 0000 1100 1100 1101 دون اللجوء إلى آلة حاسبة، فإن المدخل إلى تحويل الأنظمة العددية: الثنائي والست عشري والثماني يشرح الأساس الذي تقوم عليه الصيغتان معاً.

أسئلة شائعة عن الفاصلة الثابتة Q15

ماذا تعني Q15؟

في اصطلاح DSP الشائع، Q15 كلمة بإشارة بطول 16 بت بالمتمم الثنائي وبخمس عشرة بت كسرية: موضع إشارة واحد و15 موضعاً كسرياً. المقياس هو 2^15 = 32768، والاستبانة هي 1/32768 = 0.000030517578125، والمدى من -1 إلى 32767/32768. ولأن لافتات Q تتباين بين الوثائق، تأكّد من العرض الإجمالي ومن وجود الإشارة بدل الوثوق باللافتة وحدها.

هل Q15 وQ1.15 شيء واحد؟

هما يصفان عادةً التخطيط نفسه بطول 16 بت وبإشارة، حيث يعدّ الرقم 1 في Q1.15 موضع الإشارة. لكن الترميز ليس عالمياً، وبعض المؤلفين يضيف بت الإشارة فوق m بدل عدّه ضمنه. والوصف الموثوق هو وجود الإشارة زائد إجمالي البتات W زائد بتات الكسر F: ولهذا التخطيط، بإشارة، W=16، F=15.

ما قيمة 0.5 في Q15؟

قيمة 0.5 في Q15 هي العدد الصحيح المخزَّن 16384، وبالست عشري 0x4000. والحساب هو 0.5 × 32768 = 16384، وهي عدد صحيح أصلاً، فلا تقريب ولا خطأ تكميم. وفكّ الترميز يؤكّد ذلك: 16384 ÷ 32768 = 0.5 بالضبط.

ما القيمتان القصوى والدنيا في Q15؟

الدنيا هي -1، وهي داخلة في المدى، لأن -32768 ÷ 32768 تساوي -1 بالضبط. والقصوى هي 32767/32768 = 0.999969482421875. وكلتا القيمتين قابلة للبلوغ، فالمدى إذن [-1, 0.999969482421875]، و1.0 هي القيمة الواقعة خارجه. والمراجع التي تكتب الحدّ الأدنى بفترة مفتوحة مخطئة، وذلك الخطأ يخفي حالة فيض -1 × -1.

ماذا يحدث حين تفيض Q15؟

يتوقف ذلك على السياسة السارية. فسياسة الخطأ ترفض التحويل. والتشبُّع يقصّ إلى أقرب طرف، فتصير 1.0 هي 0.999969482421875، وهي خسارة بت واحد أقل أهمية لا تكاد تُسمع. والالتفاف يطبّق باقي القسمة على 2^16، فتُقاس 1.0 إلى 32768، وتُقرأ ثانيةً -32768 ومن ثم -1.0، أي انقلاب إشارة كامل. ويضبط عتاد DSP التشبُّع افتراضياً لهذا السبب بالذات، لكن حساب الأعداد الصحيحة العادي بلغة C يلتفّ.

لماذا يحتاج عددان من نوع Q15 إلى إزاحة يمنى بمقدار 15 بعد الضرب؟

لأن بتات الكسر تُجمَع. فـ Q15 × Q15 تنتج حاصل ضرب من نوع Q30، فيحمل الناتج الصحيح 30 بت كسرية بدل 15. والإزاحة يميناً بمقدار 15 تعيد صفّه إلى Q15: 16384 × 16384 = 268435456، و268435456 >> 15 = 8192، وهي تفكّ إلى 0.25. أضف 1 << 14 قبل الإزاحة لتقرّب إلى الأقرب بدل البتر، وأبقِ الوسيط في int32_t حتى لا يفيض حاصل الضرب بطول 32 بت.

متى أستخدم تنسيق Q بدل IEEE 754؟

استخدم تنسيق Q حين يكون بروتوكول أو خريطة مسجّلات أو خوارزمية DSP أو مرمِّز قد ثبّت أصلاً مقياساً ثنائياً لكلمة صحيحة، فالمقياس عندئذ جزء من الواجهة ولا خيار لك فيه. واستخدم IEEE 754 حين تحتاج القيمة مدى ديناميكياً واسعاً، أو حين يملك الهدف وحدة FPU عتادية، أو حين يخزّن الحقل فعلاً إشارة وأُساً ومعنوية. أما لتغيير أساس مجرّد بلا مقياس ملحق فلا ينطبق أي منهما؛ تلك عملية تحويل أنظمة عددية عادية.

الخلاصة

الفاصلة الثابتة Q15 عملية ضرب وقرار تقريب وفحص مدى. raw = round(value × 32768) عند الدخول، وvalue = raw ÷ 32768 عند الخروج. والحساب نفسه تافه، لكن الإخفاقات كلها تسكن في الأجزاء التي لا يوثّقها أحد.

فوثّقها إذن. اكتب وجود الإشارة وW وF بجانب كل لافتة Q بدل افتراض أن اللافتة كافية. وسمِّ نمط التقريب في الموضع نفسه، لأن البتر والأرضية ينفصلان بمقدار بت كامل أقل أهمية على السوالب. وقل صراحةً هل يخطئ الفيض أم يُشبِع أم يلتفّ، لأن حالة الالتفاف تحوّل 1.0 إلى -1.0 ولا تترك وراءها أي دليل.

وحين تتناقض كلمة مسجّل مع جدول بيانات، فُكّ ترميز قيمة واحدة معروفة في محوّل الأعداد الثابتة بتنسيق Q. فهو يعرض العدد الصحيح المخزَّن والعدد العشري الدقيق وخطأ التكميم والمدى القابل للتمثيل جنباً إلى جنب، وكله محسوب داخل المتصفح. وهذا يكفي عادةً لمعرفة صاحب الافتراض الخاطئ بشأن W و F.

الوسوم: fixed-point dsp embedded q-format number-representation

مقالات ذات صلة

عرض جميع المقالات