A Julia egy általános célú nyelv, amellyel a legtöbb programozási feladat elvégezhető. A gyakorlatban azonban a fő felhasználási területei inkább a mérnöki és a tudományos munka. A gyors, sokoldalú és kifinomult numerikus számítások a nyelv tervezésének a középpontjában állnak.
Az egész szám egy „kerek” szám, amelynek nincs tizedespontja.
Az Alapok fogalomban láttuk, hogy egy egész értéket anélkül rendelhetünk hozzá egy változóhoz, hogy megadnánk a típusát.
Az olvashatóság kedvéért aláhúzásjelet is használhatunk számjegy-elválasztóként. A fordító figyelmen kívül hagyja őket.
julia> x = 3
3
julia> typeof(x)
Int64
julia> large_number = 1_234_567_890
1234567890
Belül a fordító azt az előjeles egész típust használja, amelyik a legjobban illik a processzorodhoz.
Modern számítógépeken ez általában Int64, ami a legtöbb feladathoz bőven elegendő.
A Típusok témájával egy későbbi fogalomban foglalkozunk részletesebben. Egyelőre remélhetőleg a numerikus típusok elég szemléletesek ahhoz, hogy ez a dokumentum érthető legyen.
Ahogy azt egy későbbi fogalomban látni fogjuk, a Julia natívan támogatja a nagyon nagy, többdimenziós tömböket. Ezek próbára tehetik mind a rendelkezésre álló memória mennyiségét, mind az adatátvitel sávszélességét.
Hogy finomabb kontrollt adjunk a programozónak, nemcsak lehetséges, hanem meglehetősen gyakori is megadni a kívánt előjeles vagy előjel nélküli egész típust. Sok olyan Julia-tanuló, aki korábban Pythont használt, ezt inkább a NumPyhoz hasonlíthatja, mint a sima Pythonhoz.
julia> y = Int8(42)
42
julia> typeof(y)
Int8
julia> z = UInt32(1024)
0x00000400
Figyeld meg, hogy a Julia alapértelmezés szerint hexadecimális formátumban jeleníti meg az előjel nélküli egészeket, mert ezeket a típusokat gyakran használják alacsony szintű bitműveletekhez.
Az egészek bináris, oktális vagy hexadecimális formában is megadhatók, rendre a 0b, 0o és 0x előtaggal.
julia> a = 0x10
0x10
julia> Int(a)
16
julia> b = 0b1010 # displays as hexadecimal
0x0a
BigInt
Minden egész típusnak van egy maximális és egy minimális értéke, amit tárolni tud:
julia> typemax(Int8)
127
julia> typemin(Int8)
-128
Ha kívül esünk ezen az érvényes tartományon, az egész túlcsordulást okoz, aminek az eredménye nagyon furcsának tűnik.
julia> c = Int8(126)
126
julia> c * c
4
Valószínűleg egyetérthetünk abban, hogy a 126 * 126 nem egyenlő 4-gyel!
A részletes magyarázathoz tudni kellene, hogyan tárolják az egészeket a processzor regiszterei, ami túlmutat e fogalom keretein.
A probléma mérsékelhető egy „szélesebb” típus használatával, egészen Int128-ig vagy UInt128-ig (a „szélesebb” jelentéséről alább lesz szó). Azonban mindegyiknek kemény korlátai vannak, mielőtt túlcsordulási problémákba ütköznénk.
Az egyik megoldás a BigInt típus használata, amelyet csak a számítógéped memóriája korlátoz.
Ez a Julia megvalósítása a tetszőleges pontosságú aritmetikának.
julia> 2 ^ 70
0
julia> big = BigInt(2) ^ 70
1180591620717411303424
julia> UInt128(big)
0x00000000000000400000000000000000
Mivel a 2 a 70. hatványon nem fér bele egy Int64-be, a fenti példa első számítása elbukik: jegyezd meg azonban, hogy nincs hibaüzenet.
Programozó, vigyázz!
Ha a 2-t konvertálással BigInt-té alakítjuk, megkapjuk a helyes választ, a hexadecimális megjelenítés pedig világosabbá teszi, hogy ez a 2 hatványa.
Nem meglepetés, hogy a lebegőpontos számoknak opcionálisan lehet tizedespontjuk és a pont után törtrészük.
julia> f = 3.45
3.45
julia> typeof(f)
Float64
Természetesen a tudományos jelölés is támogatott.
julia> avogadro = 6.02e23
6.02e23
Az egészekhez hasonlóan az alapértelmezett típus a legtöbb célra megfelel, de más előjeles típusok is elérhetők. Nincsenek előjel nélküli lebegőpontos típusok.
Rövidítésként Float32 értékeket hozhatunk létre f0 utótaggal.
julia> f32 = 4.56f0
4.56f0
julia> typeof(f32)
Float32
A többi méretű lebegőpontos számhoz explicit konvertálás kell, akárcsak az egészeknél.
A maximális és minimális értékek meglepőek lehetnek:
julia> typemax(Float64)
Inf
julia> typemin(Float64)
-Inf
A végtelen érvényes érték!
A lebegőpontos számok hasznos tartománya azonban korlátozott: a túl nagy értékek egyszerűen Inf értéket kapnak, a túl kicsik pedig 0.0-ra kerekítődnek.
Ezeket a határokat egy másik függvénypárral láthatjuk: nagyjából ± 10 ^ 308.
julia> floatmax(Float64)
1.7976931348623157e308
julia> floatmin(Float64)
2.2250738585072014e-308
A pontosság is korlátozott: véges, de nagy számú értékes jegy ábrázolható (a Float64 esetében körülbelül 15).
Ahogy az Alapok fogalomban szó volt róla, az aritmetikai operátorok többnyire ugyanúgy működnek, mint a szabványos aritmetika, ahogyan azt a gyerekeknek tanítják.
Figyeld meg, hogy a hatványozáshoz a ^ jelet használjuk, nem a **-ot (mindkettő gyakori más nyelvekben).
2 + 3 # 5 (addition)
2 - 3 # -1 (subtraction)
2 * 3 # 6 (multiplication)
8 / 2 # 4.0 (division)
8 % 3 # 2 (remainder)
2 ^ 3 # 8 (exponentiation)
Érdemes azonban néhány Julia-specifikus részletről is beszélni.
julia> x = 4.2
4.2
julia> 2 * x
8.4
julia> 2x
8.4
julia> 2.4x
10.08
Ez meglepő lehet.
A * mindig használható infix operátorként, ahogy a legtöbb más programozási nyelvben.
A Júliát azonban olyan emberek tervezték, akik szerint a kódnak a lehető legjobban hasonlítania kell a matematikai egyenletekre.
Mivel a változóneveknek betűvel kell kezdődniük, ha a név elé számot (egészet vagy lebegőpontost) írunk, azt implicit szorzásként kezeli a nyelv.
Például ha egy gömb felszínét szeretnénk kiszámítani, a 4 * pi * r * r helyett ezt is írhatjuk:
julia> surface(r) = 4π * r^2
surface (generic function with 1 method)
julia> surface(3)
113.09733552923255
Bár a π beépített konstans, egyben egy (görög) betű is.
Az elemzőnek ezért továbbra is kell egy explicit *, hogy elválassza a π-t az r-től.
Ha a /-t infix operátorként használjuk, az mindig lebegőpontos eredményt ad, még egész bemeneteknél is.
Egész osztásra több lehetőség is van:
julia> 10 / 3
3.3333333333333335
julia> div(10, 3)
3
julia> 10 ÷ 3
3
julia> 10 // 3
10//3
A div() függvény egész osztásra való, az eredményt nulla felé csonkítja: pozitív számoknál lefelé, negatívoknál felfelé.
Szinonimaként használhatjuk a ÷ infix operátort, ismét azzal a céllal, hogy matematikaibb legyen a kinézet.
Ha Julia-tudatos szerkesztőt használsz, írd be, hogy \div, majd nyomd le a <Tab> billentyűt.
A // operátor külön fogalmat érdemel majd a tanterv későbbi részében.
Egyelőre annyit mondhatunk, hogy a // eredménye egy „racionális” szám, ez a hivatalos neve annak, amit a legtöbben törtnek neveznek.
A közös tényezőket eltávolítja a számlálóból és a nevezőből, így két egész szám arányát kapjuk, amit „legegyszerűbb alaknak” neveznek.
julia> rationalnum = 22 // 6
11//3
julia> typeof(rationalnum)
Rational{Int64}
Megvannak a racionális számok. És mi a helyzet az „irracionális” számokkal?
julia> π
π = 3.1415926535897...
julia> typeof(π)
Irrational{:π}
Az irracionális szám olyan szám, amely nem írható fel két egész szám arányaként.
Gyakori példák a π, az e (Euler-szám), és sok gyök, például a √2 (a 2 négyzetgyöke).
A Julia igyekszik korrekt módon művelni a matematikát.
Ez gyakran automatikusan megtörténik:
julia> x = 2 + 3.5
5.5
julia> typeof(x)
Float64
Egy Int64-hez adtunk egy Float64-et, és Float64 eredményt kaptunk.
Valójában az egész szám csendben Float64-gé konvertálódott, mielőtt az összeadás megtörtént.
A Júliában létezik a numerikus típusok „szélességének” fogalma.
Int64 szélesebb, mint az Int16.Ha egy kifejezésben többféle típus szerepel, mindegyik szükség szerint „előléptetésre” kerül a használt legszélesebb típusra.
A konvertálás kikényszerítéséhez egy egész számot adott típusra konvertálhatunk, például Float64(5).
Vagy egyszerűen használd a float(5)-öt, és hagyd, hogy a fordító válasszon megfelelő típust.
A lebegőpontosból egészbe való átalakítások elkerülhetetlenül bonyolultabbak. Mit szeretnél tenni a tizedespont utáni résszel?
round() függvény a legközelebbi egész számra kerekít; az olyan félúton lévő értékeket, mint a 4.5, a legközelebbi páros egészre kerekíti.floor() lefelé, a ceil() felfelé, a trunc() pedig nulla felé kerekít.Int32()-vel, az InexactError hibával elbukik.Alapértelmezés szerint azonban ezek a függvények nem feltétlenül a várt egész típust adják vissza. A kívánt kimeneti típus megadható.
julia> round(4.5)
4.0
julia> round(Int64, 4.5)
4
julia> round(Int, 4.5) # => default integer type
4
julia> ceil(Int16, 4.3)
5
A tizedespont utáni megadott számú jegyre kerekítés is lehetséges a digits kulcsszóval.
julia> round(π, digits=10)
3.1415926536
További részletekért lásd a kézikönyvet.
Biztosan csak hibát dob? Valójában a helyzet nem ilyen egyszerű.
A ÷ vagy // operátorral végzett egész osztás hibát eredményez, ahogy azt várnád is.
A /-lel végzett lebegőpontos osztás inkább mérnöki megközelítést követ, mintsem szokványos számítástudományit:
julia> 2 / 0
Inf
julia> 0 / 0
NaN
Ahogy korábban szó volt róla, a végtelen érvényes lebegőpontos szám a Júliában, amit az Inf jelöl.
Ha a számláló is nulla, az eredmény matematikailag nem definiált.
A Julia ekkor „nem számként” kezeli, amit a NaN jelöl.
Ha ez furcsának tűnik, gondolj rá úgy, mint a valós világból származó (és ezért gyakran meglehetősen rendetlen) adatokból álló nagy tömbök feldolgozására. A haladás érdekében a legjobb, ha csak megjelöljük a problémás értékeket, és továbblépünk.
A végtelen manuális értékellenőrzés fárasztó lenne programozni, és biztosan rontaná a futásidejű teljesítményt.
Ha minden apró hiba miatt hibaüzenettel állna le, a program nagyon népszerűtlen lenne a felhasználók körében!
Ahogy a Conditionals fogalomban le van írva, az egyenlőségvizsgálatot általában a == operátorral végezzük.
Ez jól működik egészeknél, karaktereknél, stringeknél stb.
A lebegőpontos értékeknek azonban korlátozott a pontosságuk, és ugyanazon eredmény különböző kiszámítási módjai kissé eltérő értékekhez vezethetnek.
A Float64 esetében ez jellemzően a 15. értékes jegy környékén van: kis eltérés, de nem „egyenlőség”.
Hagyományosan azt szokták tanácsolni a programozóknak, hogy soha ne használják a == operátort lebegőpontos értékekkel: az eredmény kiszámíthatatlan.
Széles körben használt alternatíva, hogy a különbség abszolút értékét egy megengedett tűréssel hasonlítjuk össze (amit gyakran epszilonnak vagy ϵ-nak neveznek).
Tehát az a == b helyett használd az abs(a - b) < epsilon kifejezést.
A Julia egy tisztább alternatívát kínál az isapprox() függvénnyel.
Abszolút tűrés esetén a szintaxis isapprox(a, b, atol=epsilon).
Ebben az esetben az atol kulcsszó kötelező.
A relatív tűrés gyakran hasznosabb.
Ez az alapértelmezett, így az isapprox(a, b) megpróbál valamilyen ésszerű értéket választani az rtol számára (a szabályok meglehetősen bonyolultak).
A relatív tűrés is megadható, az összehasonlított értékek egy törtjeként.
Az isapprox(a, b, rtol=0.01 tehát azt vizsgálja, hogy az értékek 1%-on belül vannak-e egymástól.
A szokásos Julia-módra létezik egy matematikai operátor, amely az alapértelmezett eset szinonimája: a ≈ b (az operátort \approx beírásával, majd <tab> lenyomásával lehet megadni).
A racionális számok mellett a tanterv későbbi részei a következőkkel foglalkoznak majd:
2.3 + 4.5im.