JavaRush /Java блогы /Random-KK /Нақты сандар құрылғысы

Нақты сандар құрылғысы

Топта жарияланған
Сәлеметсіз бе! Бүгінгі дәрісте біз Java тіліндегі сандар туралы, нақтырақ айтқанда нақты сандар туралы айтатын боламыз. Нақты сандар құрылғысы – 1Дүрлікпеңіз! :) Дәрісте математикалық қиындықтар болмайды. Біз нақты сандар туралы тек «бағдарламашы» көзқарасымызбен сөйлесетін боламыз. Сонымен, «нақты сандар» дегеніміз не? Нақты сандар – бөлшек бөлігі бар сандар (ол нөлге тең болуы мүмкін). Олар оң немесе теріс болуы мүмкін. Міне бірнеше мысалдар: 15 56,22 0,0 1242342343445246 -232336,11 Нақты сан қалай жұмыс істейді? Өте қарапайым: ол бүтін бөліктен, бөлшек бөліктен және таңбадан тұрады. Оң сандар үшін белгі әдетте анық көрсетілмейді, бірақ теріс сандар үшін ол көрсетіледі. Бұрын біз Java тілінде сандармен қандай операцияларды орындауға болатынын егжей-тегжейлі қарастырдық . Олардың арасында көптеген стандартты математикалық амалдар болды - қосу, алу және т.б. Сондай-ақ сіз үшін жаңалары болды: мысалы, бөлудің қалған бөлігі. Бірақ сандармен жұмыс компьютерде қалай жұмыс істейді? Олар жадта қандай формада сақталады?

Нақты сандарды жадта сақтау

Сандардың үлкен және кіші болуы сіз үшін жаңалық болмайды деп ойлаймын :) Оларды бір-бірімен салыстыруға болады. Мысалы, 100 саны 423324 санынан кіші. Бұл компьютердің және біздің бағдарламаның жұмысына әсер ете ме? Шын мәнінде - иә . Әрбір сан Java тілінде белгілі бір мәндер ауқымымен ұсынылған :
Түр Жад өлшемі (бит) Мәндер ауқымы
byte 8 бит -128-ден 127-ге дейін
short 16 бит -32768-ден 32767-ге дейін
char 16 бит UTF-16 таңбасын білдіретін таңбасыз бүтін сан (әріптер мен сандар)
int 32 бит -2147483648-ден 2147483647-ге дейін
long 64 бит -9223372036854775808 бастап 9223372036854775807 дейін
float 32 бит 2 -149- дан (2-2 -23 )*2 127-ге дейін
double 64 бит 2 -1074- тен (2-2 -52 )*2 1023-ке дейін
Бүгін біз соңғы екі түрі туралы сөйлесетін боламыз - floatжәне double. Екеуі де бірдей тапсырманы орындайды - бөлшек сандарды білдіреді. Оларды жиі « жылжымалы нүкте сандары» деп те атайды . Болашақ үшін бұл терминді есте сақтаңыз :) Мысалы, 2.3333 немесе 134.1212121212 саны. Біртүрлі. Ақыр соңында, бұл екі түрдің айырмашылығы жоқ, өйткені олар бір тапсырманы орындайды? Бірақ айырмашылық бар. Жоғарыдағы кестедегі «жадтағы өлшем» бағанына назар аударыңыз. Барлық сандар (тек сандар ғана емес – жалпы барлық ақпарат) компьютер жадында бит түрінде сақталады. Бит – ақпараттың ең кіші бірлігі. Бұл өте қарапайым. Кез келген бит 0 немесе 1-ге тең. Ал “ бит ” сөзінің өзі ағылшын тіліндегі “ binary digit ” – екілік саннан шыққан . Менің ойымша, сіз математикада екілік санау жүйесінің бар екендігі туралы естіген шығарсыз. Бізге таныс кез келген ондық сандар бірліктер мен нөлдер жиыны ретінде ұсынылуы мүмкін. Мысалы, екілік жүйеде 584,32 саны келесідей болады: 100100100001010001111 . Бұл сандағы әрбір бір және нөл жеке бит болып табылады. Енді деректер түрлері арасындағы айырмашылық туралы көбірек білуіңіз керек. Мысалы, егер біз сан түрін жасасақ float, бізде тек 32 бит бар. Нөмірді құру кезінде floatкомпьютердің жадында оған қанша орын бөлінеді. Егер біз 123456789.65656565656565 санын жасағымыз келсе, екілік жүйеде ол келесідей болады: 11101011011110011010001010110101000000 . Ол 38 бірлік пен нөлден тұрады, яғни оны жадта сақтау үшін 38 бит қажет. Бұл сан түрге float«сәйкес келмейді» ! Сондықтан 123456789 санын түр ретінде көрсетуге болады double. Оны сақтау үшін 64 бит бөлінген: бұл бізге сәйкес келеді! Әрине, мәндер ауқымы да қолайлы болады. Ыңғайлы болу үшін санды ұяшықтары бар шағын қорап ретінде қарастыруға болады. Әрбір битті сақтау үшін жеткілікті ұяшықтар болса, онда деректер түрі дұрыс таңдалады :) Нақты сандар құрылғысы – 2Әрине, бөлінген жадтың әртүрлі көлемі де санның өзіне әсер етеді. Түрлердің мәндер ауқымы әртүрлі floatекенін ескеріңіз . doubleБұл іс жүзінде нені білдіреді? Сан doubleсанға қарағанда үлкен дәлдікті көрсете алады float. 32-биттік өзгермелі нүкте сандары (Java-да дәл осы түрі float) шамамен 24 бит, яғни шамамен 7 ондық таңбаға ие. Ал 64 разрядты сандар (Java тілінде бұл түрі double) шамамен 53 бит, яғни шамамен 16 ондық таңбаға ие. Міне, бұл айырмашылықты жақсы көрсететін мысал:
public class Main {

   public static void main(String[] args)  {

       float f = 0.0f;
       for (int i=1; i <= 7; i++) {
           f += 0.1111111111111111;
       }

       System.out.println(f);
   }
}
Нәтижесінде біз не алуымыз керек? Барлығы өте қарапайым болып көрінеді. Бізде 0,0 саны бар, оған қатарынан 0,1111111111111111 қосамыз. Нәтиже 0,777777777777777 болуы керек. Бірақ біз нөмір жасадық float. Оның өлшемі 32 битпен шектелген және жоғарыда айтқанымыздай, ол шамамен 7-ші ондық белгіге дейінгі санды көрсете алады. Сондықтан, сайып келгенде, консольде алған нәтиже біз күткеннен өзгеше болады:

0.7777778
Нөмір «кесілген» сияқты болды. Сіз деректердің жадта қалай сақталатынын білесіз - бит түрінде, сондықтан бұл сізді таң қалдырмауы керек. Неліктен бұл орын алғаны түсінікті: 0,777777777777777 нәтижесі бізге бөлінген 32 битке сәйкес келмеді, сондықтан ол түрдегі айнымалыға сәйкес болу үшін қысқартылды float:) Біз мысалдағы айнымалының түрін өзгерте аламыз double, содан кейін соңғы нәтиже қысқартылмайды:
public class Main {

   public static void main(String[] args)  {

       double f = 0.0;
       for (int i=1; i <= 7; i++) {
           f += 0.1111111111111111;
       }

       System.out.println(f);
   }
}

0.7777777777777779
Қазірдің өзінде 16 ондық таңба бар, нәтиже 64 битке «сәйкес». Айтпақшы, сіз екі жағдайда да нәтижелер толығымен дұрыс емес екенін байқадыңыз ба? Есептеу шағын қателермен жасалды. Мұның себептері туралы төменде айтатын боламыз :) Енді сандарды бір-бірімен қалай салыстыруға болатыны туралы бірнеше сөз айтайық.

Нақты сандарды салыстыру

Өткен лекцияда салыстыру операциялары туралы айтқан кезде біз бұл мәселеге ішінара тоқталған болатынбыз. >Біз , <, сияқты операцияларды қайта талдамаймыз >=. <=Оның орнына қызықтырақ мысалды қарастырайық:
public class Main {

   public static void main(String[] args)  {

       double f = 0.0;
       for (int i=1; i <= 10; i++) {
           f += 0.1;
       }

       System.out.println(f);
   }
}
Экранда қандай сан шығады деп ойлайсыз? Логикалық жауап жауап болар еді: 1 саны. Біз 0,0 санынан санауды бастаймыз және оған қатарынан он рет 0,1 қосамыз. Барлығы дұрыс сияқты, ол бір болуы керек. Осы codeты іске қосып көріңіз және жауап сізді қатты таң қалдырады :) Консоль шығысы:

0.9999999999999999
Бірақ неге мұндай қарапайым мысалда қате пайда болды? O_o Мұнда тіпті бесінші сынып оқушысы да оңай дұрыс жауап бере алды, бірақ Java бағдарламасы дұрыс емес нәтиже берді. Мұнда «дұрыс емес» дегеннен гөрі «дәл емес» деген жақсы сөз. Біз әлі де кездейсоқ мән емес, біреуге жақын сан алдық :) Ол дұрыс саннан миллиметрмен ерекшеленеді. Бірақ неге? Мүмкін бұл бір реттік қателік шығар. Мүмкін компьютер бұзылған шығар? Тағы бір мысал жазуға тырысайық.
public class Main {

   public static void main(String[] args)  {

       //add 0.1 to zero eleven times in a row
       double f1 = 0.0;
       for (int i = 1; i <= 11; i++) {
           f1 += .1;
       }

       // Multiply 0.1 by 11
       double f2 = 0.1 * 11;

       //should be the same - 1.1 in both cases
       System.out.println("f1 = " + f1);
       System.out.println("f2 = " + f2);

       // Let's check!
       if (f1 == f2)
           System.out.println("f1 and f2 are equal!");
       else
           System.out.println("f1 and f2 are not equal!");
   }
}
Консоль шығысы:

f1 = 1.0999999999999999
f2 = 1.1
f1 и f2 не равны!
Мәселен, бұл компьютердегі ақауларға қатысты емес :) Не болып жатыр? Осы сияқты қателер сандарды компьютер жадында екілік түрде көрсету тәсілімен байланысты. Екілік жүйеде 0,1 санын дәл көрсету мүмкін емес . Айтпақшы, ондық жүйеде де осындай мәселе бар: бөлшектерді дұрыс көрсету мүмкін емес (және ⅓ орнына біз 0,333333333333333... аламыз, бұл да дұрыс нәтиже емес). Бұл ұсақ-түйек болып көрінеді: мұндай есептеулермен айырмашылық жүз мыңнан бір бөлікке (0,00001) немесе одан да аз болуы мүмкін. Бірақ егер сіздің өте маңызды бағдарламаңыздың бүкіл нәтижесі осы салыстыруға байланысты болса ше?
if (f1 == f2)
   System.out.println("Rocket flies into space");
else
   System.out.println("The launch is canceled, everyone goes home");
Біз екі сан тең болады деп күткен едік, бірақ ішкі жад дизайнына байланысты зымыран ұшырудан бас тарттық. Нақты сандар құрылғысы – 3Олай болса, салыстыру нәтижесі көбірек... ммм... болжауға болатындай екі өзгермелі нүктелі сандарды қалай салыстыру керектігін шешуіміз керек. Сонымен, біз нақты сандарды салыстыру кезінде №1 ережені үйрендік: нақты сандарды салыстыру кезінде ешқашан өзгермелі нүктелі сандарды қолданбаңыз . == Жарайды, менің ойымша, жаман мысалдар жеткілікті :) Жақсы мысалды қарастырайық!
public class Main {

   public static void main(String[] args)  {

       final double threshold = 0.0001;

       //add 0.1 to zero eleven times in a row
       double f1 = .0;
       for (int i = 1; i <= 11; i++) {
           f1 += .1;
       }

       // Multiply 0.1 by 11
       double f2 = .1 * 11;

       System.out.println("f1 = " + f1);
       System.out.println("f2 = " + f2);

       if (Math.abs(f1 - f2) < threshold)
           System.out.println("f1 and f2 are equal");
       else
           System.out.println("f1 and f2 are not equal");
   }
}
Мұнда біз бірдей нәрсені жасаймыз, бірақ сандарды салыстыру тәсілін өзгертеміз. Бізде ерекше «табалдырық» саны бар - 0,0001, он мыңнан бір. Ол басқаша болуы мүмкін. Бұл нақты жағдайда қаншалықты дәл салыстыру қажет екеніне байланысты. Сіз оны үлкенірек немесе кішірейте аласыз. Әдістің көмегімен Math.abs()санның модулін аламыз. Модуль – таңбасына қарамастан санның мәні. Мысалы, -5 және 5 сандары бірдей модульге ие болады және 5-ке тең болады. Біріншіден екінші санды алып тастаймыз, ал нәтиже белгісіне қарамастан, біз белгілеген шекті мәннен аз болса, онда сандарымыз тең. Қалай болғанда да, олар біздің «шекті сан» арқылы белгілеген дәлдік дәрежесіне тең , яғни олар кем дегенде он мыңнан біріне тең. Салыстырудың бұл әдісі сізді біз көрген күтпеген мінез-құлықтан құтқарады ==. Нақты сандарды салыстырудың тағы бір жақсы тәсілі - арнайы классты пайдалану BigDecimal. Бұл класс бөлшек бөлігі бар өте үлкен сандарды сақтау үшін арнайы жасалған. doubleЖәне- ден айырмашылығы float, қосуды қолданғанда азайту және басқа да математикалық амалдар ( , т.б.) операторлары арқылы емес, әдістер арқылы BigDecimalорындалады . +-Бұл біздің жағдайда келесідей болады:
import java.math.BigDecimal;

public class Main {

   public static void main(String[] args)  {

       /*Create two BigDecimal objects - zero and 0.1.
       We do the same thing as before - add 0.1 to zero 11 times in a row
       In the BigDecimal class, addition is done using the add () method */
       BigDecimal f1 = new BigDecimal(0.0);
       BigDecimal pointOne = new BigDecimal(0.1);
       for (int i = 1; i <= 11; i++) {
           f1 = f1.add(pointOne);
       }

       /*Nothing has changed here either: create two BigDecimal objects
       and multiply 0.1 by 11
       In the BigDecimal class, multiplication is done using the multiply() method*/
       BigDecimal f2 = new BigDecimal(0.1);
       BigDecimal eleven = new BigDecimal(11);
       f2 = f2.multiply(eleven);

       System.out.println("f1 = " + f1);
       System.out.println("f2 = " + f2);

       /*Another feature of BigDecimal is that number objects need to be compared with each other
       using the special compareTo() method*/
       if (f1.compareTo(f2) == 0)
           System.out.println("f1 and f2 are equal");
       else
           System.out.println("f1 and f2 are not equal");
   }
}
Біз қандай консоль шығысын аламыз?

f1 = 1.1000000000000000610622663543836097232997417449951171875
f2 = 1.1000000000000000610622663543836097232997417449951171875
f1 и f2 равны
Біз дәл күткен нәтижеге қол жеткіздік. Біздің сандар қаншалықты дәл шыққанына және оларға қанша ондық таңбаға сәйкес келетініне назар аударыңыз! floatІшінде және тіпті ішінде қарағанда әлдеқайда көп double! BigDecimalБолашаққа арналған сыныпты есте сақтаңыз , ол сізге міндетті түрде керек болады :) Уф! Дәріс өте ұзақ болды, бірақ сіз оны орындадыңыз: жарайсың! :) Келесі сабақта кездескенше, болашақ программист!
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION