Sz

Számok ebben a kurzusban: Julia

7 feladat

A(z) Számok fogalomról

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.

Egész számok

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

Egész túlcsordulás és a 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.

Lebegőpontos számok

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).

Aritmetikai operátorok

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.

Szorzás

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.

Osztás

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.

Numerikus típusok konvertálása

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.

  • Az egészeken belül és a lebegőpontosokon belül ez egyszerűen a tároláshoz szükséges bitek száma. Így az Int64 szélesebb, mint az Int16.
  • A lebegőpontosok szélesebbnek számítanak az egészeknél, mert törtrészt is tudnak tárolni.

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?

  • A 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.
  • A floor() lefelé, a ceil() felfelé, a trunc() pedig nulla felé kerekít.
  • Ha közvetlenül próbálunk konvertálni, például 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.

Osztás nullával

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!

Lebegőpontos értékek összehasonlítása

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).

Kapcsolódó jövőbeli fogalmak

A racionális számok mellett a tanterv későbbi részei a következőkkel foglalkoznak majd:

Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg

Tanuld meg a(z) Számok fogalmat

A gyakorlás zárolva

Oldj fel még 2 feladatot, hogy gyakorolhasd a(z) Számok fogalmat