PDA

View Full Version : سوال: مشکل با double



iman_n21
سه شنبه 05 اسفند 1393, 23:08 عصر
انتظار داریم برنامه زیر خروجی 0.9 چاپ کنه در حالیکه 1.1 رو چاپ میکنه!
اگه ممکنه راهنمایی بفرمایید که اشکال در کجاست؟



double x=0;
for(double n=0; n < 1 ;n=n+0.1 ){
n=n+0.1;
x=n;
}
cout<<x<<endl;

shahmohammadi
چهارشنبه 06 اسفند 1393, 00:10 صبح
سلام.
معلومه که مشکل از دابل هست. چون همین دستورو اگه به صورت اینت بنویسیم جواب 9 به دست می آد:
int x=0;
for(int n=0; n < 10 ;n=n+1 ){
n=n+1;
x=n;
}
cout<<x<<endl;

shahmohammadi
چهارشنبه 06 اسفند 1393, 00:17 صبح
اینطور که معلومه هنگامی که یک متغیر دابل مقدارش 1 باشه در دستور شرطی n<1 کوچک تر از یک به حساب می آد:( به خروجی توجه کنید)
double x=0;
for(double n=0; n < 1 ;n=n+0.1 ){
cout<<"n="<<n<<" n<1: "<<(n<1)<<'\t'<<"n=n+0.1;"<<'\t';
n=n+0.1;
x=n;
cout<<"n="<<n<<" x="<<x<<"\n";
}
cout<<x<<endl;

shahmohammadi
چهارشنبه 06 اسفند 1393, 00:26 صبح
البته به خروجی این کد هم توجه کنید که نشون میده همیشه اینطور نیست:

double x=1;
if(x<1) cout<<"x is les than 1"<<endl;
if(x==1) cout<<"x is equal to 1"<<endl;
if(x>1) cout<<"x is bigger than 1"<<endl;

double x2=0;
x2+=0.1;
x2+=0.1;
x2+=0.1;
x2+=0.1;
x2+=0.1;
x2+=0.1;
x2+=0.1;
x2+=0.1;
x2+=0.1;
x2+=0.1;

if(x2<1) cout<<"x2 is les than 1"<<endl;
if(x2==1) cout<<"x2 is equal to 1"<<endl;
if(x2>1) cout<<"x2 is bigger than 1"<<endl;
cout<<x2;
:متفکر:

iman_n21
چهارشنبه 06 اسفند 1393, 08:13 صبح
توجه بفرمایید که در حلقه اگر بجای double از float استفاده کنیم جواب درست بدست میاد !
در واقع مشکل در 0.1 هست که ظاهرا چیزی شبیه به 0.100000000001 در نظر گرفته میشه و در محاسبات همین عدد به جای 0.1 اعمال میشه که خب در 5 بار تکرار به بعد اثر خودش رو نشون میده. ولی چرا ++C باید 0.1 رو به این صورت در نظر بگیره ؟!!
برنامه رو دیباگ بفرمایید و تغییر وضعیت متغیرها رو بررسی کنید.
میشه 0.1 رو در یک متغیر double ذخیره کرد و به راحتی اونچه رو که ذخیره میشه دید.



double x=0,y=0.1;
for(float n=0; n<1 ;n=n+y)
{
n=n+y;
x=n;
}
cout<<x<<"\n";


128850

rahnema1
چهارشنبه 06 اسفند 1393, 10:25 صبح
سلام، علتش اینه که نحوه ذخیره اعداد double و float در سیستم با هم متفاوته و همچنین اینها خطای گرد شدن دارند و ..در نتیجه جوابها یکسان نمیشن. اصولا برای بررسی شرط نباید از اعداد اعشار استفاده کرد. مگر اینکه خطای احتمالی را هم در نظر بگیریم در حلقه یک جا n به عدد 1 نزدیک میشه ولی در یکی از اون دو برنامه یکی یک اپسیلون بیشتر از 1 میشه ولی دیگری یک اپسیلون کمتر از یک میشه که جواب متفاوت میشه

iman_n21
چهارشنبه 06 اسفند 1393, 16:52 عصر
خب اگه بخوایم 0.1 خالص و واقعی رو در متغیری اعشاری ذخیره کنیم راه حل چیه ؟

rahnema1
چهارشنبه 06 اسفند 1393, 17:11 عصر
متاسفانه این امکان وجود نداره چون کامپیوتر محدوده
مثلا ممکنه نمایش اعشاری یک عدد به صورت تکرار 0.6666666666 باشه کامپیوتر تا یک دقتی می تونه ذخیره کنه اعداد اعشاری به صورت دودویی در کامپیوتر ذخیره میشه و عملیات حسابی هم روی نمایش دودویی اعداد اعشاری صورت می گیره که همه اینها دقت محدود دارند

shahmohammadi
پنج شنبه 07 اسفند 1393, 18:38 عصر
با سلام. اگر خواستید در مورد جزییات نحوه ی ذخیره سازی اعداد اعشاری در double بخونید می تونید به لینک زیر مراجعه کنید. این لینک استانداردی هست که IEEE تعیین کرده:
http://www.cs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF

فرصت نبود خودم هم بیشتر بررسی کنم.

shahmohammadi
پنج شنبه 07 اسفند 1393, 21:43 عصر
بعد از بررسی فایل بالا و نیز نوشتن تعدادی برنامه به منظور مشاهده خروجی ها مطالب زیر رو نوشتم:
ابتدا یک مقدمه:
در مبنای 10 تعدادی از اعداد اعشاری تا آخر می روند. مثل 0.333333 یا 0.6666666 . عدد 2/3 به صورت یک صفر تعداد بینهایت 6 ادامه دارد. اگر حاصل 2/3 را بنویسیم 0.666 هر چقدر هم که 6 ها را ادامه دهیم باز هم کوچک تر از مقدار واقعی آن نوشته ایم، چرا که باز هم تعداد زیادی از 6 ها را ننوشته ایم. اگر بخواهیم فقط از 5 رقم استفاده کنیم به صورت زیر می نویسم: 0.6667 چرا که این عدد به 2/3 نزدیک تر از 0.6666 است. در واقع 2/3 بین این دو عدد است و لی به 0.6667 نزدیک تر است. اعدادی مثل 0.1 در مبنای دو به صورت تکراری هستند و تا آخر می روند. مانند 2/3 در مبنای 10. بنا براین در ذخیره سازی این عدد در دابل آخر آن را گرد می کنیم. این گرد کردن باعث می شه که عدد کمی بزرگ تر باشه. همون یک اضافی که آقا یا خانم iman_n21 (http://barnamenevis.org/member.php?22590-iman_n21) توی مراحل دیتاگ دیده بودند.

و اما بعد:
فرض کنید بخواهیم که عدد 0.1 را در متغیر دابل ذخیره کنیم. ابتدا به باینری تبدیل کنیم ببینیم چه شکلی می شود. اگر از یک ماشین حساب باینری استفاده کنید عدد زیر به دست می آید: 0.000110011001100110011001100110011001100110011001 1001101
مشاهده می شود که تا آخر می رود.
و اما این عدد چه طوری توی دابل ذخیره میشه:
طبیعی است که هر عدد اعشاری باینری شامل یه تعداد صفر و یک هست. از ممیز به بعد تا هر کجا هم که اعداد اعشاری 0 باشند بالاخره در یک جایی به یک می رسیم. که اگر نرسیم خوب یعنی عدد صحیح است. اون عدد یک نیازی به ذخیره سازی ندارد. توی فایل IEEE نوشته بود که صرفه جویی کردن.
از طرفی یک مقدمه دیگر هم از مبنای 10 می نویسم:
همونطور که از ریاضیات راهنمایی یا شاید هم اولایل دبیرستان یادتون هست هر عدد اعشاری رو میشه به صورت ضرب یک عدد صحیح در توان منفی 10 به دست آورد. مثال های زیر:
0.9=9*10^(-1)=9/(10^1)
0.25=2.5*10^(-1)=25*10^(-2)=25/10^2=25/100
در مبنای دو هم می توان همین کار را به جای 10 با 2 انجام داد.
متغیر دابل از این روش برای ذخیره عدد اعشاری استفاده می کند. عبارت قبل باید در مبنای دو به صورت زیر نوشته شود:
1.100110011001100110011001100110011001100110011001 101*10^any
در کد بالا تمام اعداد در مبنای دو نوشته شده اند. حتی عدد 2 که در مبنای دو می شود 10 . چون نخواستم بشمرم ببینم چند عدد ممیز اینور اومده توان رو any نوشتم. خب. حالا اون (یک) اول که پشت ممیز هست در دابل ذخیره نمی شه. چون طبیعیه که وجود داره. این (یک) رو در یاد داشته باشید. بعدا بهش میرسیم.

دابل شامل 8 بایت است که از سه قسمت تشکیل شده است. بیت آخر (پر ارزش ترین بیت) که بیت علامت هست. 0یعنی +، 1 یعنی منفی. بعد از بیت علامت 11 بیت اینور تر میشه توان (exponent). که با e هم نشون می دن. تا الان 12 بیت که میشه یک بایت و نصفی گفتیم. خوب حالا خود 2 (یا 10 باینری) رو که پایه هست داریم. توانش هم میشه همون مقدار این 11 بیت منهای 1023 . موند 52 بیت. این 52 بیت شامل همون 10011001100110011001100110011001100110011001100110 1 هست. که بدون (یک) هست. این 52 بیت میشه:
9 99 99 99 99 99 9a
توی هگزا دسیمال نوشتم. 9 ها رو تا آخر بگیریم می روند. ولی خوب به همون دلیلی که توی مقدمه گفتم باید آخرشو گرد کنیم به عدد بزرگتر از 9 یعنی a.
فرمول زیر مقدار ذخیره شده در دابل رو می ده:
(-1)^sign * (1.b51 b52 ... b0)* 2^ (e-1023)
برنامه زیر رو ببینید:
ابتدا عدد x برابر با سفر میشه و در هر مرحله مقدار 0.1 به اون اضافه میشه. محتویات بایت های اون به هگزا دسیمال چاپ میشه و بعد محتویات عدد واقعی که در y ذخیره شده چاپ میشه:
#include <cstdlib>
#include <iostream>
#include <iomanip>

using namespace std;
union dobchar {unsigned char arr[8]; double d; };

int main(int argc, char *argv[])
{
dobchar x,y[10];
x.d=0;
y[0].d=0.1;
y[1].d=0.2;
y[2].d=0.3;
y[3].d=0.4;
y[4].d=0.5;
y[5].d=0.6;
y[6].d=0.7;
y[7].d=0.8;
y[8].d=0.9;
y[9].d=1;

for(int j=0;j<10;j++)
{
x.d+=0.1;
cout<<(j+1)/10.0<<endl;
cout<<"x:\n";
for(int i=7;i >=0;i--)
{
cout<<hex<<int(x.arr[i])<<" ";
}
cout<<endl<<"y:\n";
for(int i=7;i >=0;i--)
{
cout<<hex<<int(y[j].arr[i])<<" ";
}
cout<<endl<<"-----------------\n\n";
}

system("PAUSE");
return EXIT_SUCCESS;
}

همون طور که می بینید اون مقدار گرد شدن ها آخر سر کار دست عمل جمع می ده و عدد کمی کوچک تر می شه.
موفق باشید.