fmtflt.inc 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  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. Result:=IntToStr(aExponent);
  272. Result:=StringOfChar('0',ExpSize-Length(Result))+Result;
  273. if (aExponent<0) then
  274. Result:='-'+Result
  275. else if (aExponent>0) and (aSign='+') then
  276. Result:=aSign+Result;
  277. end;
  278. var
  279. I,S : Integer;
  280. C,Q : FChar;
  281. PA : TPosArray;
  282. InLiteral : Boolean;
  283. begin
  284. Result:=0;
  285. Initvars;
  286. // What section to use ?
  287. if (E>0) then
  288. S:=1
  289. else if (E<0) then
  290. S:=2
  291. else
  292. S:=3;
  293. PA[0]:=0;
  294. I:=GetSections(PA);
  295. if (I<S) or (PA[S]-PA[S-1]=0) then
  296. S:=1;
  297. // Extract correct section
  298. SectionLength:=PA[S]-PA[S-1]-1;
  299. SetLength(Section,SectionLength);
  300. Move(Format[PA[S-1]],Section[1],SizeOf(FChar)*SectionLength);
  301. // Writeln('Section ',I,' : "',Section,'" ',SectionLength);
  302. AnalyzeFormat;
  303. // Writeln('RequestedDigits: ',RequestedDigits,', DecimalPos : ',DecimalPos,', LastDigit: ',LastDigit,', FirstDigit: ',FirstDigit);
  304. CalcRunVars;
  305. // If we cannot process value using current settings, fallback
  306. if (SectionLength=0) or ValueOutSideScope then
  307. Exit(FloatToText(FPChar(Buf), E, ffGeneral, 15, 0, AFormatSettings));
  308. // Get Started
  309. I:=1;
  310. Current:=0;
  311. Q:=' ';
  312. InLiteral:=False;
  313. if (FV.Negative) and (S=1) then
  314. ToResult('-');
  315. while (I<=SectionLength) do
  316. begin
  317. C:=Section[i];
  318. // Writeln('Analyzing pos ',I,': "',C,'"');
  319. If (C in ['"', '''']) then
  320. begin
  321. if InLiteral then
  322. InLiteral:=C<>Q
  323. else
  324. begin
  325. inLiteral:=True;
  326. Q:=C;
  327. end;
  328. end
  329. else if InLiteral then
  330. ToResult(C)
  331. else
  332. case C of
  333. '0', '#':
  334. CopyDigit;
  335. '.', ',':
  336. ; // Do nothing, handled by CopyDigit
  337. 'e', 'E':
  338. begin
  339. ToResult(C); // Always needed
  340. Inc(I);
  341. if I<=Section.Length then
  342. begin
  343. C:=Section[I];
  344. if (C in ['+','-']) then
  345. begin
  346. AddToResult(FormatExponent(C,FV.Exponent-DecimalPos+1));
  347. // Skip rest
  348. while (I<SectionLength) and (Section[i+1]='0') do
  349. Inc(I);
  350. end;
  351. end;
  352. end;
  353. else
  354. ToResult(C);
  355. end;
  356. Inc(i);
  357. end;
  358. // Writeln('Result ',Result);
  359. end;