Ingeniørfamilien i Excel leses som det enkleste hjørnet av funksjonsreferansen. DEC2BIN gjør et tall til en binærstreng. HEX2DEC gjør det tilbake. IMSUM legger til to komplekse tall. Hver av dem ser ut som en formateringsøvelse. De er ikke det. Bak disse navnene sitter en ti-bits to-komplement-koding (ten-bit two's complement encoding) de fleste utviklere ikke har rørt siden en datamaskinarkitektur-klasse, et komplekst tallformat som utelukkende lever inne i strenger (strings), og bitvise operatorer (bitwise operators) som lydløst vil overflyte (overflow) et 64-bits heltall (integer) hvis du forskyver (shift) før du sjekker. En regnearkmotor (spreadsheet engine) som reproduserer Excel eksakt, kan ikke runde av noe av det
Funksjonene deler seg i tre grupper, og hver gruppe skjuler en annen felle (trap). Basiskonvertering handler om negative tall og terskler (thresholds) per base. Kompleks aritmetikk handler om å tolke (parsing) og formatere en streng. Bitvise operasjoner handler om å holde seg innenfor grensene til Int64. Denne artikkelen går gjennom hver gruppe slik HotXLS implementerer dem, med arbeidsark-kallene (worksheet calls) du faktisk ville skrevet
Basiskonvertering og ti-bits to-komplementet
Forover-retningen (forward direction) er den delen alle forventer. DEC2BIN(9) gir "1001", og et valgfritt andre argument venstre-polstrer (left-pads) resultatet til en fast bredde. Fellen er negative inndata. Excel skriver ikke et minustegn. Det koder (encodes) verdien som en tisifret (ten-digit) to-komplementstreng i målbasen (target base), som er grunnen til at DEC2BIN(-5,10) returnerer "1111111011" i stedet for noe med et fortegn (sign). Plasser-argumentet (places argument) ignoreres så snart verdien er negativ, fordi kodingen allerede er festet ved ti sifre
Ti sifre er et fast budsjett, og det budsjettet setter det representerbare området (representable range) per base. I binær er størrelsen som vipper inn i den negative halvdelen 512, og innpakningsmodulen (wrap modulus) er 1024, så en binær streng (binary string) har fortegn bare når den er nøyaktig ti tegn lang og verdien er minst 512. Den samme ideen skalerer (scales) med basen. Oktal (octal) bruker en halv terskel på 2^29 og en full modul på 2^30. Heksadesimal (hexadecimal) bruker 2^39 og 2^40. HotXLS-leseren bruker nøyaktig denne regelen: den akkumulerer sifrene, og bare når strengen er ti tegn bred og den akkumulerte verdien sitter på eller over den halve terskelen (half threshold), trekker den fra den fulle modulen for å gjenopprette den signerte verdien (signed value). En nitegns-streng (nine-character string) er alltid ikke-negativ, uansett hvor stor den er
Koderen (encoder) er speilbildet. En ikke-negativ verdi (non-negative value) konverteres siffer for siffer og eventuelt nullpolstres (zero-padded) til den forespurte bredden, og den avvises (rejected) hvis den overflyter basens positive tak (positive ceiling) eller hvis den forespurte bredden er for smal (too narrow) til å holde den. En negativ verdi (negative value) bringes først i område ved å legge til den fulle modulen (full modulus), noe som gjør den om til en verdi hvis base-representasjon alltid er ti sifre, og deretter sendes sifrene ut med ledende nuller (leading zeros) for å fylle bredden. Den ene delte områdesjekken (shared range check), de symmetriske nedre og øvre grensene per base, er det som holder DEC2BIN, DEC2OCT og DEC2HEX konsekvente med hverandre ved kantene (edges)
Det etterlater konverteringene på tvers av baser, de som for eksempel HEX2BIN og OCT2HEX som bytter base uten å passere gjennom desimal i funksjonsnavnet. Implementeringen bærer ikke en egen rutine for hvert ordnede par. Den tolker inndatastrengen (input string) til en signert desimalverdi ved å bruke kildebasen (source base), og formaterer (formats) deretter denne desimalverdien inn i destinasjonsbasen (destination base). Desimal er omdreiningspunktet (pivot). Én tolke-rutine (parse routine) og én formaterings-rutine, sammensatt (composed), dekker hver kombinasjon, og fordi begge halvdelene deler den samme ti-sifrede signerte konvensjonen (ten-digit signed convention), overlever en negativ verdi turen med fortegnet sitt intakt (sign intact)
Komplekse tall er strenger, så jobben er tolking (parsing)
Excel har ingen kompleks datatype. En kompleks verdi (complex value) er strengen "a+bi", og hver funksjon i IM-familien tar de strengene inn og leverer en tilbake. COMPLEX bygger strengen fra en reell (real) og en imaginær (imaginary) del. IMSUM, IMSUB, IMPRODUCT og IMDIV tolker argumentene sine, gjør aritmetikken på de numeriske delene, og formaterer resultatet tilbake til en streng. Det numeriske arbeidet er algebra på bachelornivå (undergraduate). Vanskeligheten ligger utelukkende i å gjøre teksten om til to flyttall (floating-point numbers) pålitelig, og det er der den interne tolkeren (internal parser) tjener til livets opphold
To detaljer i den tolkeren er lett å få feil. Den første er den bare imaginære enheten. Strengen "i" betyr én gang i, ikke null og ikke en feil, så når koeffisienten (coefficient) foran suffikset er tom (empty) eller er et enslig plusstegn (lone plus sign), må tolkeren (parser) lese den som verdien 1, og en enslig minus som -1. Hopper du over det, slutter IMSUM("i","i") å være 2i. Den andre er vitenskapelig notasjon (scientific notation) som kolliderer (colliding) med tegnet som skiller den reelle og den imaginære delen. Tolkeren finner den separatoren ved å søke (scanning) etter et pluss eller minus, men et tall skrevet som "1.5E-3" inneholder et minus som tilhører eksponenten. Søket nekter (refuses) derfor å behandle et pluss eller minus som separatoren (separator) når tegnet rett før det er e eller E. Uten denne beskyttelsen (guard) ville den reelle delen blitt revet i to ved eksponenttegnet, og tolkningen ville feilet på helt gyldig (perfectly valid) inndata
Selve suffikset (suffix) er bevart (preserved) snarere enn normalisert (normalised). Excel godtar (accepts) både i og j, og HotXLS husker hvilken inndataen (input) brukte, slik at det formaterte resultatet (formatted result) bærer samme bokstav. Formatering bruker deretter de konvensjonelle forkortelsene (conventional shorthands): en imaginær del på én skrives ut som bare suffikset, minus én som -i, en imaginær del på null kollapser (collapses) til en ren reell, og en reell del på null slipper den ledende (drops the leading) 0+
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Engineering');
// Negative inndata: et ti-bits to-komplement, plasser-argument ignoreres.
Sheet.Cells[1, 1].Value := Sheet.Calculate('=DEC2BIN(-5,10)'); // 1111111011
// Kompleks multiplikasjon på to "a+bi" strenger.
Sheet.Cells[2, 1].Value := Sheet.Calculate('=IMPRODUCT("3+4i","1+2i")'); // -5+10i
finally
Book.Free;
end;
end;
De transcendentale (transcendental) komplekse funksjonene, blant dem IMSQRT, IMEXP, IMLN og IMPOWER, opererer (work) ikke i rektangulære koordinater (rectangular coordinates). De konverterer (convert) den tolkede verdien (parsed value) til polar form, utfører (apply) operasjonen på modulen (modulus) og argumentet (argument), og konverterer tilbake. En kvadratrot (square root) halverer argumentet og tar roten (root) av modulen. En potens (power) multipliserer (multiplies) argumentet og hever modulen. Å gjøre det på noen annen måte ville bety å utlede (re-deriving) hver identitet i rektangulær form, noe som er både mer kode og mindre numerisk stabil (numerically stable) nær forgreningskuttene (branch cuts)
Bitvise operatorer og overflyten du må sjekke først
Excel 2013 la til BITAND, BITOR, BITXOR, BITLSHIFT og BITRSHIFT. Operandene (operands) er begrenset (constrained): hver enkelt må være et ikke-negativt (non-negative) heltall (integer) ikke større enn 2^48 minus 1, og ethvert brøk- (fractional) eller negativt argument er en numerisk feil (numeric error). Det taket (cap) er sjenerøst nok til å dekke et hvilket som helst realistisk flagg-sett mens man holder seg godt innenfor den nøyaktig representerbare (exactly representable) rekkevidden til en double, noe som har betydning fordi Excel leverer hvert numerisk argument over som en flyttallsverdi (floating-point value)
Forskyvningsfunksjonene (shift functions) bærer den ene ordningsregelen (ordering rule) som genuint (genuinely) biter. En venstreforskyvning (left shift) kan produsere en verdi langt større enn dens inndata, og hvis du utfører shl først og inspiserer (inspect) resultatet etterpå, har du allerede overflytet (overflowed) Int64 og testen er meningsløs. Sjekken (check) må komme før forskyvningen. HotXLS sammenligner operanden (operand) med taket forskjøvet til høyre (shifted right) med forskyvningsbeløpet, og bare hvis operanden passer, utfører den den faktiske venstreforskyvningen. En forskyvningsstørrelse utover 53 biter (bits) avvises direkte (rejected outright), og en negativ forskyvning reverserer rett og slett retningen, så BITLSHIFT med et negativt tall (count) oppfører seg som en høyreforskyvning (right shift). Prinsippet generaliserer (generalises) langt forbi denne ene funksjonen: når det eksisterer en beskyttelse (guard) for å forhindre overflyt, må den kjøres (run) på inndataene (inputs), aldri på resultatet den var ment å beskytte
// Bitvise kall evalueres på samme måte gjennom 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
Fremtidige funksjoner og navneprefikset _xlfn
De bitvise operatorene og en lang liste over andre post-2007-tillegg (additions) samhandler (interact) med et navneskjema (naming scheme) som ikke har noe å gjøre med hva de beregner (compute), og alt å gjøre med hvordan Excel lagrer dem. Det originale binære arbeidsarkformatet (worksheet format) tildelte hver innebygd funksjon (built-in function) et numerisk spor (numeric slot) i en fast tabell. Funksjoner oppfunnet etter at den tabellen ble frosset (frozen) har ikke noe spor. For å lagre (save) en slik funksjon i en fil og la en moderne Excel gjenkjenne den, skrives navnet med et _xlfn.-prefiks, så BITAND lagres som _xlfn.BITAND på disk selv om brukeren bare noen gang skriver BITAND
Haken (catch) er at regelen ikke er ensartet (uniform). Noen nyere funksjoner ble gitt tabellspor (table slots) og skrives bare (bare), mens noen få eldre skjulte (legacy hidden) funksjoner også skrives uten prefiks til tross for deres alder. HotXLS opprettholder (keeps) en eksplisitt hviteliste (whitelist) over hvilke navn som trenger prefikset, legger det til ved skriving og fjerner det ved lesing, så formelteksten (formula text) du setter og leser tilbake, er alltid det rene (clean) Excel-vendte (Excel-facing) navnet. Du setter =BITLSHIFT(5,2), filen (file) holder _xlfn.BITLSHIFT, og verdien kommer tilbake som 20 uansett (regardless). Prefikset (prefix) er en lagringsdetalj (storage detail) som aldri skal lekke ut i formlene du arbeider med i kode
Sett det sammen i et arbeidsark
Den offentlige overflaten (public surface) for alt dette er liten. Opprett en TXLSXWorkbook, legg til et arbeidsark (worksheet), og skriv enten (either write) en formel (formula) i en celle gjennom Cells[Row, Col].Formula og rekalkuler (recalculate), eller evaluer (evaluate) et uttrykk (expression) direkte med arbeidsarkets Calculate-metode, som kompilerer (compiles) formelen mot det arket og returnerer en Variant. Eksemplene ovenfor bruker Calculate fordi den viser resultatet av ett enkelt (single) ingeniørkall uten den omliggende arktilstanden (sheet state), men de samme funksjonene evalueres (evaluate) identisk inne i ekte (real) celleformler når arbeidsboken rekalkulerer
Kodingene (encodings) er delen å ha i bakhodet, ikke anropsstedene (call sites). En binærstreng er signert bare ved ti sifre og bare forbi den halve terskelen for sin base. Et komplekst tall er tekst, en tom imaginær koeffisient (imaginary coefficient) er én, og tolkeren (parser) trer over e i en eksponent. En venstreforskyvning (left shift) sjekkes (checked) før den forskyves (shifts). Får du disse fire fakta riktig, og ingeniørfamilien slutter å være en kilde til overraskelser av typen (off-by-a-sign surprises)
Hvis du kobler (wiring) din egen domene-matematikk inn i den samme motoren, dekkes (covered) mekanikken (mechanics) for å registrere en håndterer (handler) og returnere verdier i vår artikkel om å utvide formelmotoren (extending the formula engine) med tilpassede funksjoner (custom functions), og når de formlene (formulas) må nå på tvers (reach across) ark (sheets) ved navn snarere enn ved celleadresse (cell address), viser gjennomgangen av definerte navn (defined names) og kryssarkformler (cross-sheet formulas) hvordan referansene (references) løses (resolve). Ingeniørfunksjonene beskrevet her leveres (ship) som en del av HotXLS regnearkkomponenten (spreadsheet component) for Delphi og C++Builder, ved siden av lesings-, formel- og formaterings-API-ene dekket andre steder på denne bloggen