fmtflt.inc 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. function IntFloatToTextFmt(Buf: FPChar; const Value; ValueType: TFloatValue; Format: FPChar; const AFormatSettings: TFormatSettings): Integer;
  2. Type
  3. TPosArray = Array[0..3] of Integer;
  4. const
  5. MaxPrecision = 18; // Extended precision
  6. var
  7. // Input in usable format
  8. E : Extended; // Value as extended.
  9. FV: TFloatRec; // Value as floatrec.
  10. Section : String; // Format can contain 3 sections, semicolon separated: Pos;Neg;Zero. This is the one to use.
  11. SectionLength : Integer; // Length of section.
  12. // Calculated based on section. Static during output
  13. ThousandSep: Boolean; // Thousands separator detected in format ?
  14. IsScientific: Boolean; // Use Scientific notation ? (E detected in format)
  15. DecimalPos: Integer; // Position of decimal point in pattern.
  16. FirstDigit: Integer; // First actual digit in input (# or 0), relative to decimal point
  17. LastDigit: Integer; // Last required (0) digit, relative to decimal point
  18. RequestedDigits: Integer; // Number of requested digits, # and 0 alike
  19. ExpSize : Integer; // Number of digits in exponent
  20. Available: Integer; // Available digits in FV.
  21. // These change during output loop
  22. Current: Integer; // Current digit in available digits
  23. PadZeroes: Integer; // Difference in requested digits before comma and exponent, needs to be padded with zeroes.
  24. DistToDecimal: Integer; // Place of current digit, relative to decimal point taking in account PadZeroes!
  25. Procedure InitVars;
  26. begin
  27. if ValueType = fvCurrency then
  28. E:=Currency(Value)
  29. else
  30. E:=Extended(Value);
  31. Section:='';
  32. SectionLength:=0;
  33. ThousandSep:=false;
  34. IsScientific:=false;
  35. DecimalPos:=0;
  36. FirstDigit:=MaxInt;
  37. LastDigit:=0;
  38. RequestedDigits:=0;
  39. ExpSize:=0;
  40. Available:=-1;
  41. end;
  42. procedure ToResult(const AChar: FChar); inline;
  43. begin
  44. Buf[Result]:=AChar;
  45. Inc(Result);
  46. // Writeln('->',AChar,'(',Ord(AChar),') : ',Result);
  47. end;
  48. procedure AddToResult(const AStr: FString);
  49. var
  50. I : Integer;
  51. begin
  52. For I:=1 to Length(AStr) do
  53. ToResult(AStr[I]);
  54. end;
  55. procedure WriteDigit(ADigit: FChar);
  56. // Write a digit to result, prepend with decimalseparator or append with 1000 separator
  57. begin
  58. if ADigit=#0 then exit;
  59. // Writeln('WriteDigit: ',ADigit,', DistToDecimal: ',DistToDecimal);
  60. Dec(DistToDecimal);
  61. // -1 -> we've arrived behind the decimal
  62. if (DistToDecimal=-1) then
  63. begin
  64. ToResult(AFormatSettings.DecimalSeparator);
  65. ToResult(ADigit);
  66. end
  67. else
  68. begin
  69. // We're still before the decimal.
  70. ToResult(ADigit);
  71. if ThousandSep and ((DistToDecimal mod 3)=0) and (DistToDecimal>1) then
  72. ToResult(AFormatSettings.ThousandSeparator);
  73. end;
  74. end;
  75. Function GetDigit : FChar;
  76. // Return next digit from available digits.
  77. // May return #0 if none available.
  78. // Will return '0' if applicable.
  79. begin
  80. // Writeln(' DistToDecimal <= LastDigit : ',DistToDecimal,' < ',LastDigit,' have digit: ',Current<=Available, '(',Current,')');
  81. Result:=#0;
  82. if (Current<=Available) then
  83. begin
  84. Result:=FV.Digits[Current];
  85. Inc(Current);
  86. end
  87. else if (DistToDecimal <= LastDigit) then
  88. Dec(DistToDecimal)
  89. else
  90. Result:='0';
  91. // Writeln('GetDigit ->: ',Result);
  92. end;
  93. procedure CopyDigit;
  94. // Copy a digit (#, 0) to the output with the correct value
  95. begin
  96. // Writeln('CopyDigit ');
  97. if (PadZeroes=0) then
  98. WriteDigit(GetDigit) // No shift needed, just copy what is available.
  99. else if (PadZeroes<0) then
  100. begin
  101. // We must prepend zeroes
  102. Inc(PadZeroes);
  103. if (DistToDecimal<=FirstDigit) then
  104. WriteDigit('0')
  105. else
  106. Dec(DistToDecimal);
  107. end
  108. else
  109. begin
  110. // We must append zeroes
  111. while PadZeroes > 0 do
  112. begin
  113. WriteDigit(GetDigit);
  114. Dec(PadZeroes);
  115. end;
  116. WriteDigit(GetDigit);
  117. end;
  118. end;
  119. Function GetSections(Var SP : TPosArray) : Integer;
  120. var
  121. FL : Integer;
  122. i : Integer;
  123. C,Q : FChar;
  124. inQuote : Boolean;
  125. begin
  126. Result:=1;
  127. SP[1]:=-1;
  128. SP[2]:=-1;
  129. SP[3]:=-1;
  130. inQuote:=False;
  131. Q:=#0;
  132. I:=0;
  133. FL:=StrLen(Format);
  134. while (I<FL) do
  135. begin
  136. C:=Format[I];
  137. case C of
  138. ';':
  139. begin
  140. if not InQuote then
  141. begin
  142. if Result>3 then
  143. Raise Exception.Create('Invalid float format');
  144. SP[Result]:=I+1;
  145. Inc(Result);
  146. end;
  147. end;
  148. '"','''':
  149. begin
  150. if InQuote then
  151. InQuote:=C<>Q
  152. else
  153. begin
  154. InQuote:=True;
  155. Q:=C;
  156. end;
  157. end;
  158. end;
  159. Inc(I);
  160. end;
  161. if SP[Result]=-1 then
  162. SP[Result]:=FL+1;
  163. end;
  164. Procedure AnalyzeFormat;
  165. var
  166. I,Len: Integer;
  167. Q,C: FChar;
  168. InQuote : Boolean;
  169. begin
  170. Len:=Length(Section);
  171. I:=1;
  172. InQuote:=False;
  173. Q:=#0;
  174. while (I<=Len) do
  175. begin
  176. C:=Section[i];
  177. if C in ['"',''''] then
  178. begin
  179. if InQuote then
  180. InQuote:=C<>Q
  181. else
  182. begin
  183. InQuote:=True;
  184. Q:=C;
  185. end;
  186. end
  187. else if not InQuote then
  188. case C of
  189. '.':
  190. if (DecimalPos=0) then
  191. DecimalPos:=RequestedDigits+1;
  192. ',':
  193. ThousandSep:=AFormatSettings.ThousandSeparator<>#0;
  194. 'e', 'E':
  195. begin
  196. Inc(I);
  197. if (I<Len) then
  198. begin
  199. C:=Section[i];
  200. IsScientific:=C in ['-','+'];
  201. if IsScientific then
  202. while (I<Len) and (Section[i+1]='0') do
  203. begin
  204. Inc(ExpSize);
  205. Inc(I);
  206. end;
  207. if ExpSize>4 then
  208. ExpSize:=4;
  209. end;
  210. end;
  211. '#':
  212. Inc(RequestedDigits);
  213. '0':
  214. begin
  215. if RequestedDigits<FirstDigit then
  216. FirstDigit:=RequestedDigits+1;
  217. Inc(RequestedDigits);
  218. LastDigit:=RequestedDigits+1;
  219. end;
  220. end;
  221. Inc(I);
  222. end;
  223. if DecimalPos=0 then
  224. DecimalPos:=RequestedDigits+1;
  225. // Writeln('LastDigit: ',DecimalPos,'-',LastDigit);
  226. LastDigit:=DecimalPos-LastDigit;
  227. if LastDigit>0 then
  228. LastDigit:=0;
  229. // Writeln('FirstDigit: ',DecimalPos,'-',FirstDigit);
  230. FirstDigit:=DecimalPos-FirstDigit;
  231. if FirstDigit<0 then
  232. FirstDigit:=0;
  233. end;
  234. Function ValueOutSideScope : Boolean;
  235. begin
  236. With FV do
  237. Result:=((Exponent >= 18) and (not IsScientific)) or (Exponent = $7FF) or (Exponent = $800)
  238. end;
  239. Procedure CalcRunVars;
  240. Var
  241. D,P: Integer;
  242. begin
  243. if IsScientific then
  244. begin
  245. P:=RequestedDigits;
  246. D:=9999;
  247. end
  248. else
  249. begin
  250. P:=MaxPrecision;
  251. D:=RequestedDigits-DecimalPos+1;
  252. end;
  253. FloatToDecimal(FV,Value,ValueType,P,D);
  254. DistToDecimal:=DecimalPos-1;
  255. if IsScientific then
  256. PadZeroes:=0 // No padding.
  257. else
  258. begin
  259. PadZeroes:=FV.Exponent-(DecimalPos-1);
  260. if (PadZeroes>=0) then
  261. DistToDecimal:=FV.Exponent
  262. end;
  263. // Writeln('PadZeroes : ',PadZeroes, ', DistToDecimal : ',DistToDecimal);
  264. Available:=-1;
  265. while (Available<High(FV.Digits)) and (FV.Digits[Available+1]<>#0) do
  266. Inc(Available);
  267. // Writeln('Available: ',Available);
  268. end;
  269. Function FormatExponent(ASign: FChar; aExponent: Integer) : FString;
  270. begin
  271. if E = 0 then
  272. aExponent := 0;
  273. Result:=IntToStr(Abs(aExponent));
  274. Result:=StringOfChar('0',ExpSize-Length(Result))+Result;
  275. if (aExponent<0) then
  276. Result:='-'+Result
  277. else if (aExponent>=0) and (aSign='+') then
  278. Result:=aSign+Result;
  279. end;
  280. var
  281. I,S : Integer;
  282. C,Q : FChar;
  283. PA : TPosArray;
  284. InLiteral : Boolean;
  285. begin
  286. Result:=0;
  287. Initvars;
  288. // What section to use ?
  289. if (E>0) then
  290. S:=1
  291. else if (E<0) then
  292. S:=2
  293. else
  294. S:=3;
  295. PA[0]:=0;
  296. I:=GetSections(PA);
  297. if (I<S) or (PA[S]-PA[S-1]=0) then
  298. S:=1;
  299. // Extract correct section
  300. SectionLength:=PA[S]-PA[S-1]-1;
  301. SetLength(Section,SectionLength);
  302. Move(Format[PA[S-1]],Section[1],SizeOf(FChar)*SectionLength);
  303. // Writeln('Section ',I,' : "',Section,'" ',SectionLength);
  304. AnalyzeFormat;
  305. // Writeln('RequestedDigits: ',RequestedDigits,', DecimalPos : ',DecimalPos,', LastDigit: ',LastDigit,', FirstDigit: ',FirstDigit);
  306. CalcRunVars;
  307. // If we cannot process value using current settings, fallback
  308. if (SectionLength=0) or ValueOutSideScope then
  309. Exit(FloatToText(FPChar(Buf), E, ffGeneral, 15, 0, AFormatSettings));
  310. // Get Started
  311. I:=1;
  312. Current:=0;
  313. Q:=' ';
  314. InLiteral:=False;
  315. if (FV.Negative) and (S=1) then
  316. ToResult('-');
  317. while (I<=SectionLength) do
  318. begin
  319. C:=Section[i];
  320. // Writeln('Analyzing pos ',I,': "',C,'"');
  321. If (C in ['"', '''']) then
  322. begin
  323. if InLiteral then
  324. InLiteral:=C<>Q
  325. else
  326. begin
  327. inLiteral:=True;
  328. Q:=C;
  329. end;
  330. end
  331. else if InLiteral then
  332. ToResult(C)
  333. else
  334. case C of
  335. '0', '#':
  336. CopyDigit;
  337. '.', ',':
  338. ; // Do nothing, handled by CopyDigit
  339. 'e', 'E':
  340. begin
  341. ToResult(C); // Always needed
  342. if IsScientific then
  343. begin
  344. Inc(I);
  345. if I<=Section.Length then
  346. begin
  347. C:=Section[I];
  348. if (C in ['+','-']) then
  349. begin
  350. AddToResult(FormatExponent(C,FV.Exponent-DecimalPos+1));
  351. // Skip rest
  352. while (I<SectionLength) and (Section[i+1]='0') do
  353. Inc(I);
  354. end;
  355. end;
  356. end
  357. else if I< SectionLength Then
  358. begin
  359. inc(I);
  360. ToResult(Section[i]);
  361. end;
  362. end;
  363. else
  364. ToResult(C);
  365. end;
  366. Inc(i);
  367. end;
  368. // Writeln('Result ',Result);
  369. end;