التعبئة 32 بت تطفو في 30 بت (C ++)
-
27-09-2019 - |
سؤال
فيما يلي الأهداف التي أحاول تحقيقها:
- أحتاج إلى حزم 32 بت IEEE يطفو في 30 بت.
- أريد أن أفعل ذلك عن طريق تقليل حجم مانتيسا بمقدار 2 بت.
- يجب أن تكون العملية نفسها بأسرع ما يمكن.
- أدرك أن بعض الدقة ستضيع ، وهذا أمر مقبول.
- ستكون هذه ميزة ، إذا لم تدمر هذه العملية حالات خاصة مثل Snan و Qnan و Infinities ، إلخ. لكنني مستعد للتضحية بهذه السرعة.
أعتقد أن هذه الأسئلة تتكون من جزأين:
1) هل يمكنني ببساطة مسح القطع الأقل أهمية من Mantissa؟ لقد جربت هذا ، وحتى الآن يعمل ، لكن ربما أطلب مشكلة ... شيء مثل:
float f;
int packed = (*(int*)&f) & ~3;
// later
f = *(float*)&packed;
2) إذا كانت هناك حالات تفشل فيها 1) ، فما هي الطريقة الأسرع لتحقيق ذلك؟
شكرا لك مقدما
المحلول 4
لا يمكنني تحديد أي من الإجابات باعتبارها الإجابات المحددة ، لأن معظمها لديهم معلومات صالحة ، ولكن ليس ما كنت أبحث عنه تمامًا. لذلك سألخص استنتاجاتي فقط.
من الواضح أن طريقة التحويل التي نشرتها في الجزء الأول من سؤالي) خاطئة بشكل واضح بواسطة C ++ Standard ، لذلك يجب استخدام طرق أخرى لاستخراج بت Float.
والأهم من ذلك ... بقدر ما أفهم من قراءة الردود والمصادر الأخرى حول عوامات IEEE754 ، لا بأس في إسقاط البتات الأقل أهمية من مانتيسا. سوف يؤثر في الغالب على الدقة فقط ، باستثناء واحد: سنان. نظرًا لأن SNAN ممثلة بواسطة الأسعار التي تم تعيينها على 255 ، و Mantissa! = 0 ، يمكن أن يكون هناك موقف حيث سيكون Mantissa <= 3 ، وسقدم بتات الأخيرين من شأنه أن يحول سنان إلى +/- اللانهاية. ولكن نظرًا لأن SNAN لا يتم توليدها أثناء عمليات النقطة العائمة على وحدة المعالجة المركزية ، فإنها آمنة في ظل البيئة الخاضعة للرقابة.
نصائح أخرى
أنت في الواقع تنتهك قواعد الاسم المستعارة الصارمة (القسم 3.10 من معيار C ++) مع إعادة التفسير هذه. من المحتمل أن ينفجر هذا في وجهك عندما تقوم بتشغيل تحسينات البرمجيات.
C ++ Standard ، القسم 3.10 الفقرة 15 يقول:
إذا حاول البرنامج الوصول إلى القيمة المخزنة للكائن من خلال LVALUE لآخر غير الأنواع التالية ، يكون السلوك غير محدد
- النوع الديناميكي للكائن ،
- إصدار مؤهل للسيرة الذاتية للنوع الديناميكي للكائن ،
- نوع مشابه للنوع الديناميكي للكائن ،
- نوع هو النوع الموقّع أو غير الموقّع المقابل للنوع الديناميكي للكائن ،
- نوع هو النوع الموقّع أو غير الموقّع المقابل لإصدار مؤهل للسيرة الذاتية للنوع الديناميكي للكائن ،
- نوع إجمالي أو اتحاد يتضمن أحد الأنواع المذكورة أعلاه بين أعضائها (بما في ذلك ، على نحو متكرر ، عضوًا في اتحاد فرعي أو محتوى) ،
- نوع من النوع الأساسي (ربما مؤهل من CV) للنوع الديناميكي للكائن ،
- نوع char أو غير موقعة.
على وجه التحديد ، لا يسمح لنا 3.10/15 بالوصول إلى كائن تعويم عبر lvalue من النوع غير موقّع. لقد تعرضت لعض نفسي بالفعل. توقف البرنامج الذي كتبته عن العمل بعد تشغيل التحسينات. على ما يبدو ، لم تتوقع مجلس التعاون الخليجي أن يكون هناك lvalue من النوع تعويم إلى الاسم المستعار من النوع int الذي يعد افتراضًا عادلًا بمقدار 3.10/15. تم خلط التعليمات من قبل المحسن تحت القاعدة التي تستغلها 3.10/15 وتوقفت عن العمل.
تحت ما يلي الافتراضات
- تعويم يتوافق حقًا مع طلاق IEEE 32 بت 32 بت ،
- حجم (تعويم) == sizeof (int)
- INT غير موقعة لا تحتوي على أجزاء الحشو أو تمثيلات فخ
يجب أن تكون قادرًا على القيام بذلك مثل هذا:
/// returns a 30 bit number
unsigned int pack_float(float x) {
unsigned r;
std::memcpy(&r,&x,sizeof r);
return r >> 2;
}
float unpack_float(unsigned int x) {
x <<= 2;
float r;
std::memcpy(&r,&x,sizeof r);
return r;
}
هذا لا يعاني من "3.10-vialation" وعادة ما يكون سريعًا جدًا. على الأقل يعامل GCC memcpy كدالة جوهرية. في حال لم تكن بحاجة إلى وظائف للعمل مع NANS أو Infinities أو الأرقام ذات الحجم العالي للغاية ، يمكنك حتى تحسين الدقة عن طريق استبدال "R >> 2" مع "(R+1) >> 2":
unsigned int pack_float(float x) {
unsigned r;
std::memcpy(&r,&x,sizeof r);
return (r+1) >> 2;
}
يعمل هذا حتى لو قام بتغيير الأسس بسبب تدفق Mantissa لأن خرائط ترميز IEEE-754 على التوالي على التوالي إلى أعداد صحيحة متتالية (تجاهل +/- صفر). هذا التعيين يقارب لوغاريتم بشكل جيد.
قد يفشل إسقاط اثنين من LSBs من الطفو لعدد صغير من ترميزات النان غير العادية.
يتم ترميز NAN على أنه الأسس = 255 ، Mantissa! = 0 ، لكن IEEE-754 لا يقول أي شيء عن قيم mantiassa التي يجب استخدامها. إذا كانت قيمة mantissa <= 3 ، فيمكنك تحويل نان إلى ما لا نهاية!
يجب عليك تغليفه في بنية ، بحيث لا تخلط بطريق الخطأ من استخدام العوامة الموسومة مع "int غير موقعة" العادية:
#include <iostream>
using namespace std;
struct TypedFloat {
private:
union {
unsigned int raw : 32;
struct {
unsigned int num : 30;
unsigned int type : 2;
};
};
public:
TypedFloat(unsigned int type=0) : num(0), type(type) {}
operator float() const {
unsigned int tmp = num << 2;
return reinterpret_cast<float&>(tmp);
}
void operator=(float newnum) {
num = reinterpret_cast<int&>(newnum) >> 2;
}
unsigned int getType() const {
return type;
}
void setType(unsigned int type) {
this->type = type;
}
};
int main() {
const unsigned int TYPE_A = 1;
TypedFloat a(TYPE_A);
a = 3.4;
cout << a + 5.4 << endl;
float b = a;
cout << a << endl;
cout << b << endl;
cout << a.getType() << endl;
return 0;
}
لا يمكنني ضمان قابلية الحمل رغم ذلك.
ما مقدار الدقة التي تحتاجها؟ إذا كان الطوابق 16 بت كافية (كافية لبعض أنواع الرسومات) ، فإن تعويم ILM 16 بت ("نصف") ، جزء من OpenExr رائع ، يطيع جميع أنواع القواعد (http://www.openexr.com/ ) ، وسيكون لديك مساحة كبيرة بعد أن تحزمها في بنية.
من ناحية أخرى ، إذا كنت تعرف النطاق التقريبي للقيم التي سيتخذونها ، فيجب عليك النظر في نقطة ثابتة. إنها أكثر فائدة مما يدركه معظم الناس.