Tekninen artikkeli

Delphi-insinöörifunktiot: Kanta-muunnos, kompleksimatematiikka

Excelin insinööriperhe (engineering family) lukee kuin helppotajuisin nurkka funktioreferenssiä. DEC2BIN muuttaa luvun binäärimerkkijonoksi (binary string). HEX2DEC muuttaa sen takaisin. IMSUM laskee yhteen kaksi kompleksilukua. Jokainen näyttää siltä kuin ne olisivat muotoiluharjoituksia. Ne eivät ole. Näiden nimien takana istuu kymmenbittinen kahden komplementti -koodaus (ten-bit two's complement encoding), johon useimmat kehittäjät eivät ole koskeneet sitten tietokonearkkitehtuurikurssin; kompleksilukumuoto, joka asuu kokonaan merkkijonojen sisällä; ja bittioperaattorit (bitwise operators), jotka hiljaisesti (silently) ylivuotavat (overflow) 64-bittisen kokonaisluvun (integer), jos siirrät (shift) ennen kuin tarkistat. Taulukkolaskentamoottori (spreadsheet engine), joka toisintaa Excelin tarkalleen, ei voi pyöristää mitään tästä pois

Funktiot jakautuvat kolmeen ryhmään, ja jokainen ryhmä piilottaa eri ansan. Kanta-muunnos (base conversion) kertoo negatiivisista luvuista ja kantakohtaisista kynnyksistä (per-base thresholds). Kompleksiaritmetiikassa (complex arithmetic) on kyse merkkijonon jäsentämisestä (parsing) ja muotoilemisesta (formatting). Bittioperaatiot (bitwise operations) käsittelevät Int64:n rajojen (bounds) sisällä pysymistä. Tämä artikkeli kävelee jokaisen ryhmän läpi kuten HotXLS toteuttaa ne, käyttäen laskentataulukon kutsuja (worksheet calls), joita todella kirjoittaisit

Kanta-muunnos ja kymmenbittinen kahden komplementti

Eteenpäinsuunta on osa, jota kaikki odottavat. DEC2BIN(9) antaa tulokseksi "1001", ja valinnainen toinen argumentti täyttää (left-pads) tuloksen kiinteään leveyteen vasemmalta. Ansa on negatiivinen syöte (input). Excel ei kirjoita miinusmerkkiä. Se koodaa (encodes) arvon kohdekannassa kymmennumeroisena kahden komplementin merkkijonona, mikä on syy, miksi DEC2BIN(-5,10) palauttaa "1111111011" minkään sellaisen sijaan, jossa on etumerkki. Pituutta säätelevä argumentti (places argument) jätetään huomiotta heti, kun arvo on negatiivinen, koska koodaus on jo lukittu kymmeneen numeroon

Kymmenen numeroa on kiinteä budjetti, ja tuo budjetti asettaa esitettävissä olevan alueen (representable range) kantaa kohti. Binäärijärjestelmässä suuruusluokka (magnitude), joka kääntyy negatiiviselle puoliskolle, on 512, ja palautusmoduuli (wrap modulus) on 1024, joten binäärimerkkijono on etumerkillinen (signed) vain silloin, kun se on tarkalleen kymmenen merkkiä pitkä ja sen arvo on vähintään 512. Sama idea skaalautuu kannan mukana. Oktaalijärjestelmä käyttää puolikynnystä (half threshold) 2^29 ja täyttä moduulia (full modulus) 2^30. Heksadesimaalijärjestelmä käyttää arvoja 2^39 ja 2^40. HotXLS-lukija (reader) soveltaa tarkalleen tätä sääntöä: se kerää (accumulates) numerot, ja vasta kun merkkijono on kymmenen merkkiä leveä ja kerrytetty arvo on vähintään puolikynnyksen suuruinen, se vähentää täyden moduulin palauttaakseen etumerkillisen arvon. Yhdeksänmerkkinen merkkijono on aina ei-negatiivinen, ei väliä kuinka suuri

Kooderi (encoder) on peilikuva (mirror image). Ei-negatiivinen arvo muunnetaan numero kerrallaan ja valinnaisesti nollatäytetään (zero-padded) pyydettyyn leveyteen, ja se hylätään (rejected), jos se ylivuotaa (overflows) kannan positiivisen katon, tai jos pyydetty leveys on liian kapea pitääkseen sen sisällään. Negatiivinen arvo tuodaan (brought) ensin alueelle (into range) lisäämällä täysi moduuli, mikä muuttaa sen arvoksi, jonka kantaesitys (base representation) on aina kymmenen numeroa, ja sitten numerot emittoidaan (emitted) johtavien nollien (leading zeros) kanssa leveyden täyttämiseksi. Yksi jaettu alueen tarkistus (shared range check), eli symmetriset ala- ja ylärajat (lower and upper bounds) kantaa kohden, pitää DEC2BIN, DEC2OCT ja DEC2HEX johdonmukaisina toistensa kanssa reunoillaan (edges)

Se jättää ristikantamaiset muunnokset (cross-base conversions), kuten HEX2BIN ja OCT2HEX, jotka muuttavat kantaa ilman, että funktion nimessä ohitetaan (passing through) desimaalia (decimal). Toteutus (implementation) ei kanna erillistä rutiinia (routine) jokaista järjestettyä paria (ordered pair) varten. Se jäsentää (parses) syötemerkkijonon (input string) etumerkilliseksi desimaaliarvoksi käyttämällä lähdekantaa (source base), minkä jälkeen se muotoilee (formats) kyseisen desimaaliarvon kohdekantaan (destination base). Desimaali on napa (pivot). Yksi jäsentämisrutiini (parse routine) ja yksi muotoilurutiini (format routine) yhdistettyinä, kattavat jokaisen kombinaation, ja koska kumpikin puolisko (half) jakaa saman kymmennumeroisen etumerkillisen käytännön (ten-digit signed convention), negatiivinen arvo selviää matkasta etumerkkinsä säilyttäen (with its sign intact)

Kompleksiluvut ovat merkkijonoja, joten työ on jäsentämistä (parsing)

Excelillä ei ole kompleksidatatyyppiä (complex data type). Kompleksiarvo (complex value) on merkkijono (string) "a+bi", ja jokainen IM-perheen (IM family) funktio ottaa noita merkkijonoja sisään ja antaa yhden takaisin. COMPLEX rakentaa merkkijonon reaaliosasta (real part) ja imaginaariosasta (imaginary part). IMSUM, IMSUB, IMPRODUCT ja IMDIV jäsentävät argumenttinsa, tekevät aritmetiikan numeerisille osille, ja muotoilevat (format) tuloksen takaisin merkkijonoksi. Numeerinen työ on perustason algebraa (undergraduate algebra). Vaikeus on yksinomaan tekstin muuttamisessa kahdeksi liukuluvuksi (floating-point numbers) luotettavasti, ja juuri siinä sisäinen jäsentäjä (internal parser) ansaitsee elantonsa

Kaksi yksityiskohtaa (details) kyseisessä jäsentäjässä on helppo saada väärin. Ensimmäinen on pelkkä (bare) imaginaariyksikkö (imaginary unit). Merkkijono "i" tarkoittaa "yksi kertaa i", ei nollaa eikä virhettä (error), joten kun kertoimen (coefficient) edessä oleva jälkiliite (suffix) on tyhjä tai se on yksittäinen (lone) plusmerkki, jäsentäjän (parser) on luettava se arvona 1, ja yksittäinen miinus arvona -1. Ohita tämä ja IMSUM("i","i") lakkaa olemasta 2i. Toinen on tieteellinen merkintätapa (scientific notation), joka törmää etumerkkiin, joka erottaa (separates) reaali- ja imaginaariosat. Jäsentäjä löytää kyseisen erottimen etsimällä plus- tai miinusmerkkiä, mutta muodossa "1.5E-3" kirjoitettu luku sisältää miinuksen, joka kuuluu eksponenttiin. Skannaus siis kieltäytyy (refuses) käsittelemästä plussaa tai miinusta erottimena, kun sitä välittömästi edeltävä merkki on e tai E. Ilman tuota vartijaa (guard) reaaliosa repiytyisi kahtia eksponentin merkin (exponent sign) kohdalla ja jäsennys epäonnistuisi täysin pätevällä (valid) syötteellä (input)

Itse jälkiliite (suffix) säilytetään (preserved) sen sijaan, että se normalisoitaisiin (normalised). Excel hyväksyy sekä kirjaimen i että j, ja HotXLS muistaa, kumpaa syötteessä käytettiin, jotta muotoiltu tulos (formatted result) kantaa samaa kirjainta. Muotoilu (formatting) soveltaa (applies) sitten perinteisiä lyhenteitä (conventional shorthands): imaginaariosa, joka on yksi, tulostuu vain pelkkänä jälkiliitteenä, miinus yksi on -i, nolla-imaginaariosa romahtaa (collapses) pelkäksi reaaliluvuksi (plain real), ja nolla-reaaliosa (zero real part) pudottaa johtavan 0+:n pois

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Engineering');
    // Negatiivinen syöte: kymmenbittisen kahden komplementti, paikka-argumentti (places) sivuutetaan.
    Sheet.Cells[1, 1].Value := Sheet.Calculate('=DEC2BIN(-5,10)'); // 1111111011
    // Kompleksinen kertolasku (multiply) kahdella "a+bi" -merkkijonolla.
    Sheet.Cells[2, 1].Value := Sheet.Calculate('=IMPRODUCT("3+4i","1+2i")'); // -5+10i
  finally
    Book.Free;
  end;
end;

Transkendenttiset kompleksifunktiot (transcendental complex functions), niiden joukossa IMSQRT, IMEXP, IMLN ja IMPOWER, eivät toimi suorakulmaisissa koordinaateissa (rectangular coordinates). Ne muuntavat (convert) jäsennetyn arvon (parsed value) napamuotoon (polar form), soveltavat (apply) operaation moduuliin ja argumenttiin, ja muuntavat sen takaisin. Neliöjuuri (square root) puolittaa argumentin ja ottaa moduulin juuren (root of the modulus). Potenssi (power) kertoo argumentin ja korottaa (raises) moduulin. Sen tekeminen millä tahansa muulla tavalla tarkoittaisi jokaisen identiteetin uudelleenjohtamista (re-deriving) suorakulmaisessa muodossa, mikä merkitsee sekä enemmän koodia että on vähemmän numeerisesti vakaata haarautumisleikkausten (branch cuts) lähellä

Bittioperaattorit (Bitwise operators) ja ylivuoto (overflow), joka sinun täytyy tarkistaa ensin

Excel 2013 lisäsi funktiot BITAND, BITOR, BITXOR, BITLSHIFT ja BITRSHIFT. Operandit (operands) ovat rajoitettuja: kunkin on oltava ei-negatiivinen kokonaisluku (integer), joka ei saa olla suurempi kuin 2^48 miinus 1, ja mikä tahansa murto-osainen (fractional) tai negatiivinen argumentti (argument) on numeerinen virhe (numeric error). Tämä katto (cap) on riittävän antelias kattamaan minkä tahansa realistisen lippujoukon (flag set) pysyen samalla reilusti double-tyypin tarkasti esitettävissä olevan alueen (exactly representable range) sisällä. Tällä on merkitystä, sillä Excel luovuttaa jokaisen numeerisen argumentin (numeric argument) yli liukulukuarvona (floating-point value)

Siirtofunktiot (shift functions) sisältävät sen ainoan järjestyssäännön (ordering rule), joka todella puree (bites). Vasen siirto (left shift) voi tuottaa arvon, joka on paljon syötettä (input) suurempi, ja jos suoritat shl-operaation ensin ja tarkastelet tulosta jälkikäteen, olet jo ylivuotanut (overflowed) Int64-arvon ja testi on merkityksetön (meaningless). Tarkistuksen on tapahduttava ennen siirtoa (shift). HotXLS vertaa operandia kattoon, jota on siirretty oikealle (shifted right) siirtomäärän (shift amount) verran, ja vasta kun operandi mahtuu, se suorittaa varsinaisen vasemman siirron. Yli 53 bitin suuruinen (magnitude) siirto hylätään suoralta kädeltä (outright), ja negatiivinen siirto kääntää yksinkertaisesti suunnan (reverses direction), joten BITLSHIFT negatiivisella lukumäärällä käyttäytyy kuin oikea siirto (right shift). Tämä periaate (principle) yleistyy huomattavasti ohi tämän yhden funktion: kun on olemassa vartija (guard) ylivuotoa vastaan, sen on ajettava syötteillä, ei koskaan tuloksella, jota sen oli tarkoitus suojata

// Bittikohtaiset (bitwise) kutsut evalvoituvat (evaluate) samalla tavalla Calculate:n kautta.
Sheet.Cells[3, 1].Value := Sheet.Calculate('=BITAND(13,11)');    // 9
Sheet.Cells[4, 1].Value := Sheet.Calculate('=BITLSHIFT(5,2)');   // 20
Sheet.Cells[5, 1].Value := Sheet.Calculate('=BITRSHIFT(40,3)');  // 5

Tulevat funktiot ja _xlfn-nimietuliite

Bittioperaattorit (bitwise operators) ja pitkä lista muita vuoden 2007 (post-2007) lisäyksiä ovat vuorovaikutuksessa nimiskeeman (naming scheme) kanssa, jolla ei ole mitään tekemistä sen kanssa, mitä ne laskevat, ja kaikkea tekemistä sen kanssa, miten Excel ne tallentaa (stores them). Alkuperäinen binäärinen laskentataulukkoformaatti (binary worksheet format) osoitti (assigned) jokaiselle sisäänrakennetulle (built-in) funktiolle numeerisen paikan (numeric slot) kiinteässä (fixed) taulukossa. Funktioilla, jotka keksittiin tuon taulukon jäädyttämisen jälkeen, ei ole paikkaa. Tällaisen funktion tallentamiseksi tiedostoon ja jotta moderni Excel tunnistaisi sen, nimi kirjoitetaan _xlfn.-etuliitteellä (prefix), joten BITAND tallennetaan muodossa _xlfn.BITAND levylle, vaikka käyttäjä kirjoittaa ainoastaan BITAND

Koukkuna on se, että sääntö (rule) ei ole yhdenmukainen. Jollekin uudemmille funktioille on annettu paikka (table slots) taulukosta ja ne on kirjoitettu paljaana (bare), kun taas joitakin vanhoja (legacy) piilotettuja (hidden) funktioita kirjoitetaan myös ilman etuliitettä iästään huolimatta. HotXLS ylläpitää (keeps) nimenomaista (explicit) sallittujen luetteloa (whitelist) siitä, mitkä nimet tarvitsevat etuliitteen (prefix), lisää sen kirjoitettaessa (on write) ja poistaa sen lukiessa (on read), joten asettamasi (you set) ja takaisinlukemasi (read back) kaavateksti (formula text) on aina puhdas Exceliin suunnattu (Excel-facing) nimi. Asetat =BITLSHIFT(5,2), tiedosto pitää sisällään _xlfn.BITLSHIFT, ja arvo (value) tulee takaisin 20:nä riippumatta mistään. Etuliite on säilytysyksityiskohta (storage detail), jonka ei tulisi koskaan vuotaa (leak) kaavoihin (formulas), joiden parissa (work with) koodailet

Kokoaminen yhteen laskentataulukkoon (worksheet)

Julkinen pinta (public surface) kaikelle tälle on pieni. Luo TXLSXWorkbook, lisää laskentataulukko (worksheet) ja kirjoita kaava (formula) joko soluun Cells[Row, Col].Formula:n kautta ja laske uudelleen (recalculate), tai evaluoi (evaluate) lauseke suoraan laskentataulukon Calculate-metodilla, joka kääntää (compiles) kaavan kyseistä taulukkoa vasten ja palauttaa Variant-muuttujan. Yllä olevat esimerkit käyttävät Calculate-metodia, koska se näyttää yhden insinöörikutsun (engineering call) tuloksen ilman ympäröivää laskentataulukon tilaa (sheet state), mutta samat funktiot evaluoituvat identtisesti oikeiden solukaavojen (cell formulas) sisällä, kun työkirja (workbook) lasketaan uudelleen

Koodaukset (encodings) ovat se osa, joka on syytä pitää mielessä, eivät kutsupaikat (call sites). Binäärimerkkijono (binary string) on etumerkillinen (signed) vain kymmenen numeron kohdalla ja vasta, kun sen perusta ohittaa puolikynnyksen (half threshold). Kompleksiluku (complex number) on tekstiä, tyhjä (empty) imaginaarikerroin (imaginary coefficient) on yksi, ja jäsentäjä (parser) askeltaa (steps over) eksponentin e-kirjaimen yli. Vasen siirto (left shift) tarkistetaan, ennen kuin se siirtyy (shifts). Hanki (Get) nuo neljä faktaa oikein, ja insinööriperhe (engineering family) lakkaa olemasta merkkivirheen aiheuttamien yllätysten (off-by-a-sign surprises) lähde

Jos kytket (wiring) oman sovellusalueesi matematiikkaa (domain math) samaan moottoriin, käsittelijän (handler) rekisteröinnin (registering) ja arvojen palauttamisen mekaniikka (mechanics) on katettu artikkelissamme, joka koskee kaavamoottorin laajentamista mukautetuilla funktioilla. Ja kun näiden kaavojen (formulas) on kurotettava taulukoiden yli nimellä (by name) eikä soluosoitteella (cell address), määriteltyjen nimien (defined names) ja ristitaulukoiden (cross-sheet) kaavojen läpikäynti näyttää, kuinka viittaukset (references) ratkeavat (resolve). Tässä kuvatut insinöörifunktiot toimitetaan (ship) osana HotXLS-taulukkolaskentakomponenttia Delphille ja C++Builderille, niiden luku-, kirjoitus- ja laskenta-API:en (calculation APIs) rinnalla, joita on käsitelty muualla tässä blogissa