fmtflt.inc 10.0 KB

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