Excel precision as displayed afrunder hvert gemt tal til de decimaler, dets talformat viser: den formatsektion, der matcher værdiens fortegn, to ekstra decimaler pr. %, tre færre pr. tusinde-skaleringskomma, afrundet half away from zero. HotXLS anvender samme regel i begge sine Delphi-motorer, når TXLSXWorkbook.FullPrecision eller TXLSWorkbook.UseFullPrecision er False. Det lyder som en one-liner, indtil en kunde rapporterer, at dine eksporterede fakturatotaler afviger fra Excel med en cent, eller at en kolonne af varigheder i [ss].00 kollapsede til nul. Begge ting skete, og begge sporede tilbage til en af reglerne taget forkert. Siden v2.384.57 deler de to motorer én implementering, hvis forventede værdier blev målt i Excel 16 med Workbook.PrecisionAsDisplayed slået til
Hvad ændrer precision as displayed egentlig i en workbook?
Precision as displayed er ét workbook-niveau-flag, der beder beregningsmotoren om at gemme tal, som de ser ud, ikke som de blev beregnet. I Excel UI ligger den under File, Options, Advanced, "When calculating this workbook", som "Set precision as displayed". På disken er det én bit. En BIFF8-fil bærer den i CalcPrecision-recorden ($000E, [MS-XLS] §2.4.35), hvis fFullPrec-felt er 1 for normal fuld præcision og 0, når optionen er slået til. En XLSX-pakke bærer den som attributten fullPrecision på calcPr-elementet i workbook.xml, defineret i ECMA-376 Part 1, hvor default er true, og fullPrecision="0" tænder for afrunding
Flaget er ikke en visningsindstilling. Når du sætter fluebenet, advarer Excel om, at data permanent mister nøjagtighed, og det mener den: værdier omskrives til deres viste præcision, og de cifre, der blev skåret af, er væk. At fjerne fluebenet senere bringer ikke de gamle cifre tilbage. En 0.1234 vist som 12.3% bliver til 0.123 for altid
HotXLS læser og skriver flaget i begge formater og eksponerer det i begge motorer:
TXLSXWorkbook.FullPrecision: Booleanpå XLSX-motoren, indlæst fra og gemt tilcalcPr/@fullPrecisionTXLSWorkbook.UseFullPrecision: Booleanpå Classic-motoren (også påIXLSWorkbook), indlæst fra og gemt til CalcPrecision-recorden- Begge default'er til True, som er den sikre, ikke-destruktive tilstand og Excels default
Hvor HotXLS anvender afrunding, tæller. HotXLS afrunder på det punkt, hvor den beregner en værdi: hvert formelresultat afrundes til sin viste præcision, før det gemmes som cellens cachede værdi, både under Recalculate og ved on-demand-evaluering. Konstanter, du tildeler gennem Value, gemmes præcis som givet. Skal dit output gengive, hvad Excel gemmer efter fluebenet er sat, så afrund selv disse konstanter, før du skriver dem, for eksempel med hjælpefunktionen vist senere
Hvordan beslutter Excel, hvor mange decimaler der skal beholdes?
Excel udleder antallet af beholdte decimaler fra den specifikke formatsektion, der viser værdien, ikke fra formatstrengen som helhed. Reglerne nedenfor blev målt i Excel 16 og er det, XlsApplyDisplayedPrecision i lxNumFormat implementerer for begge HotXLS-motorer
- Vælg sektionen efter fortegn. Et to-sektions-format bruger den anden sektion til negative værdier. Et format med tre eller flere sektioner bruger den anden til negative værdier og den tredje til præcis nul. Alt andet bruger den første sektion
- Tæl decimalpladsholderne. Hver
0,#eller?efter decimalpunktet i den sektion tilføjer én beholdt decimal - Tilføj to pr. procenttegn.
0.0%viser 0.1234 som 12.3%, så den gemte værdi er en hundrededel af, hvad du ser, og beholder tre decimaler, ikke én - Træk tre fra pr. skaleringskomma. Et komma efter den sidste heltalspladsholder (
0,,0.0,,0,.0) deler visningen med 1000.0.0,viser 12345.678 som 12.3, så Excel beholder én decimal minus tre, hvilket er en negativ optælling: værdien afrundes til hundrederne og gemmes som 12300. Et komma mellem heltalspladsholdere, som i#,##0, er ren ciffergruppering og ændrer ingenting - Rør ikke ikke-numeriske sektioner. General-, dato- og tidssektioner (inklusive elapsed
[h],[mm]og[ss]), videnskabelige, brøk- og tekstsektioner samt sektioner uden nogen cifferpladsholder beholder fuld præcision
Målt mod Excel 16 er disse de værdier, begge HotXLS-motorer nu gemmer for et formelresultat i hvert format:
| Talformat | Beregnet værdi | Gemt værdi | Regel, der gælder |
|---|---|---|---|
0.0% | 0.1234 | 0.123 | Én decimal plus to for procenttegnet |
0 | 2.5 | 3 | Half away from zero, ikke til lige |
0 | -2.5 | -3 | Half away from zero på den negative side også |
0.00;(0.0) | -1.2345 | -1.2 | Negativ sektion viser én decimal |
0.00;(0.0) | 1.2345 | 1.23 | Positiv sektion viser to decimaler |
#,##0.0 | 1234.5678 | 1234.6 | Grupperingskomma, ingen skalering |
0.0, | 12345.678 | 12300 | Én decimal minus tre: afrund til hundreder |
0.0%;(0.00%) | -0.0125 | -0.0125 | Negativ sektion beholder to plus to decimaler |
0.00 | 1.005 | 1.01 | Tolerance for binær repræsentationsfejl |
0;-0;0.0 | 0.5 | 1 | Ikke nul, så den positive sektion afgør |
Den sidste række er en fin fælde. Værdien 0.5 afrunder til et helt tal, og nulsektionen kommer aldrig i spil, fordi Excel vælger sektionen ud fra den beregnede værdi før afrunding. Én ærlig begrænsning på HotXLS-siden: sektioner vælges kun efter fortegn, så et format, hvis sektioner bærer tilpassede klammebetingelser som [>=1000], deles stadig efter fortegn. Tjek sådanne formater mod Excel, hvis de betyder noget for dig
Hvorfor afrundes 1.005 til 1.01 og ikke til 1.00?
Excel afrunder 1.005 i en 0.00-celle til 1.01, selv om den double, der ligger nærmest 1.005, ligger en anelse under halvvejspunktet, og HotXLS matcher det med en tolerance på et par ulp. Literalen 1.005 kan ikke repræsenteres i binær flydende punkt. Den nærmeste IEEE 754 double er 1.00499999999999989341858963598497211933135986328125, og gang med 100 giver 100.49999999999999. En lærebogs-Floor(x * 100 + 0.5) / 100 returnerer derfor 1.00, hvilket afviger fra det tal, brugeren tastede, fra det, Excel viser, og fra det, Excel gemmer
Delphi tilføjer sin egen drejning. System.Round afrunder lige til lige, så Round(2.5) er 2 og Round(3.5) er 4. Det er bankers afrunding, en fornuftig default til statistik og den forkerte regel her: Excel gemmer 3 for 2.5 i en 0-celle og -3 for -2.5. HotXLS-implementeringen arbejder på absolutværdien, lægger 0.5 plus en relativ tolerance på 2-51 gange den skalerede værdi til (et par ulp ved den størrelse, aldrig mindre end to ulp af 1.0), trunkerer, skalerer tilbage og gendanner fortegnet. Følgende funktion er en selvstændig illustration af princippet, ikke selve bibliotekskoden, og den håndterer negative cifferantaler for skaleringskommaer på samme måde:
// Principskitse: afrund half away from zero til ADigits decimaler,
// med en tolerance på et par ulp, så 1.005 når 1.01.
// ADigits < 0 afrunder til tiered, hundreder, ... ("0.0," giver -2)
function RoundAsDisplayed(AValue: Double; ADigits: Integer): Double;
const
Tolerance = 4.440892098500626E-16; // 2^-51, to ulp af 1.0
var
I: Integer;
Scale, Scaled, Eps: Double;
begin
Result := AValue;
if (ADigits < -15) or (ADigits > 14) then
Exit; // ud over double-præcision: lad værdien være
Scale := 1;
for I := 1 to Abs(ADigits) do
Scale := Scale * 10;
if ADigits >= 0 then
begin
if Abs(AValue) > 1E300 / Scale then
Exit; // skalering ville overflowe
Scaled := Abs(AValue) * Scale;
end
else
Scaled := Abs(AValue) / Scale;
Eps := Scaled * Tolerance;
if Eps < Tolerance then
Eps := Tolerance;
Scaled := Int(Scaled + 0.5 + Eps); // half away from zero, ikke Round()
if ADigits >= 0 then
Result := Scaled / Scale
else
Result := Scaled * Scale;
if AValue < 0 then
Result := -Result;
end;
// RoundAsDisplayed(1.005, 2) = 1.01 (Floor-baseret: 1.00)
// RoundAsDisplayed(2.5, 0) = 3 (Round: 2)
// RoundAsDisplayed(-2.5, 0) = -3
// RoundAsDisplayed(0.1234, 3) = 0.123 ("0.0%": 1 + 2 cifre)
// RoundAsDisplayed(12345.678, -2) = 12300 ("0.0,": 1 - 3 cifre)
Tolerancen er en bevidst afvejning. En værdi, der reelt ligger to ulp under et halvt trin, afrundes også opad, men på den afstand er forskellen uadskillelig fra repræsentationsfejl, og at behandle den som et halvt trin er det, der får tastede decimaler til at opføre sig, som brugerne forventer
Hvad gik galt før v2.384.57?
Før v2.384.57 havde XLSX-motoren og Classic-motoren hver deres egen precision-as-displayed-kode, og hver tog fejl på hver sin måde. Producerer du workbooks med optionen slået til, er disse symptomerne, du skal lede efter i filer genereret af ældre builds
XLSX-motor: kun første sektion, ingen procent, bankers afrunding
Den gamle XLSX-vej bad om decimalantallet for formatstrengen som helhed, hvilket kun kiggede på den første sektion og ignorerede %, og afrundede derefter med Round. En 0.1234 i 0.0% blev gemt som 0.1, altså 10% i stedet for de 12.3% på skærmen. En 2.5 i 0 blev gemt som 2 i stedet for 3. Negative værdier i et format som 0.00;(0.0) blev afrundet til den positive sektions to decimaler. Siden v2.384.57 kalder XLSX-motoren den samme delte rutine som Classic-motoren, som også fik understøttelse af skaleringskomma i den udgivelse
Classic-motor: TRUE blev til -1
Classic-motoren vogtede sin afrunding med VarIsNumeric, og VarIsNumeric returnerer True for en varBoolean-Variant. Konverteres den Variant med Double(V), kommer der -1 ud, fordi en COM-agtig Boolean True gemmes som -1. En formel som =A1>0 i en celle formatteret 0.00 kom derfor ud af genberegningen som tallet -1. Siden v2.384.57 udelukkes Boolean-resultater før enhver numerisk test, og et logisk resultat forbliver et logisk resultat i begge motorer
Elapsed-time-formater læst som farver (v2.384.9)
Den tredje bug sad i talformat-modellen frem for i afrunding. Parseren klassificerede hver klamret token, der ikke var en betingelse, som en farve, så [h], [mm] og [ss] markerede aldrig deres sektion som dato/tid. Visningen var upåvirket, fordi formatering kører på en separat vej, men precision as displayed er afhængig af det flag for at springe tidsværdier over. En varighed på fem sekunder er 5/86400 af en dag, omkring 0.0000579, og et format som [ss].00 så ud som et almindelig tocifret tal, så med FullPrecision slået fra blev varigheden afrundet til 0.00 dage. Siden v2.384.9 parses en klamret sekvens af ét h-, m- eller s-bogstav som en elapsed-time-token, og sektionen behandles som dato/tid. Samme udgivelse rettede minutdetektion i h:mm, hvor kolon mellem tokens plejede at skjule timen for parseren
Slå precision as displayed til i HotXLS fra Delphi
For at få Excel-ækvivalente gemte værdier, sæt flaget før den genberegning, der skal ære det, og læs derefter de cachede resultater, eller gem. På XLSX-motoren er FullPrecision et almindeligt flag: at ændre det invaliderer ikke resultater, en tidligere Recalculate allerede har gemt, så sæt det lige efter Create eller Open og før den første Recalculate. Eksemplet bruger formler, for det er dér, HotXLS anvender afrunding:
var
Wb: TXLSXWorkbook;
Sh: TXLSXWorksheet;
begin
Wb := TXLSXWorkbook.Create;
try
Sh := Wb.Sheets.Add('Totals');
Sh.Cells[1, 1].Value := 0.1234;
Sh.Cells[2, 1].Value := 2.5;
Sh.Cells[3, 1].Value := 12345.678;
Sh.Cells[1, 2].Formula := '=A1';
Sh.Cells[1, 2].NumberFormat := '0.0%'; // viser 12.3%
Sh.Cells[2, 2].Formula := '=A2';
Sh.Cells[2, 2].NumberFormat := '0'; // viser 3
Sh.Cells[3, 2].Formula := '=A3';
Sh.Cells[3, 2].NumberFormat := '0.0,'; // viser 12.3 (tusinder)
// Skal sættes før den første Recalculate i XLSX-motoren
Wb.FullPrecision := False;
Wb.Recalculate;
// Cachede resultater matcher nu Excel 16: 0.123, 3 og 12300.
// Konstanterne i kolonne A beholder deres fulde præcision.
Assert(Abs(Double(Sh.Cells[1, 2].Value) - 0.123) < 1E-12);
Assert(Double(Sh.Cells[2, 2].Value) = 3);
Assert(Double(Sh.Cells[3, 2].Value) = 12300);
Wb.SaveAs('totals.xlsx'); // skriver <calcPr fullPrecision="0"/>
finally
Wb.Free;
end;
end;
Classic-motoren opfører sig ens, med én bekvemmelighed: at tildele TXLSWorkbook.UseFullPrecision markerer hver formel i afhængighedsgrafen som dirty, så næste Recalculate gen-evaluerer hele workbooken under den nye regel. At ændre en NumberFormat, mens optionen er slået til, markerer også de berørte formulaceller som dirty, fordi formatet nu afgør den gemte værdi. Bemærk, at Classic-Recalculate returnerer antallet af formulaceller, den ikke kunne evaluere, så nul betyder succes:
var
Wb: TXLSWorkbook;
Sh: TXLSWorksheet;
begin
Wb := TXLSWorkbook.Create;
try
Sh := Wb.Sheets.Add;
Sh.Range['A1', 'A1'].Value := -1.2345;
Sh.Range['B1', 'B1'].Formula := '=A1';
Sh.Range['B1', 'B1'].NumberFormat := '0.00;(0.0)';
Sh.Range['C1', 'C1'].Formula := '=A1<0';
Sh.Range['C1', 'C1'].NumberFormat := '0.00';
Wb.UseFullPrecision := False; // markerer hver formel som dirty
if Wb.Recalculate <> 0 then
raise Exception.Create('Some formulas could not be evaluated');
// B1 = -1.2: den negative sektion "(0.0)" viser én decimal
// C1 forbliver Boolean True (builds før v2.384.57 gemte -1)
Wb.SaveAs('report.xls'); // CalcPrecision-record med fFullPrec = 0
finally
Wb.Free;
end;
end;
Begge motorer ærer også flaget, der kommer ind med en fil. Åbn en workbook gemt med optionen slået til, og FullPrecision eller UseFullPrecision er allerede False, så en Recalculate efter indlæsning afrunder præcis, som Excel ville. Behøver du kun at læse de tal, Excel allerede har gemt, kan du springe genberegningen helt over, som beskrevet i læsning af cachede formelværdier uden genberegning. For hvordan serienumre og datoformater interagerer med formatmodellen, der driver dato/tid-tjekket, se Excel-datoserienumre, 1904-systemet og numFmt i Delphi
Hvornår bør du slå precision as displayed til, og hvornår ikke?
Slå precision as displayed til, kun når workbookens gemte tal skal være lig dens viste tal, og du accepterer at miste de ekstra cifre for altid. Det klassiske legitime tilfælde er en finansiel skema, hvor kolonner af afrundede beløb skal lægge sammen til den afrundede total på skærmen, uden skjulte brøkdele af en cent, der producerer en total, der afviger med én på sidste plads. At matche en kundes eksisterende workbook, der allerede har optionen sat, er den anden gode grund, og HotXLS bevarer flaget ved round-trip, så du ikke lydløst skifter dem tilbage til fuld præcision
Undgå det i de fleste andre situationer:
- Ingeniør- og videnskabelige data. At afrunde en måling, fordi nogen valgte et to-decimalers format til en rapport, ødelægger information, som ingen senere formatændring kan genskabe
- Procenter med grove formater. Et
0%-format beholder kun to decimaler af den gemte ratio, så 0.1234 bliver til 0.12, og hver formel nedstrøms, der læser cellen, arbejder med 0.12 - Skalerede visninger. Et
0,eller0.0,-format brugt til at vise tusinder afrunder den gemte værdi til tusinder eller hundreder, hvilket sjældent er, hvad personen, der valgte formatet, mente - Delte skabeloner. Flaget gælder hele workbooken. Alle, der senere tilføjer et ark, arver opførslen, som regel uden at vide, at den er slået til
Er det, du reelt ønsker, afrundede resultater i et par specifikke celler, så skriv ROUND ind i de formler i stedet. ROUND er eksplicit, lokal for cellen, synlig for enhver, der læser formlen, og evalueres af HotXLS formelmotor som enhver anden funktion, uden workbook-bredsideeffekter
Precision as displayed hurtig reference
- Filflag: CalcPrecision
$000EmedfFullPrec= 0 i BIFF8 ([MS-XLS] §2.4.35),calcPr fullPrecision="0"i XLSX (ECMA-376 Part 1) - HotXLS-switches:
TXLSXWorkbook.FullPrecision := FalseogTXLSWorkbook.UseFullPrecision := False, begge default True - Sektion: valgt efter den beregnede værdis fortegn; tredje sektion kun til præcis nul
- Cifre: decimalpladsholdere, plus to pr.
%, minus tre pr. skaleringskomma; optællingen kan være negativ - Afrunding: half away from zero med en tolerance på et par ulp, så 2.5 giver 3, -2.5 giver -3 og 1.005 giver 1.01
- Springes over: General, dato/tid og elapsed time, videnskabelige, brøker, tekst, Boolean og fejlværdier
- Omfang i HotXLS: formelresultater, efterhånden som de beregnes; konstanter gemmes som tildelt
- XLSX-motor: sæt
FullPrecisionfør den førsteRecalculate; Classic-sætteren re-dirty'er selv alle formler - Versioner: matcher Excel 16 i begge motorer siden v2.384.57; elapsed-time-formater beskyttet siden v2.384.9
HotXLS læser, skriver og beregner XLS- og XLSX-workbooks nativt fra Delphi og C++Builder, inklusive de beregningsoptioner for workbooks, der er dækket her. Detaljer, udgaver og trial-download står på siden om HotXLS Delphi spreadsheet-komponenten