JavaRush /Java Blog /Random-TL /Ang aparato ng mga tunay na numero

Ang aparato ng mga tunay na numero

Nai-publish sa grupo
Kamusta! Sa panayam ngayon ay pag-uusapan natin ang tungkol sa mga numero sa Java, at partikular na tungkol sa mga tunay na numero. Ang aparato ng mga tunay na numero - 1Huwag mag-panic! :) Walang mathematical na kahirapan sa lecture. Pag-uusapan natin ang tungkol sa mga tunay na numero ng eksklusibo mula sa aming "programmer" na pananaw. Kaya, ano ang "tunay na mga numero"? Ang mga tunay na numero ay mga numerong may fractional na bahagi (na maaaring zero). Maaari silang maging positibo o negatibo. Narito ang ilang halimbawa: 15 56.22 0.0 1242342343445246 -232336.11 Paano gumagana ang isang tunay na numero? Medyo simple: ito ay binubuo ng isang integer na bahagi, isang fractional na bahagi at isang tanda. Para sa mga positibong numero ang senyales ay karaniwang hindi malinaw na ipinahiwatig, ngunit para sa mga negatibong numero ito ay ipinahiwatig. Noong nakaraan, sinuri namin nang detalyado kung anong mga operasyon sa mga numero ang maaaring gawin sa Java. Kabilang sa mga ito ang maraming karaniwang mga operasyon sa matematika - karagdagan, pagbabawas, atbp. Mayroon ding ilang mga bago para sa iyo: halimbawa, ang natitira sa dibisyon. Ngunit paano eksaktong gumagana ang pagtatrabaho sa mga numero sa loob ng isang computer? Sa anong anyo sila nakaimbak sa memorya?

Pag-iimbak ng mga totoong numero sa memorya

Sa palagay ko hindi magiging isang pagtuklas para sa iyo na ang mga numero ay maaaring malaki at maliit :) Maaari silang ihambing sa isa't isa. Halimbawa, ang bilang na 100 ay mas mababa sa numerong 423324. Nakakaapekto ba ito sa pagpapatakbo ng computer at ng aming programa? Sa totoo lang oo . Ang bawat numero ay kinakatawan sa Java ng isang tiyak na hanay ng mga halaga :
Uri Laki ng memorya (bits) Saklaw ng mga halaga
byte 8 bit -128 hanggang 127
short 16 bit -32768 hanggang 32767
char 16 bit unsigned integer na kumakatawan sa isang UTF-16 na character (mga titik at numero)
int 32 bits mula -2147483648 hanggang 2147483647
long 64 bits mula -9223372036854775808 hanggang 9223372036854775807
float 32 bits mula 2 -149 hanggang (2-2 -23 )*2 127
double 64 bits mula 2 -1074 hanggang (2-2 -52 )*2 1023
Ngayon ay pag-uusapan natin ang tungkol sa huling dalawang uri - floatat double. Parehong gumaganap ang parehong gawain - kumakatawan sa mga fractional na numero. Ang mga ito ay madalas ding tinatawag na " floating point numbers" . Tandaan ang terminong ito para sa hinaharap :) Halimbawa, ang numerong 2.3333 o 134.1212121212. Medyo kakaiba. Pagkatapos ng lahat, lumalabas na walang pagkakaiba sa pagitan ng dalawang uri na ito, dahil ginagawa nila ang parehong gawain? Ngunit may pagkakaiba. Bigyang-pansin ang column na "laki sa memorya" sa talahanayan sa itaas. Ang lahat ng mga numero (at hindi lamang mga numero - lahat ng impormasyon sa pangkalahatan) ay naka-imbak sa memorya ng computer sa anyo ng mga bit. Ang bit ay ang pinakamaliit na yunit ng impormasyon. Ito ay medyo simple. Anumang bit ay katumbas ng alinman sa 0 o 1. At ang salitang “ bit ” mismo ay nagmula sa Ingles na “ binary digit ” - isang binary number. Sa tingin ko marahil ay narinig mo na ang tungkol sa pagkakaroon ng binary number system sa matematika. Ang anumang decimal na numero na pamilyar sa atin ay maaaring katawanin bilang isang set ng mga isa at mga zero. Halimbawa, ang numerong 584.32 sa binary ay magiging ganito: 100100100001010001111 . Ang bawat isa at zero sa numerong ito ay isang hiwalay na bit. Ngayon ay dapat kang maging mas malinaw tungkol sa pagkakaiba sa pagitan ng mga uri ng data. Halimbawa, kung gagawa kami ng isang bilang ng uri float, mayroon lang kaming 32 bits sa aming pagtatapon. Kapag lumilikha ng isang numero, floatito ay eksakto kung gaano karaming espasyo ang ilalaan para dito sa memorya ng computer. Kung gusto nating likhain ang numerong 123456789.65656565656565, sa binary ito ay magiging ganito: 11101011011110011010001010110101000000 . Binubuo ito ng 38 at mga zero, iyon ay, 38 bits ang kinakailangan upang maiimbak ito sa memorya. floatAng numerong ito ay sadyang hindi "magkasya" sa uri ! Samakatuwid, ang numerong 123456789 ay maaaring katawanin bilang isang uri double. Hanggang 64 bits ang inilalaan para iimbak ito: nababagay ito sa amin! Siyempre, ang hanay ng mga halaga ay magiging angkop din. Para sa kaginhawahan, maaari mong isipin ang isang numero bilang isang maliit na kahon na may mga cell. Kung may sapat na mga cell upang mag-imbak ng bawat bit, ang uri ng data ay napili nang tama :) Ang aparato ng mga tunay na numero - 2Siyempre, ang iba't ibang mga halaga ng inilalaan na memorya ay nakakaapekto rin sa numero mismo. Pakitandaan na ang mga uri floatay may doubleiba't ibang hanay ng mga halaga. Ano ang ibig sabihin nito sa pagsasanay? Ang isang numero doubleay maaaring magpahayag ng higit na katumpakan kaysa sa isang numero float. Ang mga 32-bit na floating point na numero (sa Java ito ang eksaktong uri float) ay may katumpakan na humigit-kumulang 24 bits, iyon ay, mga 7 decimal na lugar. At ang mga 64-bit na numero (sa Java ito ang uri double) ay may katumpakan na humigit-kumulang 53 bits, iyon ay, humigit-kumulang 16 na decimal na lugar. Narito ang isang halimbawa na nagpapakita ng pagkakaibang ito nang maayos:
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);
   }
}
Ano ang dapat nating makuha dito bilang isang resulta? Tila ang lahat ay medyo simple. Mayroon kaming numerong 0.0, at idinaragdag namin ang 0.11111111111111111 dito nang 7 beses sa isang hilera. Ang resulta ay dapat na 0.777777777777777. Ngunit gumawa kami ng isang numero float. Ang laki nito ay limitado sa 32 bits at, tulad ng sinabi namin kanina, ito ay may kakayahang magpakita ng isang numero hanggang sa humigit-kumulang sa ika-7 decimal na lugar. Samakatuwid, sa huli, ang resulta na makukuha natin sa console ay magiging iba sa inaasahan natin:

0.7777778
Ang numero ay tila "naputol." Alam mo na kung paano naka-imbak ang data sa memorya - sa anyo ng mga piraso, kaya hindi ka dapat mabigla. Malinaw kung bakit nangyari ito: ang resulta na 0.7777777777777777 ay hindi magkasya sa 32 bits na inilaan sa amin, kaya ito ay pinutol upang magkasya sa isang uri ng variable float:) Maaari naming baguhin ang uri ng variable doublesa aming halimbawa, at pagkatapos ay ang pangwakas ang resulta ay hindi mapuputol:
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
Mayroon nang 16 na decimal na lugar, ang resulta ay "magkasya" sa 64 bits. Sa pamamagitan ng paraan, marahil napansin mo na sa parehong mga kaso ang mga resulta ay hindi ganap na tama? Ang pagkalkula ay ginawa na may maliliit na pagkakamali. Pag-uusapan natin ang mga dahilan nito sa ibaba :) Ngayon sabihin natin ang ilang salita tungkol sa kung paano mo maihahambing ang mga numero sa isa't isa.

Paghahambing ng mga tunay na numero

Bahagyang nahawakan na namin ang isyung ito sa huling panayam, noong pinag-usapan namin ang tungkol sa mga pagpapatakbo ng paghahambing. Hindi namin muling susuriin ang mga pagpapatakbo tulad ng >, <, >=. <=Sa halip, tingnan natin ang isang mas kawili-wiling halimbawa:
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);
   }
}
Anong numero sa tingin mo ang ipapakita sa screen? Ang lohikal na sagot ay ang magiging sagot: ang numero 1. Nagsisimula kaming magbilang mula sa numerong 0.0 at sunud-sunod na magdagdag ng 0.1 dito nang sampung beses sa isang hilera. Mukhang tama ang lahat, dapat isa lang. Subukang patakbuhin ang code na ito, at ang sagot ay lubos na magugulat sa iyo :) Console output:

0.9999999999999999
Ngunit bakit nagkaroon ng pagkakamali sa gayong simpleng halimbawa? O_o Dito kahit isang fifth grader ay madaling makasagot ng tama, ngunit ang Java program ay gumawa ng hindi tumpak na resulta. Ang "hindi tumpak" ay isang mas mahusay na salita dito kaysa sa "mali." Nakakuha pa rin kami ng isang numero na napakalapit sa isa, at hindi lamang ilang random na halaga :) Naiiba ito sa tama sa literal ng isang milimetro. Pero bakit? Marahil ito ay isang beses lamang na pagkakamali. Baka nag-crash ang computer? Subukan nating magsulat ng isa pang halimbawa.
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!");
   }
}
Output ng console:

f1 = 1.0999999999999999
f2 = 1.1
f1 и f2 не равны!
Kaya, ito ay malinaw na hindi isang bagay ng computer glitches :) Ano ang nangyayari? Ang mga uri ng error na ito ay nauugnay sa paraan ng pagre-represent ng mga numero sa binary form sa memorya ng computer. Ang katotohanan ay sa binary system imposibleng tumpak na kumatawan sa numero 0.1 . Sa pamamagitan ng paraan, ang sistema ng desimal ay mayroon ding katulad na problema: imposibleng kumatawan nang tama ang mga fraction (at sa halip na ⅓ ay makakakuha tayo ng 0.33333333333333..., na hindi rin masyadong tamang resulta). Ito ay tila isang maliit na bagay: sa gayong mga kalkulasyon, ang pagkakaiba ay maaaring isang daang libong bahagi (0.00001) o mas kaunti pa. Ngunit paano kung ang buong resulta ng iyong Napakaseryosong Programa ay nakasalalay sa paghahambing na ito?
if (f1 == f2)
   System.out.println("Rocket flies into space");
else
   System.out.println("The launch is canceled, everyone goes home");
Malinaw naming inaasahan na magkapantay ang dalawang numero, ngunit dahil sa disenyo ng panloob na memorya, kinansela namin ang paglulunsad ng rocket. Ang aparato ng mga tunay na numero - 3Kung gayon, kailangan nating magpasya kung paano ihambing ang dalawang floating-point na numero upang ang resulta ng paghahambing ay mas... ummm... predictable. Kaya, natutunan na natin ang panuntunan No. 1 kapag naghahambing ng mga tunay na numero: huwag gumamit ng ==mga floating point na numero kapag naghahambing ng mga tunay na numero. Ok, sa tingin ko ay sapat na ang masamang halimbawa :) Tingnan natin ang isang magandang halimbawa!
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");
   }
}
Narito kami ay mahalagang ginagawa ang parehong bagay, ngunit binabago ang paraan ng paghahambing namin ng mga numero. Mayroon kaming espesyal na "threshold" na numero - 0.0001, isang sampu-sa-libo. Maaaring iba ito. Depende ito sa kung gaano katumpak ang isang paghahambing na kailangan mo sa isang partikular na kaso. Maaari mo itong gawing mas malaki o mas maliit. Gamit ang pamamaraan, Math.abs()nakukuha namin ang modulus ng isang numero. Ang modulus ay ang halaga ng isang numero anuman ang tanda. Halimbawa, ang mga numero -5 at 5 ay magkakaroon ng parehong modulus at magiging katumbas ng 5. Ibinabawas namin ang pangalawang numero mula sa una, at kung ang resulta, anuman ang tanda, ay mas mababa sa threshold na itinakda namin, kung gayon pantay-pantay ang ating mga numero. Sa anumang kaso, ang mga ito ay katumbas ng antas ng katumpakan na itinatag namin gamit ang aming "numero ng threshold" , ibig sabihin, sa pinakamababa ay katumbas sila hanggang sa isang sampung libo. Ang paraan ng paghahambing na ito ay magliligtas sa iyo mula sa hindi inaasahang pag-uugali na nakita namin sa kaso ng ==. Ang isa pang magandang paraan upang ihambing ang mga tunay na numero ay ang paggamit ng isang espesyal na klase BigDecimal. Ang klase na ito ay partikular na nilikha upang mag-imbak ng napakalaking numero na may fractional na bahagi. Hindi tulad doubleng at float, kapag gumagamit BigDecimalng karagdagan, pagbabawas at iba pang mga pagpapatakbo ng matematika ay ginaganap hindi gamit ang mga operator ( +-, atbp.), ngunit gumagamit ng mga pamamaraan. Ito ang magiging hitsura nito sa aming kaso:
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");
   }
}
Anong uri ng console output ang makukuha natin?

f1 = 1.1000000000000000610622663543836097232997417449951171875
f2 = 1.1000000000000000610622663543836097232997417449951171875
f1 и f2 равны
Nakuha namin ang eksaktong resulta na inaasahan namin. At bigyang pansin kung gaano katumpak ang aming mga numero, at kung gaano karaming mga decimal na lugar ang magkasya sa kanila! Higit pa kaysa sa at floatkahit sa double! Tandaan ang klase BigDecimalpara sa hinaharap, tiyak na kakailanganin mo ito :) Phew! Medyo mahaba ang lecture, ngunit nagawa mo ito: magaling! :) Magkita-kita tayo sa susunod na aralin, programmer sa hinaharap!
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION