3.14159'u # ??/?? kesir maskesiyle biçimlendirin; Excel, 3 14/99 yedi kattan fazla daha yakınken 3 1/7 gösterir. En yakın kesri arayan bir Delphi dışa aktarımı böylece kullanıcının hiç görmediği bir değer basar. Sebep şu: Excel yalnızca basamaklı kesir yakınsaklarını kabul eder. HotXLS'te TXLSXCell.FormattedText, dolgu boşlukları dahil, aynı 3 1/7'yi döndürür
Excel basamaklı kesir yakınsaklarında durur
Yaklaşık kesir üreten her maske Excel'e aynı işi verir: tamsayıdan sonraki kısım için, eğik çizgiden sonraki basamak yuvası sayısıyla sınırlı bir payda ile bir pay ve payda bulmak. Excel bunu, değeri basamaklı kesir olarak açıp yalnızca yakınsakları tutarak yapar; yakınsak, o açılımı her terden sonra keserek elde ettiğiniz kesirdir. 0.14159 için açılım [0; 7, 15, 1, ...] ile başlar; bu, önce 1/7'yi, sonra 15/106'yı, sonra 16/113'ü verir. 3 tamsayısı hiçbir şeyi değiştirmez. Onunla 3.14159, her pi yaklaşımı tablosunda bulunan aynı 22/7, 333/106 ve 355/113'ten geçer. 106, iki basamaklı bir paydayı çoktan taşırdığından Excel 1/7'de kalır. Daha yakın olan 14/99 ise bir yarı yakınsaktır (semiconvergent): iki komşu yakınsak arasında oturan ara kesirlerden biri. Excel oralara hiç uğramaz. Bununla karşılaşmak için özel bir koda da ihtiyaç yoktur: Hücreleri Biçimlendir iletişim kutusundaki Up to one digit ve Up to two digits türleri, ECMA-376 Part 1 §18.8.30'un öntanımlı sayı biçimi listesindeki 12 ve 13 kimlikli gömülü biçimlerdir; yani # ?/? ile # ??/??
Bu fark pi benzeri girdilerle sınırlı değildir. En iyi yaklaşık kesir araması (best approximation) — tipik olarak bir Stern-Brocot yürüyüşü ya da izin verilen her payda üzerinde bir tarama — hangi kesrin en yakın olduğunu sorar; yakınsak döngüsü ise, payda hâlâ sığarken açılımın hangi kesre ilk ulaştığını sorar. Sıradan iki ondalıklı girdilerde iki kural, fark edilecek kadar sık ayrışır ve aşağıdaki koddaki 0.27 tam da böyle bir değerdir. Kodunuz kesirleri, birisinin çalışma kitabıyla karşılaştıracağı bir PDF faturasına, bir HTML raporuna ya da bir CSV'ye yazıyorsa, Excel'in kuralını oraya kopyalayın; daha az isabetli olan kural olduğunu bilerek de olsa. Kâğıtta 3 14/99, Excel'de 3 1/7 gören bir okuyucu, dışa aktarımınızın daha hassas olduğu sonucuna varmaz; ikisinden birinin bozuk olduğu sonucuna varır. Bozuk olan Excel olmayacaktır
Döngünün kendisi kısadır. Eğik çizgiden sonraki basamak yer tutucularını sayın — ister ? ister # ister 0 — bir, iki ya da üç adet için sınır 9, 99 ya da 999 olur. # ??/?? gibi karma bir maske tamsayıyı ayırır ve yalnızca kesirli kısmı açar; ?/? gibi uygunsuz bir maske bütün değeri açar ve 3.14159'un ?/? altında 22/7 çıkmasının nedeni budur. Her adım, eldeki B değerinin tamsayı kısmı A'yı alır ve bir sonraki yakınsağa katlar. Kalan B − A, 5e-8'in altına indiğinde döngü durur; böylece kayan nokta gürültüsü taşımış basit bir kesir yine o kesre düşer. Bir adım paydayı sınırın ötesine ittiğinde Excel önceki yakınsağa geri düşer ve bu düşüş 1/1'e konabilir; karma bir maske bunu tamsayıya taşır, dolayısıyla # ?/? altındaki 12.9999, 13 gösterir. Aşağıda döngü, kaba kuvvet bir en yakın kesir aramasının yanında sade Delphi olarak yazılıdır; böylece iki kuralın kendi sayılarınızda nerede ayrıştığını görebilirsiniz
uses
SysUtils;
type
TFraction = record
Num, Den: Int64;
end;
// Excelin # ?/?, # ??/?? ve # ???/??? maskelerinin ardındaki yakınsak kuralı.
// X negatif değildir: karma maske için kesirli kısım, uygunsuz maske için
// bütün değer. MaxDen 9, 99 ya da 999'dur.
function ExcelFraction(X: Double; MaxDen: Integer): TFraction;
var
B, R: Double;
A, P, Q, P1, Q1, P2, Q2: Int64;
begin
B := X;
P2 := 0; Q2 := 1; // yakınsak k-2
P1 := 1; Q1 := 0; // yakınsak k-1
P := 0; Q := 1;
while Q1 < MaxDen do
begin
A := Trunc(B);
P := A * P1 + P2;
Q := A * Q1 + Q2;
R := B - A;
if R < 5E-8 then
Break; // kalan sıfır sayılır
B := 1 / R;
P2 := P1; Q2 := Q1;
P1 := P; Q1 := Q;
end;
if Q > MaxDen then // sınırı aştı: bir yakınsak geri
if Q1 > MaxDen then
begin
P := P2; Q := Q2;
end
else
begin
P := P1; Q := Q1;
end;
Result.Num := P;
Result.Den := Q;
end;
// Bir "best approximation" yordamının döndürdüğü: D <= MaxDen iken en yakın N/D
function NearestFraction(X: Double; MaxDen: Integer): TFraction;
var
D: Integer;
N: Int64;
Err, BestErr: Double;
begin
Result.Num := Round(X);
Result.Den := 1;
BestErr := Abs(X - Result.Num);
for D := 2 to MaxDen do
begin
N := Round(X * D);
Err := Abs(X - N / D);
if Err < BestErr then // katı: eşitlikte sadeleşmiş biçim kazanır
begin
BestErr := Err;
Result.Num := N;
Result.Den := D;
end;
end;
end;
procedure CompareFractionRules;
const
Samples: array[0..3] of Double = (0.14159, 0.99, 0.27, 0.5);
var
I: Integer;
E, N: TFraction;
begin
for I := Low(Samples) to High(Samples) do
begin
E := ExcelFraction(Samples[I], 99);
N := NearestFraction(Samples[I], 99);
Writeln(Format('%-8g Excel %d/%d nearest %d/%d',
[Samples[I], E.Num, E.Den, N.Num, N.Den]));
end;
end;
// Beklenen:
// 0.14159 Excel 1/7 nearest 14/99
// 0.99 Excel 98/99 nearest 98/99
// 0.27 Excel 10/37 nearest 17/63
// 0.5 Excel 1/2 nearest 1/2
0.27 satırı listede, çünkü sıradan iki ondalıklı bir girdi de ayrışır: 10/37'ye karşı 17/63; değerde hiçbir egzotik yan yoktur. Kuralların ne kadar sık ayrıştığı verilerinize bağlıdır: 0.5 ya da 0.375 gibi gerçek basit kesirler iki yoldan da aynı çıkar; ölçülen nicelikler ile hesaplanan oranlar çok daha az güvenilirdir. Trunc, Floor'un yerine durur; çünkü girdi asla negatif değildir. Excel işareti kesrin dışında ele alır. Sizin kodunuz da öyle yapmalı. Ve argüman, bir sonraki bölümün somutlaştıracağı nedenle, hücrenin gerçekte sakladığı Double olmalıdır
0.99, iki basamaklı bir kesirde neye dönüşür?
Excel, # ??/?? altında 0.99'u ·98/99 olarak gösterir (bu yazıdaki her · bir boşluk karakterini temsil eder). Sebep ikili kayan noktadır. 0.99 literali tam bir Double değildir ve saklanan değer onun bir tüy altında durur. Böylece 1/(1/0.99 − 1), kabaca 98.9999999999994 olarak değerlendirir, açılımın ikinci terimi 98 olur ve 98/99 yakınsağı sınırın altına sığar. Aynı açılımı tam ondalıklarla yapın: [0; 1, 99] elde edersiniz; işte o 99 paydayı 100'e iter, döngü 1/1'e geri düşer ve hücre 1······ okurdu. Burada Excel'den daha dikkatli olmak hatadır. Tutarları Currency ya da TBcd olarak tutan ya da ondalık dizgiyi veritabanından yeniden ayrıştırıp tam aritmetikle açan bir rapor üreticisi, Excel'in 98/99 yazdığı yere bütün bir 1 basar. Biçimlendiriciye saklanan Double'ı verin. Her ara değeri kayan noktada tutun. Kesir bir gösterim meselesi olarak da kalır: formüller 0.99'u görmeye devam eder ve HotXLS, precision as displayed açıkken bile kesir bölümlerini tam hassasiyetle tutar
Excel kesir alanını nasıl doldurur ve hizalar
Excel kesirleri, bir ondalık sütununun dizildiği gibi dizer: pay, yuvalarının içinde sağa yaslıdır; payda sola yaslıdır; böylece eğik çizgi her satırda aynı konuma düşer. Kullanılmayan her yuva, yer tutucusuna göre doldurulur: ? boşluk bırakır, 0 sıfır yazar, # hiçbir şey yazmaz. 3.14159'un # ??/?? altında 3··1/7· okunması budur: bir boşluk maskeden gelen ayraçtır, biri 1'in önündeki boş pay yuvasını, biri de 7'den sonraki boş payda yuvasını doldurur. Tablo, düzen kurallarını kapsayan bir girdi kümesi için Excel'in gösterdiği metni listeler
| Biçim kodu | Değer | Excel'in gösterdiği |
|---|---|---|
# ?/? | 0.5 | ·1/2 |
# ?/? | 4/3 | 1·1/3 |
# ??/?? | 3.14159 | 3··1/7· |
# ??/?? | 0.99 | ·98/99 |
# ?/? | 12 | 12···· |
# ?/? | 0 | 0···· |
# ?/? | 12.9999 | 13···· |
# #/# | 12 | 12 |
0 ?/? | 0.5 | 0·1/2 |
#,##0 ?/? | 1234.75 | 1,234·3/4 |
?/? | 3.14159 | 22/7 |
?/? | 0 | 0/1 |
??/?? | 12 | 12/1· |
Elle yazılan biçimlendiriciler Excel'den en çok tüm sayılarda sapar. Payı ? kullanan karma bir maske altında Excel tamsayı basamaklarını — sıfır dahil — korur ve ayraç, pay, eğik çizgi ile paydayı her birini bir boşlukla değiştirir; dolayısıyla # ?/? altındaki 12, 12···· olur ve # ??/100 altında ardına yedi boşluk eklenir. Pay yer tutucusunu #'ye çevirin; dolgu gider. Sıfır tamsayı ters yönde işler: # yuvası 0.5 için hiçbir şey basmaz ve ayraç yalnızca bir boşluk olarak hayatta kalır; 0 ?/? basamağı zorlarken uygunsuz maskeler kesri hiçbir zaman düşürmez
Dolguyla ne yapılacağı, metnin nereye gittiğine bağlıdır. Tek aralıklı metin raporları ve sağa yaslı PDF tablo hücreleri için koruyun; orada eğik çizgileri hizalamak işin bütün noktasıdır. CSV ve JSON için kırpın; orada sondaki bir boşluk, 12'yi artık sayı olarak ayrışmayan bir dizgiye çevirir. HTML de bir karar ister; çünkü tarayıcı, hücre white-space: pre kullanmadıkça boşluk dizilerini çöker. Ya onu ayarlayın ya kırpın; kimi satırları kaydıran, kimi kaydırmayan yarım çökmüş dolgudan kaçının
# ?/8 gibi sabit paydalar
1'den 9'a bir basamakla başlayan rakamlarla yazılmış payda, bir sınır değil sabit bir değerdir ve aritmetik baştan aşağı değişir: Excel değeri paydayla çarpar, en yakın tüm paya yuvarlar ve kesri sadeleştirmeden bırakır. 0/8 altında 0.375, 3/8 gösterir ve 0.64, 5/8 gösterir; çünkü 0.64 × 8, 5.12'dir. Hazır As sixteenths türü 0.5'i 8/16 olarak gösterir; teknik resim okuyan bir operatörün beklediği de budur, her sonucu sadeleştiren bir biçimlendiricinin onu 1/2'ye çevirmesi değil. Yuvarlanmış bir pay paydaya ulaşabilir. O zaman karma bir maske onu tamsayıya taşır: # ?/8 altındaki 2.97'nin 3···· okuması beklenir; çünkü 0.97 × 8 = 7.76, tam bir sekize yuvarlanır. İki aile arasında, paydanın okuyucu için ne anlama geldiğine göre seçin. Sabit paydalar, sabit adımlarla ifade edilen her şeye uyar: emperyal uzunluklar, matkap ölçüleri ya da sekizlik eski hisse fiyatları gibi. Kereste ölçüsü okuyan kimse 3/7 inç istemez. Yaklaşık kesir maskeleri ise, hiçbir belirli paydanın anlam taşımadığı oranlara ve ölçülen değerlere uyar
Excel'in kesir metnini Delphi'de FormattedText ile okumak
HotXLS'te döngüye kendiniz nadiren ihtiyaç duyarsınız. TXLSXCell.FormattedText, saklanan değeri hücrenin NumberFormat'ından, lxStandard.pas'ın TXLSFormater.GetDisplayValue'sını kullanarak geçirir; yakınsak seçimi, dolgu ve herhangi bir literal metin tek dizgide birlikte gelir. Aşağıdaki yordam hücrelere değer ve maske çiftleri atar ve her sonucu köşeli parantezler arasında basar; böylece sondaki boşluklar görünür kalır. Yeni bir TXLSXWorkbook çalışma sayfasız başlar; kod bu yüzden bir hücreye dokunmadan önce Sheets.Add çağırır. Sheets.Items ile Cells[Row, Col] ikisi de 1 tabanlıdır
uses
SysUtils, lxHandleX;
type
TFractionCase = record
Mask: string;
Value: Double;
end;
const
Cases: array[0..8] of TFractionCase = (
(Mask: '# ??/??'; Value: 3.14159),
(Mask: '# ??/??'; Value: 0.99),
(Mask: '# ?/?'; Value: 12),
(Mask: '# ?/?'; Value: 12.9999),
(Mask: '# #/#'; Value: 12),
(Mask: '?/?'; Value: 3.14159),
(Mask: '0/8'; Value: 0.64),
(Mask: '"x"# ?/?" y"'; Value: -12),
(Mask: '# ?/?;(# ?/?)'; Value: -1.5));
procedure ShowFractionText;
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Cell: TXLSXCell;
I: Integer;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Fractions'); // yeni bir çalışma kitabının henüz sayfası yoktur
for I := Low(Cases) to High(Cases) do
begin
Cell := Sheet.Cells[I + 1, 1]; // satır ve sütunlar 1'den başlar
Cell.Value := Cases[I].Value;
Cell.NumberFormat := Cases[I].Mask;
Writeln(Format('%-16s %9g [%s]',
[Cases[I].Mask, Cases[I].Value, Cell.FormattedText]));
end;
finally
Book.Free;
end;
end;
// Beklenen:
// # ??/?? 3.14159 [3 1/7 ]
// # ??/?? 0.99 [ 98/99]
// # ?/? 12 [12 ]
// # ?/? 12.9999 [13 ]
// # #/# 12 [12]
// ?/? 3.14159 [22/7]
// 0/8 0.64 [5/8]
// "x"# ?/?" y" -12 [-x12 y]
// # ?/?;(# ?/?) -1.5 [(1 1/2)]
Son iki durum, kesri neyin çevrelediğini gösterir. Tırnaklı metin ve ters eğik çizgi kaçışları düz karakter olarak basılır ve bir tüm değerin dolgusu sonekin önüne düşer; dolayısıyla "x"# ?/?" y", -12'yi -x12·····y'ye çevirir. Eksi işareti, öndeki (# ?/?) gibi açık bir negatif bölüm işaretin kendisini yazmadıkça, önek dahil her şeyin önüne gider; kesirler, öbür her Excel sayı biçimiyle aynı bölüm ve eksi işareti kurallarını izler. Değer bir hücreden değil bir veri kümesinden geldiğinde çalışma kitabını atlayın: TXLSFormater'ın parametresiz bir kurucusu vardır ve GetDisplayValue(Value, NumberFormat), FormattedText'in sayısal değerler için yaptığı çağrıdır
uses
SysUtils, lxStandard;
function FractionText(const AValue: Double; const AMask: WideString): WideString;
var
Fmt: TXLSFormater;
begin
Fmt := TXLSFormater.Create;
try
Result := Fmt.GetDisplayValue(AValue, AMask);
finally
Fmt.Free;
end;
end;
// FractionText(0.5, '# ?/?') Beklenen: ' 1/2'
// FractionText(1234.75, '#,##0 ?/?') Beklenen: '1,234 3/4'
// Trim(FractionText(12, '# ?/?')) Beklenen: '12' (CSV dostu)
Delphi kodunuzun Excel dışına bastığı her kesir, yakınsak döngüsünden ya da doğrudan FormattedText'ten, hücrenin kendi Double'ıyla beslenerek gelmelidir. Böylece kâğıttaki 3 1/7 ile ızgaradaki 3 1/7 aynıdır ve HotXLS Delphi spreadsheet component ile o metni, döngüyü hiç yazmadan alırsınız