awk finomságok már megint

 ( locsemege | 2017. október 19., csütörtök - 20:05 )

Nem rosszalló a cím, inkább csak arról írok, mibe futottam bele. Két problémába. Nézzük hát őket.

Az int(x) függvény véletlenül sem azt csinálja, amit matematikából tanultunk, hogy egész szám esetén visszaadja x-et, tört esetén pedig a nála kisebb egészet, hanem nulla felé a legközelebbi egészet adja vissza. Így aztán kellett írnom egy függvényt, amelyik x==int(x) esetén visszatér x-szel, x<0 esetén int(x-1)-gyel, minden más esetben int(x)-szel.

A másik probléma érdekesebb. A tömbök asszociatív indexelésűek. Van egy táblázatom mérési eredményekkel 0.05-re kerekítve. Tehát például 2.00, 2.05, 2.10, 2.15, 2.20 és így tovább. Lineáris interpolációt valamint bináris keresést használok, hiszen diszkrét helyeken vannak mérési eredményeim csupán, illetve összetartozó értékpárok. Igen ám, de amikor 0.05-re kerekítek, akkor a fentebbi sor helyett valami ilyesmi fog kijönni: 2, 2.05, 2.1, 2.15, 2.2.

Mivel az asszociatív tömb indexe strig, így a 2.1-es index érték nem lesz azonos a tömbben lévő 2.10-es indexszel, s így az elem nem érhető el. Egész egyszerűen 2.1-es indexű elem nem létezik, az awk 0-t ad vissza értékül. Mi hát a megoldás? Az, hogy mindenképpen két tizedesre állítjuk elő az indexként használt értékeket:

tbl[sprintf("%1.2f", index)]

Egyből jobb lett. :)

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

> Az int(x) függvény véletlenül sem azt csinálja, amit matematikából tanultunk, hogy egész szám esetén visszaadja x-et, tört esetén pedig a nála kisebb egészet, hanem nulla felé a legközelebbi egészet adja vissza.

Dehát egy programnyelvben az a normális, nem? Az integerre való typecastolás az a lebegőpontos számból csak a karakterisztikát és az előjelet adja vissza; az pedig azt jelenti, hogy az előjel állapota irreleváns, a mantissza eltűnésével ténylegesen a tizedesponttól jobbra eső részt veszed le. Ez C/C++-ban is így megy.

Írtam már assembly-ben lebegőpontos aritmetikát, így azt mondom erre, hogy pontatlan a megfogalmazásod. A szám m*2k alakú, ahol 1<=abs(m)<2 a mantissza, k egész a karakterisztika. A nullát speciálisan, külön ábrázoljuk, mivel az abszolútérték első bitjét felesleges ábrázolni, oda az előjelet érdemes írni. Így viszont nem lenne nullánk.

Az int(x) egy viszonylag kellemetlen függvény. Ha elég nagy, akkor a mantissza teljes hosszában egészeket tárol. Ez akkor van, ha a karakterisztika nagyobb, vagy egyenlő, mint a mantissza hossza. Ha elég kicsi, ami azt jelnti, hogy a karakterisztika negatív, akkor az egész rész nulla, legalább is pozitív szám esetén.

A két eset között viszont az van, hogy a karakterisztikától függő maszkkal kell a mantisszát maszkolni, amelynek hatására eltűnik a tört rész. Nem is ez a baj amúgy, hanem az, hogy egy normális int(x) az értelmezési tartományon haladva minden olyan esetben ugrik 1-et, amikor egy újabb egészhez értünk. Az az int(), amit az awk használ, de helyesebb volna talán valami másnak nevezni, olyan, hogy általában egyet ugrik, amikor újabb egészhez érünk, kivéve nullánál, ahol nem ugrik, azaz -1-től 1-ig végig 0 lesz az értéke. Na, ez viszont szívás, ha nem számítasz rá.

Amúgy definíciós kérdés, nem nagy probléma, ha tud róla az ember, olvas doksit. A baj ott volt, hogy int() alatt én másra számítottam.


tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

"Ugrik"? Sajnos nem értem mire gondolsz. Kipróbáltam -1 és 1 közötti törtekre mit ad vissza az int() és azt adta vissza, ami a tizedesponttól balra volt, az előjeltől függetlenül. A doksit én is csak most olvastam el, de a többi nyelv után egy float to int castnál én pont arra számítottam, amit leírt a doksi. Nincs kizárva, hogy nálam hiányzik valami infó, ami miatt nem értem a problémát...de én nem látom, mi a hiba.

Ábrázold, kérlek az y=int(x) függvényt mondjuk az x=[-5; 5] intervallumon, s meg fogod érteni, mi a bajom. Minden egésznél szakadása van a függvénynek, de ez a nulla felé kerekítős olyan, hogy nullánál nincs szakadása, kimarad egy lépcső. Ez viszont szívás, ha kerekítésre használod a függvényt.


tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Tehát az a baj, hogy a nullán bármely irányban túllépve az eredmény nem változik, mint teszi azt a többi egésznél akár negatív akár pozitív irányba? Ez azért van, mert -0 = +0, nem?

Kerekítésre az nem jó, hogy int(x + (x < 0 ? -0.5 : 0.5)) ?

De, jó, csak azzal az int() függvénnyel, amelyet én szeretek, s minden törtnél a nála kisebb egészet adja vissza, tehát int(3.2)==3 és int(-3.2)==-4, ennyi a kerekítés: int(x+0.5).


tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

De elmondásod szerint az awk-ban nem az van, viszont ami megoldást írtál

> Így aztán kellett írnom egy függvényt, amelyik x==int(x) esetén visszatér x-szel, x<0 esetén int(x-1)-gyel, minden más esetben int(x)-szel.

abban külön függvényt kell használnod és 3 eset van benne 2 helyett, míg így nem kell egy "wrapper" függvény, csak beledobsz egy ternaryt az int() belsejébe.

Jobb lenne nem belemenni a lebegőpontos számokba... Például amit te 2.1-nek látsz, az belül egy végtelen tizedes kettedes tört. Pontosabban mondva, véges biten tárolva nem lehet pontosan ábrázolni a 2.1-et, de több különböző lebegőpontos érték van, amit 2.1-ként ír ki a gép.
A megkerülő megoldás az lehet, ha n=2.10 helyett n="2.10"-et (string) használsz. Vagy 210-et (integer).

Ebben tökéletesen igazad van. Az awk gyengén típusos nyelv, az automatikus konverziók csodákra képesek néha. Lebegőpontosan számol, de a kiírásra szánt stringet használja szerintem. Valóban tehetném azt, hogy indexnek az érték 100-szorosát használom, így egycsapásra megszűnnek a problémáim. Meg persze úgy is, ha formázott kiíratást használok az indexnél.


tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

> Vagy 210-et (integer).

Vagy, mivel (ha jól értem) az indexek lépésköze konstans 0.05, így akár vehet egy sima integer indexelt tömböt is és a valós indexet egy egyszerű 20.0-szal való osztással visszakapja iterálás alatt.

Vagy ez rossz ötlet?

Ebben a konkrét esetben lehet ezt is, de akkor vagy kell egy függvénykapcsolat - igen, a 20-szal osztásra gondolok -, vagy egy másik tömb, amelyik összerendeli az indexet ezekkel az értékekkel is.


tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Nem akarok beleszólni, csak kíváncsiság: Miért pont awk és nem másik script nyelv?

Nyugodtan beleszólhatsz. :) Azért, mert a bash-t, (ap)calc-ot és az awk-t ismerem. Nem gúnyolódni, nyilván nem minden zeg-zugát az utolsó apró jelölésig, nyelvi elemig, de annyira igen, hogy meg tudom velük oldani a feladataimat. A munkahelyi gépemen Windows 10 van, viszont használom a Windows Subsistem for Linux nevezetű vékony réteg virtualizációt. Tettem a gépre X szervert, így mennek a linuxos grafikus alkalmazások is, de például a Geany-t natív windows-os verzióban tettem fel. :) Vastag réteg virtualizációt csak akkor használnék, ha nem lenne a WSL, de van, s szerintem ez elég az ilyen jellegű mérnöki feladatokhoz. Pontosabban segédfeladatokhoz, amikor valmihez numerikus módszert hívok segítségül, méretezek valamit, vagy táblázatosan van meg egy függvény, így aztán némileg problematikus az analitikus megközelítés.

A bash kiesik, mert nem tud lebegőpontos aritmetikát. Az (ap)calc lassú, viszont tud komplex aritmetikát, ami sokszor nagyon kell. Az awk gyors, de nem tud komplex számokat kezelni, noha nyilván függvényekkel meg lehet erőszakolni, hogy számpárosokat hordozzon.

Van még az octave, de borzalmas a szintaktikája, utálom. Na jó, amikor Gauss-eliminációt egy nyúlfarknyi kifejezéssel megold, mint komplex együtthatós mátrixegyenletet, azt azért tudom szeretni. :)

Perl, Python nem az én világom, a PHP sem, bár abban írtam ezt-azt korábban, viszont nem szeretem.


tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Azt hittem valami beágyazott rendszeren futtatod. Akkor ok, nyilván nagy a paletta, lehet választani ki mit ismer.

Az awk-ban azt szeretem, hogy

- igen gyors
- ismeri a lebegőpontos aritmetikát és az alap függvényeket
- ismeri a regexp-eket
- soronként végigmegy az input file-on, de bármi írható a BEGIN és END szekcióba is, ha ez valamiért nem kell
- C-like szintaxis, úgy nagyjából

Hamar eredményre lehet vele jutni.


tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Ha mar ugyis irtal ezt-azt PHP-ban, FYI:

http://php.net/manual/en/function.ceil.php
echo ceil(4.3); // 5
echo ceil(9.999); // 10
echo ceil(-3.14); // -3

http://php.net/manual/en/function.floor.php
echo floor(4.3); // 4
echo floor(9.999); // 9
echo floor(-3.14); // -4

Es ha mar itt tartunk:
http://php.net/manual/en/function.round.php
float round ( float $val [, int $precision = 0 [, int $mode = PHP_ROUND_HALF_UP ]] )

PHP_ROUND_HALF_UP Round val up to precision decimal places away from zero, when it is half way there. Making 1.5 into 2 and -1.5 into -2.
PHP_ROUND_HALF_DOWN Round val down to precision decimal places towards zero, when it is half way there. Making 1.5 into 1 and -1.5 into -1.
PHP_ROUND_HALF_EVEN Round val to precision decimal places towards the next even value.
PHP_ROUND_HALF_ODD Round val to precision decimal places towards the next odd value.

--
Worrying about killer AI and the superintelligent robots is like worrying about overcrowding on Mars. - Garry Kasparov

Tudom, a calc-ban is paraméterezhető emlékeim szerint a round() működése. De ettől még nem fogok beleszeretni a PHP-be.


tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Nem bántani akarlak, csak tisztázni az alapokat.

„Az int(x) függvény véletlenül sem azt csinálja, amit matematikából tanultunk”

Nem tudom mikor tanultál matematikát, de én bőven a rendszerváltás előtt jártam iskolába, és akkor ilyeneket biztosan nem tanítottak matematikából. Az életkorodat nem ismerem, de a korábbi írásaid alapján arra tippelnék, hogy korban közelebb állsz hozzám, mint egy 30 éveshez.

Tanultunk olyat, hogy kerekítés, de az egy másik dolog. Az egyes programozási nyelvekben általában több függvény is van (floor, ceil, round) a kerekítésre. Ahol van int függvény, ott általában csonkolás történik, azaz a törtrész eldobása. Pechedre az awk-ban csak int van, de – szerintem – az pont úgy működik, ahogy kell. Ami nem nagy baj, mert van megoldás, és azt meg is találtad (= saját függvény).

A '70-es, '80-as években jártam általános iskolába. :) Nekem úgy tanították az int(x)-et, hogy amennyiben x egész, úgy önmaga lesz az eredmény, amennyiben x tört, úgy a nála kisebb legközelebbi egész az eredmény. A csonkolással az a bajom, hogy nullánál kimarad egy lépcső belőle, de ahogy írod is, van megoldás.


tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Jól értem?
int(-0.1)-re te -1-et szeretnél kapni eredményként?

Igen.


tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

„amennyiben x tört, úgy a nála kisebb legközelebbi egész az eredmény.”

Pont erre van a floor(). Már amelyik nyelvben/hez létezik.

+1
Python doksijában szoktam megnézni a programozási nyelvek matematikáját, ha valamiben nem vagyok biztos.

$ pydoc int
"If x is floating point, the conversion truncates towards zero."
Ez egyébként megegyezik a math.trunc(x) függvénnyel.

$ pydoc math.floor
"This is the largest integral value <= x."

és ha már floor, akkor nézzük a ceil-t is, ami szintén kell néha:
$ pydoc math.ceil
"This is the smallest integral value >= x."

A C nyelv is a fentiek szerint viselkedik.

Az int() nem azonos az (alsó) egészrész függvénnyel, annak entier a neve: http://m.wolframalpha.com/input/?i=entier%28x%29

Üdv,
Marci