الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

[تحدي الجزء 1] الأعداد العشرية

بدأه zirek99i في 21 يناير 2014 · 2 رد · 2,159 مشاهدة · في لغة C و ++C
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

السلام عليكم ورحمة الله تعالى وبركاته

 

الهدف:

إزاحة شبح الأعداد العشرية

فهم المعنى الحقيقي everything is just a sequence of bits

القدرة على التعامل مبائرة مع الذاكرة

 

الموضوع:

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

 

المتطلبات:

1 كتابة برنامج يطلب من المستخدم إدخال رقم عشري، أو فقط إستخدم hardcoded number لامشكلة  ;)

2 تقسيم الرقم العشري على شكل حقول بنية، ثم طباعة كل جزء على شكل بتات، مع وضع تعليق بسيط وفي الصميم، لكل حقل في الـ output طبعاً

3 أثبت أنك تعي جيدا ما تقوم به، وذلك عن طريق تعديل الذاكرة مبائرة بتا بتاً، بإضافة 2.013- الى العدد الذي إخترته :D ، 

 

الغش غير مقبول نهائياً  :angry:

 

حسناً، لكي أشرح بالضبط موضوع التحدي قمت بإعداد مثال خاطئ تماماً يشرح المتطلبات،

/*
    This is a fake solution, 
    just to tel what the idea is
    
    author: zirek99i
*/

#include <blabla.h>

// pure c

#define read(msg, var) do somthing ... or use the usual

#pragma pack ?

typedef struct _FLOAT_MEMORY
{
    unsigned int field_1:7
    unsigned int field_2:77
    unsigned int field_2:777
    unsigned int ...
    
}
FLOAT_MEMORY, *PFLOAT_MEMORY;

#pragma pack ?

int __cdecl main (void)
{
    //******************************************************/
    // 1. get the number from the user
    
    float __float;
    FLOAT_MEMORY floatMemory;
    
    read("Enter a floating number", __float);
    
    
    //******************************************************/
    // 2. show me how the number is presented in memory
    
    floatMemory = () ....;
    
    // think of something
    m_ConvertToBinary(some_pointer);
    
    // the output should be something like
    output " field_1 = 01010010101010  -> this is the ...."
    output " field_2 = 010100          -> this is the ...."
    output " field_1 = 101001010       -> this is the ...."
    ...
    
    // mathematique operation
    // tel me how they do it
    output " field_1 + field_2"
    
    
    //******************************************************/
    // 3. this is on you think of something .. :=)
    
    // if you get this far then you are ready for the next challenge (FPU(st*) registers ;))
    // by sincere
    // zirek99i
    
    return 0;
}

المدة: أسبوع واحد

 

إن كان هناك تفاعل :)  سأضع الحل، وأضع الجزء الثاني  :D

إن لم يكن هناك أي تفاعل   :wacko:

فسأختفي  -_-

3
#2

السؤال رائع ,عذراً للتأخير

لم أجب عن الشق الأخير من السؤال بعد , ولكن هذه محاولتي المبدئية

#include <iostream>
#include <string>
void bin(int y){
    for(int i=31;i>=0;i--)
        std::cout << ((y&(1<<i))!=0) ;
    std::cout << std::endl;
}
void give(int y,int x,std::string Message){
    y^=1<<x;//sign bit
    std::cout << "Toggle the " << x << "bit will " << Message << *(float*)&y << std::endl;
    bin(y);
}
int main(){
    float x;
    std::cout << "Hello World :p : " ;
    std::cin  >> x;
    int y=*((int*)(&x));
    bin(y);
    give(y,31,"change the sign ");

    give(y,30,"Base 2 or 2^-1023 ");

    give(y,29,"mul/div by 2^(2^64)  ");
    //...
    give(y,26,"mul/div by 256\t");
    give(y,25,"mul/div by 16 \t");
    give(y,24,"mul/div by 4  \t");
    give(y,23,"mul/div by 2  \t");

    give(y,22,"add/sub 0.5\t");
    give(y,21,"add/sub 0.25\t");
    give(y,20,"add/sub 0.125\t");
    give(y,19,"add/sub 0.625\t");
    give(y,18,"add/sub 0.3125\t");
    give(y,17,"add/sub 0.15625\t");
    //...
    give(y,0,"add/sub 2^-512\t");
    return 0;
}

المرجع

 

يُتبع إن شاء الله

#3

عذراً فقد عبرت في الزمن مدة خمسة أشهر دون أن أشعر ..
وأتيت بالإجابة ( بعد أن درست مادة بنيان الحاسب :) )
إليكم بعض الشرح النظري :
أولاً تمثيل العدد الكسري وفق معيار IEEE 754
يقسم العدد الكسري من نوع float (وهو ذو 32 بتّاً ) إلى 3 أقسام .
1- الإشارة : تأخذ بتّاً واحداً في أقصى اليسار (البت رقم 31 ) 0 للموجب و 1 للسالب
2- القوّة exponent: تأخذ 8 بتّات (البتات من 23 إلى 30 ) ويتم تخزين القوة الموجبة والسالبة بتحديد الصفر (أو bias)  بقيمة 127 , أي أن القوة صفر هي 127 , وهكذا .. القوة 5 تكون 132 والقوة -5 تكون 122
3- الفاصلة mantissa : هذا القسم هو الذي يحدد قيمة العدد , وكلما زاد حجمه زادت الدقة , في float حجمه 23 بت , ويمثل العدد بعد الفاصلة بالتمثيل الثنائي المعروف للأعداد الكسرية , مثلاً 10000000 تمثّل 0.5 و 11000000 تمثل 0.5+0.25=0.75 وهكذا ..
قد يسأل سائل : أين يتم تمثيل القسم الحقيقي (على يسار الفاصلة) والجواب أنه لا يتم تمثيله أبداً , وذلك لأن الـstandard الخاص بالأعداد الكسرية يحدد أن العدد على يسار الفاصلة (بالتمثيل الثنائي) يجب أن يكون دائماً 1
فيما يلي مثال يمكّنكم من فهم طريقة التمثيل : لدينا العدد الكسري (بالتمثيل العشري ) 125.625
نثوم بتحويله إلى ثنائي (القسم الصحيح والقسم الكسري كلّ على حدة )
الصحيح : 125 = 64+32+16+8+4+1 فنكتب : 1111101
الكسري : 0.625 = هو1/2+ 1/8 فنكتب :0.101
ويكون التمثيل الكلي : 1111101.101 (هذا التمثيل يسمّى fixed point )
والآن نزيح الفاصلة لليسار حتى يبقى 1 فقط على يسارها : 1.111101101 (بما أننا أزحنا الفاصلة بمقدار 6 بت فالعدد الناتج هو 62 * 1.1111101 ) لاحظوا ظهور القوة exponent
والآن : العدد موجب (قسم الإشارة يحوي 0 ) , القوة 6 + bias يعطي 127+6 = 133 بالثنائي نكتبه 10000101 , والقسم الكسري 1111101
فيكون العدد مخزناً بتمثيل الstandard  هو : 01000010111111010000000000000000
(من الملاحظ أن هناك مشكلة في تمثيل الصفر ! لأنه يجب وضع واحد على يسار الفاصلة ) كما أن الأعداد الأكبر من 2128 والأصغر من 2-128 لا يمكن تمثيلها .

لنرجع إلى التحدي :
الكود الذي يشرح تمثيل العدد :

#include <iostream>
#include <bitset>
using namespace std;

int main()
{
    float x;
cin>>x;
        int y=*((int*)(&x));
        bitset<32>z(y);
        cout<<z<<endl;
        bitset<1>sign(z[31]);
        cout<<"sign is "<<(sign[0]==0?"+":"-")<<endl;
        bitset<8>exponent(z.to_string().substr(1,9));
        char exp=char(exponent.to_ulong())-127;
        cout<<"exponent is "<<exponent<<"\t = "<<int(exp)<<endl;

        bitset<23>mantissa(z.to_string().substr(9));
        cout<<"mantissa is "<<mantissa<<endl;
    return 0;
}

والآن إلى العمليات الحسابية
كما ذكرنا فإن العدد يمثل بالشكل A × Be وإذا كان لدينا عدد آخر C × Bd  (حيث B هو 2 )
فإننا يمكن أن نقوم بالجمع عندما يكون للعددين نفس القوة أي e=d ويكون الناتج  (A+C) مضروباً بـ Be وكذلك الأمر بالنسبة للطرح , أما إذا كان d<e فإن علينا زيادة d حتى يصل إلى قيمة e وذلك بإزاحة العدد لليمين (أي القسمة على 2 )
أما بالنسبة للضرب فهو بسيط نضرب A*C ونجمع القوى d+e ويكون الناتج A*C مضروباً بـ Be+d
والآن إلى التطبيق العملي ..
لدينا العددان 01000000011111111111111111111111  و 01000001111111111111111111111111 ونريد جمعهما
لاحظ أن القوى مختلفة فالأول القوة فيه هي 1 (أي مضروب بـ 21 ) أما الثاني فالقوة فيه 4 (أي مضروب بـ24 )
وللقيام بالجمع علينا إزاحة ذو القوة الصغير لليمين وزيادة القوة الخاصة به
العدد هو       01000000011111111111111111111111
الإزاحة الأولى 01000000101111111111111111111111
الإزاحة الثانية 01000001000111111111111111111111
الإزاحة الثالثة 01000001100011111111111111111111
والآن أصبحت القوى متساوية نقوم بجمع 00011111111111111111111 إلى 11111111111111111111111 والناتج 100011111111111111111110 والقوة كما هي 10000011
ولكن حدث عندنا فيض (overflow) أي خرجنا عن الـ 23 بت المسموحة لذلك نزيح العدد ككل لليمين ونزيد القوة بمقدار 1
فيكون الناتج 10001111111111111111111 والقوة 10000100 وطبعاً الإشارة لا تزال موجبة 0
فالناتج الكلي 01000010010001111111111111111111
إلى البرنامج الذي يقوم بجمع 2.013- إلى العدد .
العدد المطلوب جمعه في نص التحدي هو -2.013 الإشارة - إذاً 1
التمثيل : 10.000000110101001111110111110011(تمثيل الفاصلة غير منتهٍ وسيحصل فقدان للدقة الآن .. لأننا سنزيح  العدد لليسار ونأخذ 23 بت فقط للفاصلة  1.0000000110101001111110111110011بما أننا أزحنا بمقدار 1 فالقوة هي 1 أي 10000000
العدد ككل يصبح : 11000000000000001101010011111101= 2.013- ( ملاحظة : أحياناً تطبق بعض خوارزميات التقريب rounding لمحاولة الحفاظ على الدقة قدر الإمكان عند الإزاحة ولكن لن نستخدم أياً منها الآن )
بالعودة إلى التحدي , لنفترض أننا نريد جمع 3 إلى العدد السحري السابق , 103=211 = 1.1 × 21 وتمثيله 01000000010000000000000000000000
    11000000000000001101010011111101
+01000000010000000000000000000000
القوى متساوية ولكن الإشارات مختلفة (لذلك سنقوم بجمع المتمم للعدد الأول مع العدد الثاني )
المتمم للعدد الأول11111110010101100000011 ونضيف بت الإشارة 1 لليسار
+ العدد الثاني   10000000000000000000000 ونضيف بت الإشارة 0 لليسار
الناتج           01111110010101100000011 ونهمل المنقول الأخير
والناتج النهائي 01000000001111110010101100000011
والعملية الأخيرة هي عملية الـnormalization حيث نقوم بالإزاحة لليسار حتى يصبح البت الأكثر أهمية في الـmantissa هو 1 (مع كل إزاحة ننقص القوة بمقدار 1 )
فينتج 00111111111111100101011000000110
إذاً : فالعمليات المطلوبة هي : جمع ثنائي (قد يتطلب المتمم الثنائي للطرح ) مقارنة القوى (قد يتطلب زيادة منطقية وإزاحة لليمين ) وأخيراً إزاحة لليسار مع إنقاص القوة
(الآن عندي امتحان وفترت همتي الكودية سأتوقف هنا بدون إرفاق كود عام )
يُتبع إن شاء الله
الرابط التالي رابط dropbpx يحتوي العديد من الامور المفيدة الرابط الرئيسي ما يهمنا منه هو هذا الرابط

والله ولي التوفيق
 

1

مواضيع مشابهة

عدد الزوار حالياً

المتواجدون خلال آخر دقيقتين · يتحدّث كل ٣٠ ثانية

—الإجمالي—أعضاء مسجّلون—زوار بدون تسجيل

جارٍ التحقق من المتواجدين…