Porodica inženjerskih funkcija u programu Excel deluje kao najlakši ugao u referenci funkcija. DEC2BIN pretvara broj u binarni string. HEX2DEC ga vraća nazad. IMSUM sabira dva kompleksna broja. Svaka od njih izgleda kao vežba iz formatiranja. Ali one to nisu. Iza ovih imena se krije 10-bitno kodiranje dvojičnog komplementa sa kojim većina programera nije imala dodira još od časova iz arhitekture računara, zatim format kompleksnih brojeva koji u potpunosti živi unutar stringova, kao i bitovni operatori koji će tiho dovesti do prekoračenja (overflow) 64-bitnog celog broja ukoliko izvršite pomeranje (shift) pre nego što proverite vrednost. Sistem za tabelarne proračune (spreadsheet engine) koji verno reprodukuje Excel ne sme da zaobiđe nijednu od ovih specifičnosti
Ove funkcije se dele u tri grupe, i svaka grupa krije drugačiju zamku. Konverzija baza se vrti oko negativnih brojeva i pragova za svaku bazu pojedinačno. Kompleksna aritmetika se odnosi na parsiranje i formatiranje stringa. Bitovne operacije se tiču ostajanja u granicama tipa Int64. Ovaj članak prolazi kroz svaku grupu onako kako ih implementira HotXLS, uz pozive na radnom listu koje biste i sami zapravo napisali
Konverzija baza i 10-bitni dvojični komplement
Pretvaranje u pravom smeru je deo koji svi očekuju. DEC2BIN(9) daje "1001", a opcioni drugi argument dopunjava rezultat nulama sa leve strane do fiksne širine. Zamka leži u negativnom unosu. Excel tu ne piše znak minus. On tu vrednost kodira kao 10-cifreni string dvojičnog komplementa u ciljnoj bazi, što je i razlog zašto DEC2BIN(-5,10) vraća "1111111011" umesto bilo čega što bi imalo neki znak. Argument za broj mesta se ignoriše kada je vrednost negativna, zato što je kodiranje već čvrsto zakucano na tačno deset cifara
Deset cifara je fiksni budžet, i taj budžet postavlja reprezentativni opseg za svaku bazu. U binarnom sistemu veličina koja prelazi u negativnu polovinu iznosi 512, a modul omotavanja (wrap modulus) je 1024, tako da binarni string dobija znak isključivo i samo kada je dugačak tačno deset karaktera i kada mu je vrednost najmanje 512. Ista ideja se skalira i sa bazom. Oktalni sistem koristi polovinu praga od 2^29 i pun modul od 2^30. Heksadekadni sistem koristi 2^39 i 2^40. Čitač u HotXLS-u primenjuje upravo to isto pravilo: on nakuplja sve cifre, i tek isključivo kada je dati string širok deset karaktera a nakupljena vrednost se nalazi na ili iznad granice te polovine praga, tek tada on oduzima pun modul kako bi na taj način povratio potpisanu vrednost. String od devet karaktera je po tom pravilu uvek nenegativan, bez obzira koliko on bio veliki
Koder je slika u ogledalu. Nenegativna vrednost se konvertuje cifra po cifru i opciono se dopunjava nulama do te neke tražene širine, te se zatim odbacuje ako dovede do prelivanja na bazi njene pozitivne gornje granice, ili ako je tražena širina suviše uska da bi je zadržala. Negativna vrednost se najpre svodi u radni opseg dodavanjem tog njenog punog modula, što je u osnovi i pretvara u vrednost čiji je bazični prikaz uvek deset cifara, a tek nakon toga se cifre emituju sa vodećim nulama kako bi se sasvim i popunila ta širina. Jedinstvena i zajednička provera opsega, tu uz simetrične donje i gornje granice po bazi, jeste upravo ono što održava funkcije DEC2BIN, DEC2OCT i DEC2HEX doslednim jedne spram druge na njihovim rubovima
To nam ostavlja na raspolaganje i među-bazične konverzije, kakve su recimo HEX2BIN i OCT2HEX, a koje menjaju bazu ne prolazeći pri tome kroz dekadni sistem u samom imenu te funkcije. Implementacija za ovo ne nosi zasebnu rutinu za svaki mogući uređeni par baza. Umesto toga ona parsira ulazni string u potpisanu (signed) dekadnu vrednost koristeći tu izvornu bazu, a zatim formatira tu istu dekadnu vrednost u ciljnu bazu. U tom smislu je dekadni sistem tu osovina tj tačka oslonca. Zato je onda jedna rutina za parsiranje i jedna rutina za formatiranje, na taj način komponovana, sasvim dovoljna da bi pokrila svaku pa i ovakvu kombinaciju, i zato što obe te polovine dele istu i konvenciju o 10-cifrenom znaku, negativna vrednost u takvom primeru preživljava taj čitav put sa u potpunosti netaknutim znakom
Kompleksni brojevi su zapravo stringovi, pa je čitav posao u samom parsiranju
Excel nema poseban tip podataka za kompleksne brojeve. U njemu kompleksna vrednost jeste sam taj string "a+bi", tako da onda i svaka od funkcija u IM porodici prihvata takve stringove i zatim naravno istim stringom i uzvraća tj predaje ga u povratku. Funkcija COMPLEX formira i gradi pomenuti string iz njegovog realnog i njegovog imaginarnog dela. IMSUM, IMSUB, IMPRODUCT i IMDIV parsiraju svoje argumente, izvršavaju potom datu aritmetiku na samim numeričkim delovima, te onda rezultat tog rada vraćaju i formatiraju nazad u string. Sam ovaj numerički rad na svemu ovome jeste bukvalno gradivo algebre sa prve godine studija. Prava poteškoća ovde zapravo u potpunosti leži u pouzdanom pretvaranju teksta u ta dva broja u pokretnom zarezu (floating-point numbers), a to je baš upravo ono u čemu taj interni parserski mehanizam obavlja ključni rad i zaslužuje svoje mesto
Dva detalja u tom parseru je lako pogrešno uraditi. Prvi je sama imaginarna jedinica. String "i" znači jedan puta i, a ne nula i ne greška, tako da kada je koeficijent ispred sufiksa prazan ili je u pitanju usamljeni znak plus, parser to mora da pročita kao vrednost 1, a usamljeni minus kao -1. Preskočite to i IMSUM("i","i") prestaje da bude 2i. Drugi problem je naučna notacija koja se sudara sa znakom koji razdvaja realni i imaginarni deo. Parser pronalazi taj razdelnik skeniranjem u potrazi za znakom plus ili minus, ali broj napisan kao "1.5E-3" sadrži minus koji zapravo pripada eksponentu. Skeniranje stoga odbija da tretira plus ili minus kao razdelnik kada je karakter neposredno ispred njega e ili E. Bez te zaštite, realni deo bi bio rascepljen na pola kod znaka eksponenta, a parsiranje bi palo na potpuno ispravnom unosu
Sam sufiks se čuva umesto da se normalizuje. Excel prihvata i i i j, a HotXLS pamti koji od njih je ulaz koristio, tako da formatirani rezultat nosi to isto slovo. Formatiranje zatim primenjuje konvencionalne skraćenice: imaginarni deo sa vrednošću jedan se ispisuje samo kao sufiks, minus jedan kao -i, dok se nulti imaginarni deo svodi na običan realni broj, a nulti realni deo odbacuje onaj vodeći 0+ iz niza
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Engineering');
// Negative input: a ten-bit two's complement, places argument ignored.
Sheet.Cells[1, 1].Value := Sheet.Calculate('=DEC2BIN(-5,10)'); // 1111111011
// Complex multiply on two "a+bi" strings.
Sheet.Cells[2, 1].Value := Sheet.Calculate('=IMPRODUCT("3+4i","1+2i")'); // -5+10i
finally
Book.Free;
end;
end;
Transcendentalne kompleksne funkcije, među kojima su IMSQRT, IMEXP, IMLN i IMPOWER, ne funkcionišu u pravougaonim (Dekartovim) koordinatama. One konvertuju parsiranu vrednost u polarni oblik, primenjuju operaciju na modul i argument, i zatim to konvertuju nazad. Kvadratni koren prepolovljava argument i uzima koren iz modula. Stepenovanje množi argument i stepenuje modul. Bilo koji drugi način rada bi značio ponovno izvođenje svakog identiteta u pravougaonom obliku, što donosi kako više kôda, tako i manju numeričku stabilnost blizu grana presecanja (branch cuts)
Bitovni operatori i prekoračenje (overflow) koje morate prvo da proverite
Excel 2013 je dodao BITAND, BITOR, BITXOR, BITLSHIFT i BITRSHIFT. Operandi su ograničeni: svaki mora da bude nenegativan ceo broj koji nije veći od 2^48 minus 1, a bilo kakav razlomljeni ili negativan argument predstavlja numeričku grešku. Takvo gornje ograničenje je dovoljno velikodušno da pokrije svaki realističan skup zastavica (flag set) i to sve dok ostaje duboko unutar tačno reprezentativnog opsega double tipa, što je zapravo itekako važno zato što Excel prosto svaki numerički argument prenosi redom dalje kao vrednost u pokretnom zarezu (floating-point)
Funkcije za pomeranje (shift) u sebi nose jedno važno pravilo redosleda koje zna opasno da ugrize. Pomeranje ulevo (left shift) može da proizvede vrednost koja je daleko veća od onog njenog ulaza, a ukoliko prvo obavite shl pa zatim nakon toga analizirate dati dobijeni rezultat vi ste već prekoračili Int64 i sam taj test je odjednom besmislen. Provera mora jednostavno da dođe obavezno pre pomeranja. HotXLS tu po izradi poredi dati operand uz i po opcijama sa maksimalnom granicom (ceiling) pomerenom udesno za sam taj iznos pomeranja, a potom i tek onda, ako se operand u sve to uklapa, on usput izvršava stvarno pomeranje ulevo. Magnituda takvog pomeranja preko 53 bita se glatko i momentalno odbacuje, dok negativno pomeranje jednostavno obrne čitav smer obrade, pa se tako BITLSHIFT sa negativnim brojanjem ponaša potpuno kao i redovno ono pomeranje udesno. Sam ovaj princip se generalizuje daleko izvan ove jedne funkcije: kada postoji neka zaštita čiji je cilj da spreči prekoračenje, ona mora da se pokrene na ulazima, i apsolutno nikada nad dobijenim rezultatom kojeg je prvobitno trebalo da štiti
// Bitwise calls evaluate the same way through Calculate.
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
Buduće funkcije i prefiks imena _xlfn
Bitovni operatori, ali ujedno i dug spisak drugih dodataka unesenih posle 2007. godine, ulaze u interakciju sa šemom imenovanja koja apsolutno nema nikakve veze sa onim što računaju, već isključivo s tim kako ih tačno Excel čuva. Prvobitni binarni format radnog lista svakoj je u to vreme ugrađenoj funkciji dodelio numerički slot (prostor) u fiksnoj tabeli. Funkcije izmišljene i stvorene tek nakon što je ta tabela zamrznuta apsolutno nemaju taj i takav slot. Da bi se takva funkcija mogla sačuvati u datoteku i da bi je moderni Excel kao takvu uopšte i prepoznao, njeno ime se upisuje pomoću prefiksa _xlfn. pa se onda BITAND na disku čuva kao _xlfn.BITAND čak i onda kada korisnik sve vreme kuca samo BITAND
Caka je međutim u tome što takvo pravilo nikako nije unificirano (uniformno). Neke novije funkcije su ipak dobile svoje slotove unutar tabele i pišu se sasvim gole, dok se isto tako nekoliko legacy tj starih i skrivenih funkcija takođe piše bez ikakvog prefiksa iako su zapravo prilično stare. HotXLS ovde zato zadržava eksplicitnu belu listu onih imena tj naziva kojima je taj prefiks zaista potreban, zatim on to dodaje prilikom svakog pisanja i skida kada ga čita, pa je tekst formule koji vi podesite i iščitate nazad uvek onaj sasvim čisti naziv ka Excel-u. Vi postavite =BITLSHIFT(5,2), fajl tada unutar sebe sadrži _xlfn.BITLSHIFT, i vrednost na kraju bude vraćena kao 20. Prefiks jeste samo jedan detalj oko skladištenja koji nikada ne bi trebao da "procuri" nazad u same formule sa kojima vi radite iz koda
Sastavljanje svega na radnom listu
Javna površina za sve ovo je prilično mala. Kreirajte jedan TXLSXWorkbook, dodajte radni list, i onda bilo zapišite formulu u ćeliju kroz Cells[Row, Col].Formula te zatim je izračunajte, ili procenite sam takav izraz direktno pomoću Calculate metode radnog lista, koja prevodi formulu prema tom listu te potom vraća Variant. Primeri gore koriste Calculate zato što on pokazuje rezultat samog pojedinačnog inženjerskog poziva bez ostalog okružujućeg stanja oko njega na listu, ali isto tako se funkcije jednako i identično procenjuju onako unutar stvarnih formula ćelija kada sveska vrši preračunavanje
Sama kodiranja jesu onaj deo koji treba imati na umu, a ne ta ista mesta pozivanja. Binarni string jeste označen (potpisan) isključivo kod deset cifara i isključivo nakon što pređe polovinu praga za svoju bazu. Kompleksni broj jeste tekst, prazan imaginarni koeficijent jeste jedan (1), i parser prebacuje preko eksponenta e. Pomeranje ulevo se proverava pre nego što izvrši pomeranje. Ako shvatite i primenite ova četiri fakta ispravno, inženjerska porodica će prestati da bude izvor neprijatnih iznenađenja oko jednog znaka (off-by-a-sign surprises)
Ukoliko povezujete svoju domensku matematiku na isti mehanizam (engine), mehanika registracije rukovaoca (handler) i vraćanje vrednosti su objašnjeni u našem članku o proširenju mehanizma formula sa prilagođenim funkcijama, a kada te formule moraju da dosegnu kroz listove po nazivu a ne po adresama ćelija, vodič o definisanim imenima i formulama kroz listove (cross-sheet) pokazuje kako se reference razrešavaju. Inženjerske funkcije opisane ovde isporučuju se kao deo HotXLS komponente za tabelarne proračune (spreadsheet) za Delphi i C++Builder, pored API-ja za čitanje, pisanje i preračunavanja koji su objašnjeni na drugom mestu na ovom blogu