سؤال

لقد قمت مؤخرا بقراءة قليلا على IEEE 754 بنية X87. كنت أفكر في استخدام NAN ك "قيمة مفقودة" في بعض رمز الحساب الرقمي، وأنا كنت آمل أن تستخدم إرسال الإشارات سيسمح لي نان بالقبض على استثناء نقطة عائمة في الحالات التي لا أريد فيها متابعة "القيم المفقودة". على العكس، أود استخدام هادئ نان للسماح "القيمة المفقودة" للنشر من خلال حساب. ومع ذلك، فإن الإشارة إلى Nans لا تعمل كما اعتقدت أنها ستستند إلى الوثائق (المحدودة للغاية) الموجودة عليها.

إليك ملخص ما أعرفه (كل هذا باستخدام X87 و VC ++):

  • _em_invalid (استثناء "IEEE" غير صالح ") يتحكم في سلوك X87 عند مواجهة NANS
  • إذا كان _em_invalid ملثمين (يتم تعطيل الاستثناء)، فلن يتم إنشاء استثناء ويمكن للعمليات إعادة نان هادئة. عملية تنطوي على الإشارة نان سوف ليس تسبب استثناء يتم إلقاؤه، ولكن سيتم تحويله إلى هادئ نان.
  • إذا كان _em_invalid غير مكتسب (تمكين الاستثناء)، فإن عملية غير صالحة (على سبيل المثال، SQRT (-1) تسبب استثناء غير صالح.
  • X87. مطلقا يولد الإشارة نان.
  • إذا كان _em_invalid غير مكتظ أي يؤدي استخدام إشارة نان (حتى في تهيئة متغير معه) إلى إلقاء استثناء غير صالح.

توفر المكتبة القياسية طريقة للوصول إلى قيم NAN:

std::numeric_limits<double>::signaling_NaN();

و

std::numeric_limits<double>::quiet_NaN();

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

إذا _em_invalid هو ليس ملثمين (استثناء ممكن)، ثم لا يمكن للمرء حتى تهيئة متغير مع إشارة نان:double dVal = std::numeric_limits<double>::signaling_NaN(); نظرا لأن هذا يلقي استثناء (يتم تحميل قيمة الإشارة NAN في سجل X87 لتخزينه إلى عنوان الذاكرة).

قد تظن ما يلي كما فعلت:

  1. قناع _em_invalid.
  2. تهيئة المتغير مع إشارة نان.
  3. unmask_em_invalid.

ومع ذلك، يؤدي الخطوة 2 إلى تحويل إشارة نان إلى نان هادئة، لذلك الاستخدامات اللاحقة لها ليس تسبب الاستثناءات التي يجب إلقاؤها! حتى وتف؟!

هل هناك أي فائدة أو غرض على الإطلاق إلى إشارة نان؟ أفهم أحد النوايا الأصلية هو تهيئة الذاكرة معها حتى يتم اكتشاف استخدام قيمة النقطة العائمة Unitedialized.

هل يمكن لأحد أن يقول لي إذا فقدت شيئا هنا؟


تعديل:

لتوضيح ما كنت آمل أن أفعله، إليك مثال:

النظر في أداء العمليات الرياضية على متجه البيانات (الزوجي). بالنسبة لبعض العمليات، أرغب في السماح للمتجه بأن يحتوي على "قيمة مفقودة" (يتظاهر هذا يتوافق مع عمود جدول البيانات، على سبيل المثال، حيث لا تملك بعض الخلايا قيمة، ولكن وجودهم مهم). لبعض العمليات، أفعل ليس تريد السماح للمتجه بأن يحتوي على "قيمة مفقودة". ربما أرغب في اتخاذ مسار مختلف للعملات إذا كانت "القيمة المفقودة" موجودة في المجموعة - ربما أداء عملية مختلفة (وبالتالي فإن هذه ليست حالة غير صالحة لتكون فيها).

هذا الرمز الأصلي يبدو أن هذا مثل هذا:

const double MISSING_VALUE = 1.3579246e123;
using std::vector;

vector<double> missingAllowed(1000000, MISSING_VALUE);
vector<double> missingNotAllowed(1000000, MISSING_VALUE);

// ... populate missingAllowed and missingNotAllowed with (user) data...

for (vector<double>::iterator it = missingAllowed.begin(); it != missingAllowed.end(); ++it) {
    if (*it != MISSING_VALUE) *it = sqrt(*it); // sqrt() could be any operation
}

for (vector<double>::iterator it = missingNotAllowed.begin(); it != missingNotAllowed.end(); ++it) {
    if (*it != MISSING_VALUE) *it = sqrt(*it);
    else *it = 0;
}

لاحظ أنه يجب إجراء الشيك من أجل "القيمة المفقودة" كل تكرار حلقة. وبعد بينما أفهم في معظم الحالات، sqrt من المحتمل أن تكون الوظيفة (أو أي عملية رياضية أخرى) على الأرجح من المحتمل أن تتغلب على هذا الشيك، وهناك حالات تكون فيه العملية ضئيلة (ربما مجرد إضافة) والشيك مكلفة. ناهيك عن حقيقة أن "القيمة المفقودة" تتخذ قيمة إدخال قانونية من اللعب ويمكن أن تتسبب في حشرات إذا وصلت عملية حسابية إلى هذه القيمة (من غير المرجح أن تكون). أيضا أن تكون صحيحة تقنيا، يجب التحقق من بيانات إدخال المستخدم ضد هذه القيمة ويجب اتخاذ مسار مناسب للعمل. أجد هذا الحل inelegant وأقل غير مثالية من الأداء الحكيم. هذا رمز مهم للأداء، ونحن بالتأكيد ليس لدينا ترف هياكل البيانات الموازية أو كائنات عنصر البيانات من نوع ما.

ستبدو نسخة NAN مثل هذا:

using std::vector;

vector<double> missingAllowed(1000000, std::numeric_limits<double>::quiet_NaN());
vector<double> missingNotAllowed(1000000, std::numeric_limits<double>::signaling_NaN());

// ... populate missingAllowed and missingNotAllowed with (user) data...

for (vector<double>::iterator it = missingAllowed.begin(); it != missingAllowed.end(); ++it) {
    *it = sqrt(*it); // if *it == QNaN then sqrt(*it) == QNaN
}

for (vector<double>::iterator it = missingNotAllowed.begin(); it != missingNotAllowed.end(); ++it) {
    try {
        *it = sqrt(*it);
    } catch (FPInvalidException&) { // assuming _seh_translator set up
        *it = 0;
    }
}

الآن يتم القضاء على الشيك الصريح وسيتم تحسين الأداء. أعتقد أن هذا سيعمل جميعا إذا استطعت تهيئة المتجه دون لمس سجلات FPU ...

علاوة على ذلك، أود أن أتصور أي احترام الذات sqrt فحص التنفيذ لنان وإرجاع نان على الفور.

هل كانت مفيدة؟

المحلول

كما أفهمها، فإن الغرض من الإشارة نان هو تهيئة هياكل البيانات، ولكن بالطبع مدة العرض يقوم التهيئة في C في ج بإجراء مخاطر وجود NAN عند تسجيل تعويم كجزء من التهيئة، مما يؤدي إلى تشغيل الإشارة لأن المترجم لا يدرك أن هذه القيمة العائمة تحتاج إلى نسخ باستخدام تسجيل عدد صحيح.

آمل أن تتمكن من التهيئة static القيمة مع إشارة نان، ولكن حتى ذلك سيتطلب بعض المناولة الخاصة من قبل المترجم لتجنب وجودها تحولت إلى نان هادئة. ربما يمكنك استخدام القليل من السحر الصب لتجنب التعامل معها كقيمة تعويم أثناء التهيئة.

إذا كنت تكتب في ASM، فلن يكون هذا مشكلة. ولكن في C وخاصة في C ++، أعتقد أنك سوف تضطر إلى تخريب نظام النوع من أجل تهيئة متغير مع نان. أقترح باستخدام memcpy.

نصائح أخرى

باستخدام القيم الخاصة (حتى NULL) يمكن أن تجعل بياناتك الكثير من الطين ورمزك بكثير موديل. سيكون من المستحيل التمييز بين نتيجة QNAN وقيمة QNAN "الخاصة".

قد تكون من الأفضل الحفاظ على بنية بيانات متوازية لتتبع الصلاحية، أو ربما امتلاك بيانات FP الخاصة بك في بنية بيانات مختلفة (متقطعة) للحفاظ على بيانات صالحة فقط.

هذه نصيحة عامة إلى حد ما؛ القيم الخاصة مفيدة للغاية في بعض الحالات (مثل قيود الذاكرة أو الأداء الضيقة حقا)، ولكن كما ينمو السياق أكبر، يمكن أن يسبب صعوبة أكبر مما يستحق.

لا يمكن أن يكون لديك فقط const uint64_t حيث تم تعيين البتات على تلك التي تشير نان؟ طالما كنت تعاملها كنوع عدد صحيح، فإن الإشارة نان لا تختلف عن الأعداد الصحيحة الأخرى. يمكنك كتابة ذلك المكان الذي تريده من خلال مؤشر الصب:

Const uint64_t sNan = 0xfff0000000000000;
Double[] myData;
...
Uint64* copier = (uint64_t*) &myData[index];
*copier=sNan | myErrorFlags;

للحصول على معلومات حول البتات لتعيين:https://www.doc.ic.ac.uk/~eedwards/compsys/float/nan.html.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top