Number.Formatting.cs 102 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Buffers.Text;
  5. using System.Diagnostics;
  6. using System.Globalization;
  7. using System.Runtime.CompilerServices;
  8. using System.Runtime.InteropServices;
  9. using System.Text;
  10. namespace System
  11. {
  12. // The Format methods provided by the numeric classes convert
  13. // the numeric value to a string using the format string given by the
  14. // format parameter. If the format parameter is null or
  15. // an empty string, the number is formatted as if the string "G" (general
  16. // format) was specified. The info parameter specifies the
  17. // NumberFormatInfo instance to use when formatting the number. If the
  18. // info parameter is null or omitted, the numeric formatting information
  19. // is obtained from the current culture. The NumberFormatInfo supplies
  20. // such information as the characters to use for decimal and thousand
  21. // separators, and the spelling and placement of currency symbols in monetary
  22. // values.
  23. //
  24. // Format strings fall into two categories: Standard format strings and
  25. // user-defined format strings. A format string consisting of a single
  26. // alphabetic character (A-Z or a-z), optionally followed by a sequence of
  27. // digits (0-9), is a standard format string. All other format strings are
  28. // used-defined format strings.
  29. //
  30. // A standard format string takes the form Axx, where A is an
  31. // alphabetic character called the format specifier and xx is a
  32. // sequence of digits called the precision specifier. The format
  33. // specifier controls the type of formatting applied to the number and the
  34. // precision specifier controls the number of significant digits or decimal
  35. // places of the formatting operation. The following table describes the
  36. // supported standard formats.
  37. //
  38. // C c - Currency format. The number is
  39. // converted to a string that represents a currency amount. The conversion is
  40. // controlled by the currency format information of the NumberFormatInfo
  41. // used to format the number. The precision specifier indicates the desired
  42. // number of decimal places. If the precision specifier is omitted, the default
  43. // currency precision given by the NumberFormatInfo is used.
  44. //
  45. // D d - Decimal format. This format is
  46. // supported for integral types only. The number is converted to a string of
  47. // decimal digits, prefixed by a minus sign if the number is negative. The
  48. // precision specifier indicates the minimum number of digits desired in the
  49. // resulting string. If required, the number will be left-padded with zeros to
  50. // produce the number of digits given by the precision specifier.
  51. //
  52. // E e Engineering (scientific) format.
  53. // The number is converted to a string of the form
  54. // "-d.ddd...E+ddd" or "-d.ddd...e+ddd", where each
  55. // 'd' indicates a digit (0-9). The string starts with a minus sign if the
  56. // number is negative, and one digit always precedes the decimal point. The
  57. // precision specifier indicates the desired number of digits after the decimal
  58. // point. If the precision specifier is omitted, a default of 6 digits after
  59. // the decimal point is used. The format specifier indicates whether to prefix
  60. // the exponent with an 'E' or an 'e'. The exponent is always consists of a
  61. // plus or minus sign and three digits.
  62. //
  63. // F f Fixed point format. The number is
  64. // converted to a string of the form "-ddd.ddd....", where each
  65. // 'd' indicates a digit (0-9). The string starts with a minus sign if the
  66. // number is negative. The precision specifier indicates the desired number of
  67. // decimal places. If the precision specifier is omitted, the default numeric
  68. // precision given by the NumberFormatInfo is used.
  69. //
  70. // G g - General format. The number is
  71. // converted to the shortest possible decimal representation using fixed point
  72. // or scientific format. The precision specifier determines the number of
  73. // significant digits in the resulting string. If the precision specifier is
  74. // omitted, the number of significant digits is determined by the type of the
  75. // number being converted (10 for int, 19 for long, 7 for
  76. // float, 15 for double, 19 for Currency, and 29 for
  77. // Decimal). Trailing zeros after the decimal point are removed, and the
  78. // resulting string contains a decimal point only if required. The resulting
  79. // string uses fixed point format if the exponent of the number is less than
  80. // the number of significant digits and greater than or equal to -4. Otherwise,
  81. // the resulting string uses scientific format, and the case of the format
  82. // specifier controls whether the exponent is prefixed with an 'E' or an 'e'.
  83. //
  84. // N n Number format. The number is
  85. // converted to a string of the form "-d,ddd,ddd.ddd....", where
  86. // each 'd' indicates a digit (0-9). The string starts with a minus sign if the
  87. // number is negative. Thousand separators are inserted between each group of
  88. // three digits to the left of the decimal point. The precision specifier
  89. // indicates the desired number of decimal places. If the precision specifier
  90. // is omitted, the default numeric precision given by the
  91. // NumberFormatInfo is used.
  92. //
  93. // X x - Hexadecimal format. This format is
  94. // supported for integral types only. The number is converted to a string of
  95. // hexadecimal digits. The format specifier indicates whether to use upper or
  96. // lower case characters for the hexadecimal digits above 9 ('X' for 'ABCDEF',
  97. // and 'x' for 'abcdef'). The precision specifier indicates the minimum number
  98. // of digits desired in the resulting string. If required, the number will be
  99. // left-padded with zeros to produce the number of digits given by the
  100. // precision specifier.
  101. //
  102. // Some examples of standard format strings and their results are shown in the
  103. // table below. (The examples all assume a default NumberFormatInfo.)
  104. //
  105. // Value Format Result
  106. // 12345.6789 C $12,345.68
  107. // -12345.6789 C ($12,345.68)
  108. // 12345 D 12345
  109. // 12345 D8 00012345
  110. // 12345.6789 E 1.234568E+004
  111. // 12345.6789 E10 1.2345678900E+004
  112. // 12345.6789 e4 1.2346e+004
  113. // 12345.6789 F 12345.68
  114. // 12345.6789 F0 12346
  115. // 12345.6789 F6 12345.678900
  116. // 12345.6789 G 12345.6789
  117. // 12345.6789 G7 12345.68
  118. // 123456789 G7 1.234568E8
  119. // 12345.6789 N 12,345.68
  120. // 123456789 N4 123,456,789.0000
  121. // 0x2c45e x 2c45e
  122. // 0x2c45e X 2C45E
  123. // 0x2c45e X8 0002C45E
  124. //
  125. // Format strings that do not start with an alphabetic character, or that start
  126. // with an alphabetic character followed by a non-digit, are called
  127. // user-defined format strings. The following table describes the formatting
  128. // characters that are supported in user defined format strings.
  129. //
  130. //
  131. // 0 - Digit placeholder. If the value being
  132. // formatted has a digit in the position where the '0' appears in the format
  133. // string, then that digit is copied to the output string. Otherwise, a '0' is
  134. // stored in that position in the output string. The position of the leftmost
  135. // '0' before the decimal point and the rightmost '0' after the decimal point
  136. // determines the range of digits that are always present in the output
  137. // string.
  138. //
  139. // # - Digit placeholder. If the value being
  140. // formatted has a digit in the position where the '#' appears in the format
  141. // string, then that digit is copied to the output string. Otherwise, nothing
  142. // is stored in that position in the output string.
  143. //
  144. // . - Decimal point. The first '.' character
  145. // in the format string determines the location of the decimal separator in the
  146. // formatted value; any additional '.' characters are ignored. The actual
  147. // character used as a the decimal separator in the output string is given by
  148. // the NumberFormatInfo used to format the number.
  149. //
  150. // , - Thousand separator and number scaling.
  151. // The ',' character serves two purposes. First, if the format string contains
  152. // a ',' character between two digit placeholders (0 or #) and to the left of
  153. // the decimal point if one is present, then the output will have thousand
  154. // separators inserted between each group of three digits to the left of the
  155. // decimal separator. The actual character used as a the decimal separator in
  156. // the output string is given by the NumberFormatInfo used to format the
  157. // number. Second, if the format string contains one or more ',' characters
  158. // immediately to the left of the decimal point, or after the last digit
  159. // placeholder if there is no decimal point, then the number will be divided by
  160. // 1000 times the number of ',' characters before it is formatted. For example,
  161. // the format string '0,,' will represent 100 million as just 100. Use of the
  162. // ',' character to indicate scaling does not also cause the formatted number
  163. // to have thousand separators. Thus, to scale a number by 1 million and insert
  164. // thousand separators you would use the format string '#,##0,,'.
  165. //
  166. // % - Percentage placeholder. The presence of
  167. // a '%' character in the format string causes the number to be multiplied by
  168. // 100 before it is formatted. The '%' character itself is inserted in the
  169. // output string where it appears in the format string.
  170. //
  171. // E+ E- e+ e- - Scientific notation.
  172. // If any of the strings 'E+', 'E-', 'e+', or 'e-' are present in the format
  173. // string and are immediately followed by at least one '0' character, then the
  174. // number is formatted using scientific notation with an 'E' or 'e' inserted
  175. // between the number and the exponent. The number of '0' characters following
  176. // the scientific notation indicator determines the minimum number of digits to
  177. // output for the exponent. The 'E+' and 'e+' formats indicate that a sign
  178. // character (plus or minus) should always precede the exponent. The 'E-' and
  179. // 'e-' formats indicate that a sign character should only precede negative
  180. // exponents.
  181. //
  182. // \ - Literal character. A backslash character
  183. // causes the next character in the format string to be copied to the output
  184. // string as-is. The backslash itself isn't copied, so to place a backslash
  185. // character in the output string, use two backslashes (\\) in the format
  186. // string.
  187. //
  188. // 'ABC' "ABC" - Literal string. Characters
  189. // enclosed in single or double quotation marks are copied to the output string
  190. // as-is and do not affect formatting.
  191. //
  192. // ; - Section separator. The ';' character is
  193. // used to separate sections for positive, negative, and zero numbers in the
  194. // format string.
  195. //
  196. // Other - All other characters are copied to
  197. // the output string in the position they appear.
  198. //
  199. // For fixed point formats (formats not containing an 'E+', 'E-', 'e+', or
  200. // 'e-'), the number is rounded to as many decimal places as there are digit
  201. // placeholders to the right of the decimal point. If the format string does
  202. // not contain a decimal point, the number is rounded to the nearest
  203. // integer. If the number has more digits than there are digit placeholders to
  204. // the left of the decimal point, the extra digits are copied to the output
  205. // string immediately before the first digit placeholder.
  206. //
  207. // For scientific formats, the number is rounded to as many significant digits
  208. // as there are digit placeholders in the format string.
  209. //
  210. // To allow for different formatting of positive, negative, and zero values, a
  211. // user-defined format string may contain up to three sections separated by
  212. // semicolons. The results of having one, two, or three sections in the format
  213. // string are described in the table below.
  214. //
  215. // Sections:
  216. //
  217. // One - The format string applies to all values.
  218. //
  219. // Two - The first section applies to positive values
  220. // and zeros, and the second section applies to negative values. If the number
  221. // to be formatted is negative, but becomes zero after rounding according to
  222. // the format in the second section, then the resulting zero is formatted
  223. // according to the first section.
  224. //
  225. // Three - The first section applies to positive
  226. // values, the second section applies to negative values, and the third section
  227. // applies to zeros. The second section may be left empty (by having no
  228. // characters between the semicolons), in which case the first section applies
  229. // to all non-zero values. If the number to be formatted is non-zero, but
  230. // becomes zero after rounding according to the format in the first or second
  231. // section, then the resulting zero is formatted according to the third
  232. // section.
  233. //
  234. // For both standard and user-defined formatting operations on values of type
  235. // float and double, if the value being formatted is a NaN (Not
  236. // a Number) or a positive or negative infinity, then regardless of the format
  237. // string, the resulting string is given by the NaNSymbol,
  238. // PositiveInfinitySymbol, or NegativeInfinitySymbol property of
  239. // the NumberFormatInfo used to format the number.
  240. internal static partial class Number
  241. {
  242. internal const int DecimalPrecision = 29; // Decimal.DecCalc also uses this value
  243. // SinglePrecision and DoublePrecision represent the maximum number of digits required
  244. // to guarantee that any given Single or Double can roundtrip. Some numbers may require
  245. // less, but none will require more.
  246. private const int SinglePrecision = 9;
  247. private const int DoublePrecision = 17;
  248. private const int DefaultPrecisionExponentialFormat = 6;
  249. private const int ScaleNAN = unchecked((int)0x80000000);
  250. private const int ScaleINF = 0x7FFFFFFF;
  251. private const int MaxUInt32DecDigits = 10;
  252. private const int CharStackBufferSize = 32;
  253. private const string PosNumberFormat = "#";
  254. private static readonly string[] s_singleDigitStringCache = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
  255. private static readonly string[] s_posCurrencyFormats =
  256. {
  257. "$#", "#$", "$ #", "# $"
  258. };
  259. private static readonly string[] s_negCurrencyFormats =
  260. {
  261. "($#)", "-$#", "$-#", "$#-",
  262. "(#$)", "-#$", "#-$", "#$-",
  263. "-# $", "-$ #", "# $-", "$ #-",
  264. "$ -#", "#- $", "($ #)", "(# $)"
  265. };
  266. private static readonly string[] s_posPercentFormats =
  267. {
  268. "# %", "#%", "%#", "% #"
  269. };
  270. private static readonly string[] s_negPercentFormats =
  271. {
  272. "-# %", "-#%", "-%#",
  273. "%-#", "%#-",
  274. "#-%", "#%-",
  275. "-% #", "# %-", "% #-",
  276. "% -#", "#- %"
  277. };
  278. private static readonly string[] s_negNumberFormats =
  279. {
  280. "(#)", "-#", "- #", "#-", "# -",
  281. };
  282. public static unsafe string FormatDecimal(decimal value, ReadOnlySpan<char> format, NumberFormatInfo info)
  283. {
  284. char fmt = ParseFormatSpecifier(format, out int digits);
  285. byte* pDigits = stackalloc byte[DecimalNumberBufferLength];
  286. NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, pDigits, DecimalNumberBufferLength);
  287. DecimalToNumber(ref value, ref number);
  288. char* stackPtr = stackalloc char[CharStackBufferSize];
  289. ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
  290. if (fmt != 0)
  291. {
  292. NumberToString(ref sb, ref number, fmt, digits, info);
  293. }
  294. else
  295. {
  296. NumberToStringFormat(ref sb, ref number, format, info);
  297. }
  298. return sb.ToString();
  299. }
  300. public static unsafe bool TryFormatDecimal(decimal value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
  301. {
  302. char fmt = ParseFormatSpecifier(format, out int digits);
  303. byte* pDigits = stackalloc byte[DecimalNumberBufferLength];
  304. NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, pDigits, DecimalNumberBufferLength);
  305. DecimalToNumber(ref value, ref number);
  306. char* stackPtr = stackalloc char[CharStackBufferSize];
  307. ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
  308. if (fmt != 0)
  309. {
  310. NumberToString(ref sb, ref number, fmt, digits, info);
  311. }
  312. else
  313. {
  314. NumberToStringFormat(ref sb, ref number, format, info);
  315. }
  316. return sb.TryCopyTo(destination, out charsWritten);
  317. }
  318. internal static unsafe void DecimalToNumber(ref decimal d, ref NumberBuffer number)
  319. {
  320. byte* buffer = number.GetDigitsPointer();
  321. number.DigitsCount = DecimalPrecision;
  322. number.IsNegative = d.IsNegative;
  323. byte* p = buffer + DecimalPrecision;
  324. while ((d.Mid | d.High) != 0)
  325. {
  326. p = UInt32ToDecChars(p, decimal.DecDivMod1E9(ref d), 9);
  327. }
  328. p = UInt32ToDecChars(p, d.Low, 0);
  329. int i = (int)((buffer + DecimalPrecision) - p);
  330. number.DigitsCount = i;
  331. number.Scale = i - d.Scale;
  332. byte* dst = number.GetDigitsPointer();
  333. while (--i >= 0)
  334. {
  335. *dst++ = *p++;
  336. }
  337. *dst = (byte)('\0');
  338. number.CheckConsistency();
  339. }
  340. public static string FormatDouble(double value, string format, NumberFormatInfo info)
  341. {
  342. Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
  343. var sb = new ValueStringBuilder(stackBuffer);
  344. return FormatDouble(ref sb, value, format, info) ?? sb.ToString();
  345. }
  346. public static bool TryFormatDouble(double value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
  347. {
  348. Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
  349. var sb = new ValueStringBuilder(stackBuffer);
  350. string s = FormatDouble(ref sb, value, format, info);
  351. return s != null ?
  352. TryCopyTo(s, destination, out charsWritten) :
  353. sb.TryCopyTo(destination, out charsWritten);
  354. }
  355. private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int precision, NumberFormatInfo info, out bool isSignificantDigits)
  356. {
  357. if (fmt == 0)
  358. {
  359. isSignificantDigits = true;
  360. return precision;
  361. }
  362. int maxDigits = precision;
  363. switch (fmt)
  364. {
  365. case 'C':
  366. case 'c':
  367. {
  368. // The currency format uses the precision specifier to indicate the number of
  369. // decimal digits to format. This defaults to NumberFormatInfo.CurrencyDecimalDigits.
  370. if (precision == -1)
  371. {
  372. precision = info.CurrencyDecimalDigits;
  373. }
  374. isSignificantDigits = false;
  375. break;
  376. }
  377. case 'E':
  378. case 'e':
  379. {
  380. // The exponential format uses the precision specifier to indicate the number of
  381. // decimal digits to format. This defaults to 6. However, the exponential format
  382. // also always formats a single integral digit, so we need to increase the precision
  383. // specifier and treat it as the number of significant digits to account for this.
  384. if (precision == -1)
  385. {
  386. precision = DefaultPrecisionExponentialFormat;
  387. }
  388. precision++;
  389. isSignificantDigits = true;
  390. break;
  391. }
  392. case 'F':
  393. case 'f':
  394. case 'N':
  395. case 'n':
  396. {
  397. // The fixed-point and number formats use the precision specifier to indicate the number
  398. // of decimal digits to format. This defaults to NumberFormatInfo.NumberDecimalDigits.
  399. if (precision == -1)
  400. {
  401. precision = info.NumberDecimalDigits;
  402. }
  403. isSignificantDigits = false;
  404. break;
  405. }
  406. case 'G':
  407. case 'g':
  408. {
  409. // The general format uses the precision specifier to indicate the number of significant
  410. // digits to format. This defaults to the shortest roundtrippable string. Additionally,
  411. // given that we can't return zero significant digits, we treat 0 as returning the shortest
  412. // roundtrippable string as well.
  413. if (precision == 0)
  414. {
  415. precision = -1;
  416. }
  417. isSignificantDigits = true;
  418. break;
  419. }
  420. case 'P':
  421. case 'p':
  422. {
  423. // The percent format uses the precision specifier to indicate the number of
  424. // decimal digits to format. This defaults to NumberFormatInfo.PercentDecimalDigits.
  425. // However, the percent format also always multiplies the number by 100, so we need
  426. // to increase the precision specifier to ensure we get the appropriate number of digits.
  427. if (precision == -1)
  428. {
  429. precision = info.PercentDecimalDigits;
  430. }
  431. precision += 2;
  432. isSignificantDigits = false;
  433. break;
  434. }
  435. case 'R':
  436. case 'r':
  437. {
  438. // The roundtrip format ignores the precision specifier and always returns the shortest
  439. // roundtrippable string.
  440. precision = -1;
  441. isSignificantDigits = true;
  442. break;
  443. }
  444. default:
  445. {
  446. throw new FormatException(SR.Argument_BadFormatSpecifier);
  447. }
  448. }
  449. return maxDigits;
  450. }
  451. /// <summary>Formats the specified value according to the specified format and info.</summary>
  452. /// <returns>
  453. /// Non-null if an existing string can be returned, in which case the builder will be unmodified.
  454. /// Null if no existing string was returned, in which case the formatted output is in the builder.
  455. /// </returns>
  456. private static unsafe string FormatDouble(ref ValueStringBuilder sb, double value, ReadOnlySpan<char> format, NumberFormatInfo info)
  457. {
  458. if (!double.IsFinite(value))
  459. {
  460. if (double.IsNaN(value))
  461. {
  462. return info.NaNSymbol;
  463. }
  464. return double.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
  465. }
  466. char fmt = ParseFormatSpecifier(format, out int precision);
  467. byte* pDigits = stackalloc byte[DoubleNumberBufferLength];
  468. NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, DoubleNumberBufferLength);
  469. number.IsNegative = double.IsNegative(value);
  470. // We need to track the original precision requested since some formats
  471. // accept values like 0 and others may require additional fixups.
  472. int nMaxDigits = GetFloatingPointMaxDigitsAndPrecision(fmt, ref precision, info, out bool isSignificantDigits);
  473. if ((value != 0.0) && (!isSignificantDigits || !Grisu3.TryRunDouble(value, precision, ref number)))
  474. {
  475. Dragon4Double(value, precision, isSignificantDigits, ref number);
  476. }
  477. number.CheckConsistency();
  478. // When the number is known to be roundtrippable (either because we requested it be, or
  479. // because we know we have enough digits to satisfy roundtrippability), we should validate
  480. // that the number actually roundtrips back to the original result.
  481. Debug.Assert(((precision != -1) && (precision < DoublePrecision)) || (BitConverter.DoubleToInt64Bits(value) == BitConverter.DoubleToInt64Bits(NumberToDouble(ref number))));
  482. if (fmt != 0)
  483. {
  484. if (precision == -1)
  485. {
  486. Debug.Assert((fmt == 'G') || (fmt == 'g') || (fmt == 'R') || (fmt == 'r'));
  487. // For the roundtrip and general format specifiers, when returning the shortest roundtrippable
  488. // string, we need to update the maximum number of digits to be the greater of number.DigitsCount
  489. // or DoublePrecision. This ensures that we continue returning "pretty" strings for values with
  490. // less digits. One example this fixes is "-60", which would otherwise be formatted as "-6E+01"
  491. // since DigitsCount would be 1 and the formatter would almost immediately switch to scientific notation.
  492. nMaxDigits = Math.Max(number.DigitsCount, DoublePrecision);
  493. }
  494. NumberToString(ref sb, ref number, fmt, nMaxDigits, info);
  495. }
  496. else
  497. {
  498. NumberToStringFormat(ref sb, ref number, format, info);
  499. }
  500. return null;
  501. }
  502. public static string FormatSingle(float value, string format, NumberFormatInfo info)
  503. {
  504. Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
  505. var sb = new ValueStringBuilder(stackBuffer);
  506. return FormatSingle(ref sb, value, format, info) ?? sb.ToString();
  507. }
  508. public static bool TryFormatSingle(float value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
  509. {
  510. Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
  511. var sb = new ValueStringBuilder(stackBuffer);
  512. string s = FormatSingle(ref sb, value, format, info);
  513. return s != null ?
  514. TryCopyTo(s, destination, out charsWritten) :
  515. sb.TryCopyTo(destination, out charsWritten);
  516. }
  517. /// <summary>Formats the specified value according to the specified format and info.</summary>
  518. /// <returns>
  519. /// Non-null if an existing string can be returned, in which case the builder will be unmodified.
  520. /// Null if no existing string was returned, in which case the formatted output is in the builder.
  521. /// </returns>
  522. private static unsafe string FormatSingle(ref ValueStringBuilder sb, float value, ReadOnlySpan<char> format, NumberFormatInfo info)
  523. {
  524. if (!float.IsFinite(value))
  525. {
  526. if (float.IsNaN(value))
  527. {
  528. return info.NaNSymbol;
  529. }
  530. return float.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
  531. }
  532. char fmt = ParseFormatSpecifier(format, out int precision);
  533. byte* pDigits = stackalloc byte[SingleNumberBufferLength];
  534. NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, SingleNumberBufferLength);
  535. number.IsNegative = float.IsNegative(value);
  536. // We need to track the original precision requested since some formats
  537. // accept values like 0 and others may require additional fixups.
  538. int nMaxDigits = GetFloatingPointMaxDigitsAndPrecision(fmt, ref precision, info, out bool isSignificantDigits);
  539. if ((value != 0.0f) && (!isSignificantDigits || !Grisu3.TryRunSingle(value, precision, ref number)))
  540. {
  541. Dragon4Single(value, precision, isSignificantDigits, ref number);
  542. }
  543. number.CheckConsistency();
  544. // When the number is known to be roundtrippable (either because we requested it be, or
  545. // because we know we have enough digits to satisfy roundtrippability), we should validate
  546. // that the number actually roundtrips back to the original result.
  547. Debug.Assert(((precision != -1) && (precision < SinglePrecision)) || (BitConverter.SingleToInt32Bits(value) == BitConverter.SingleToInt32Bits(NumberToSingle(ref number))));
  548. if (fmt != 0)
  549. {
  550. if (precision == -1)
  551. {
  552. Debug.Assert((fmt == 'G') || (fmt == 'g') || (fmt == 'R') || (fmt == 'r'));
  553. // For the roundtrip and general format specifiers, when returning the shortest roundtrippable
  554. // string, we need to update the maximum number of digits to be the greater of number.DigitsCount
  555. // or SinglePrecision. This ensures that we continue returning "pretty" strings for values with
  556. // less digits. One example this fixes is "-60", which would otherwise be formatted as "-6E+01"
  557. // since DigitsCount would be 1 and the formatter would almost immediately switch to scientific notation.
  558. nMaxDigits = Math.Max(number.DigitsCount, SinglePrecision);
  559. }
  560. NumberToString(ref sb, ref number, fmt, nMaxDigits, info);
  561. }
  562. else
  563. {
  564. NumberToStringFormat(ref sb, ref number, format, info);
  565. }
  566. return null;
  567. }
  568. private static bool TryCopyTo(string source, Span<char> destination, out int charsWritten)
  569. {
  570. Debug.Assert(source != null);
  571. if (source.AsSpan().TryCopyTo(destination))
  572. {
  573. charsWritten = source.Length;
  574. return true;
  575. }
  576. charsWritten = 0;
  577. return false;
  578. }
  579. public static unsafe string FormatInt32(int value, ReadOnlySpan<char> format, IFormatProvider provider)
  580. {
  581. // Fast path for default format with a non-negative value
  582. if (value >= 0 && format.Length == 0)
  583. {
  584. return UInt32ToDecStr((uint)value, digits: -1);
  585. }
  586. char fmt = ParseFormatSpecifier(format, out int digits);
  587. char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
  588. if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
  589. {
  590. return value >= 0 ?
  591. UInt32ToDecStr((uint)value, digits) :
  592. NegativeInt32ToDecStr(value, digits, NumberFormatInfo.GetInstance(provider).NegativeSign);
  593. }
  594. else if (fmtUpper == 'X')
  595. {
  596. // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
  597. // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
  598. return Int32ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits);
  599. }
  600. else
  601. {
  602. NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
  603. byte* pDigits = stackalloc byte[Int32NumberBufferLength];
  604. NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int32NumberBufferLength);
  605. Int32ToNumber(value, ref number);
  606. char* stackPtr = stackalloc char[CharStackBufferSize];
  607. ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
  608. if (fmt != 0)
  609. {
  610. NumberToString(ref sb, ref number, fmt, digits, info);
  611. }
  612. else
  613. {
  614. NumberToStringFormat(ref sb, ref number, format, info);
  615. }
  616. return sb.ToString();
  617. }
  618. }
  619. public static unsafe bool TryFormatInt32(int value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
  620. {
  621. // Fast path for default format with a non-negative value
  622. if (value >= 0 && format.Length == 0)
  623. {
  624. return TryUInt32ToDecStr((uint)value, digits: -1, destination, out charsWritten);
  625. }
  626. char fmt = ParseFormatSpecifier(format, out int digits);
  627. char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
  628. if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
  629. {
  630. return value >= 0 ?
  631. TryUInt32ToDecStr((uint)value, digits, destination, out charsWritten) :
  632. TryNegativeInt32ToDecStr(value, digits, NumberFormatInfo.GetInstance(provider).NegativeSign, destination, out charsWritten);
  633. }
  634. else if (fmtUpper == 'X')
  635. {
  636. // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
  637. // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
  638. return TryInt32ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
  639. }
  640. else
  641. {
  642. NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
  643. byte* pDigits = stackalloc byte[Int32NumberBufferLength];
  644. NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int32NumberBufferLength);
  645. Int32ToNumber(value, ref number);
  646. char* stackPtr = stackalloc char[CharStackBufferSize];
  647. ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
  648. if (fmt != 0)
  649. {
  650. NumberToString(ref sb, ref number, fmt, digits, info);
  651. }
  652. else
  653. {
  654. NumberToStringFormat(ref sb, ref number, format, info);
  655. }
  656. return sb.TryCopyTo(destination, out charsWritten);
  657. }
  658. }
  659. public static unsafe string FormatUInt32(uint value, ReadOnlySpan<char> format, IFormatProvider provider)
  660. {
  661. // Fast path for default format
  662. if (format.Length == 0)
  663. {
  664. return UInt32ToDecStr(value, digits: -1);
  665. }
  666. char fmt = ParseFormatSpecifier(format, out int digits);
  667. char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
  668. if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
  669. {
  670. return UInt32ToDecStr(value, digits);
  671. }
  672. else if (fmtUpper == 'X')
  673. {
  674. // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
  675. // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
  676. return Int32ToHexStr((int)value, (char)(fmt - ('X' - 'A' + 10)), digits);
  677. }
  678. else
  679. {
  680. NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
  681. byte* pDigits = stackalloc byte[UInt32NumberBufferLength];
  682. NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt32NumberBufferLength);
  683. UInt32ToNumber(value, ref number);
  684. char* stackPtr = stackalloc char[CharStackBufferSize];
  685. ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
  686. if (fmt != 0)
  687. {
  688. NumberToString(ref sb, ref number, fmt, digits, info);
  689. }
  690. else
  691. {
  692. NumberToStringFormat(ref sb, ref number, format, info);
  693. }
  694. return sb.ToString();
  695. }
  696. }
  697. public static unsafe bool TryFormatUInt32(uint value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
  698. {
  699. // Fast path for default format
  700. if (format.Length == 0)
  701. {
  702. return TryUInt32ToDecStr(value, digits: -1, destination, out charsWritten);
  703. }
  704. char fmt = ParseFormatSpecifier(format, out int digits);
  705. char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
  706. if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
  707. {
  708. return TryUInt32ToDecStr(value, digits, destination, out charsWritten);
  709. }
  710. else if (fmtUpper == 'X')
  711. {
  712. // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
  713. // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
  714. return TryInt32ToHexStr((int)value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
  715. }
  716. else
  717. {
  718. NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
  719. byte* pDigits = stackalloc byte[UInt32NumberBufferLength];
  720. NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt32NumberBufferLength);
  721. UInt32ToNumber(value, ref number);
  722. char* stackPtr = stackalloc char[CharStackBufferSize];
  723. ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
  724. if (fmt != 0)
  725. {
  726. NumberToString(ref sb, ref number, fmt, digits, info);
  727. }
  728. else
  729. {
  730. NumberToStringFormat(ref sb, ref number, format, info);
  731. }
  732. return sb.TryCopyTo(destination, out charsWritten);
  733. }
  734. }
  735. public static unsafe string FormatInt64(long value, ReadOnlySpan<char> format, IFormatProvider provider)
  736. {
  737. // Fast path for default format with a non-negative value
  738. if (value >= 0 && format.Length == 0)
  739. {
  740. return UInt64ToDecStr((ulong)value, digits: -1);
  741. }
  742. char fmt = ParseFormatSpecifier(format, out int digits);
  743. char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
  744. if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
  745. {
  746. return value >= 0 ?
  747. UInt64ToDecStr((ulong)value, digits) :
  748. NegativeInt64ToDecStr(value, digits, NumberFormatInfo.GetInstance(provider).NegativeSign);
  749. }
  750. else if (fmtUpper == 'X')
  751. {
  752. // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
  753. // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
  754. // produces lowercase.
  755. return Int64ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits);
  756. }
  757. else
  758. {
  759. NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
  760. byte* pDigits = stackalloc byte[Int64NumberBufferLength];
  761. NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int64NumberBufferLength);
  762. Int64ToNumber(value, ref number);
  763. char* stackPtr = stackalloc char[CharStackBufferSize];
  764. ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
  765. if (fmt != 0)
  766. {
  767. NumberToString(ref sb, ref number, fmt, digits, info);
  768. }
  769. else
  770. {
  771. NumberToStringFormat(ref sb, ref number, format, info);
  772. }
  773. return sb.ToString();
  774. }
  775. }
  776. public static unsafe bool TryFormatInt64(long value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
  777. {
  778. // Fast path for default format with a non-negative value
  779. if (value >= 0 && format.Length == 0)
  780. {
  781. return TryUInt64ToDecStr((ulong)value, digits: -1, destination, out charsWritten);
  782. }
  783. char fmt = ParseFormatSpecifier(format, out int digits);
  784. char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
  785. if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
  786. {
  787. return value >= 0 ?
  788. TryUInt64ToDecStr((ulong)value, digits, destination, out charsWritten) :
  789. TryNegativeInt64ToDecStr(value, digits, NumberFormatInfo.GetInstance(provider).NegativeSign, destination, out charsWritten);
  790. }
  791. else if (fmtUpper == 'X')
  792. {
  793. // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
  794. // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
  795. // produces lowercase.
  796. return TryInt64ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
  797. }
  798. else
  799. {
  800. NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
  801. byte* pDigits = stackalloc byte[Int64NumberBufferLength];
  802. NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int64NumberBufferLength);
  803. Int64ToNumber(value, ref number);
  804. char* stackPtr = stackalloc char[CharStackBufferSize];
  805. ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
  806. if (fmt != 0)
  807. {
  808. NumberToString(ref sb, ref number, fmt, digits, info);
  809. }
  810. else
  811. {
  812. NumberToStringFormat(ref sb, ref number, format, info);
  813. }
  814. return sb.TryCopyTo(destination, out charsWritten);
  815. }
  816. }
  817. public static unsafe string FormatUInt64(ulong value, ReadOnlySpan<char> format, IFormatProvider provider)
  818. {
  819. // Fast path for default format
  820. if (format.Length == 0)
  821. {
  822. return UInt64ToDecStr(value, digits: -1);
  823. }
  824. char fmt = ParseFormatSpecifier(format, out int digits);
  825. char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
  826. if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
  827. {
  828. return UInt64ToDecStr(value, digits);
  829. }
  830. else if (fmtUpper == 'X')
  831. {
  832. // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
  833. // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
  834. // produces lowercase.
  835. return Int64ToHexStr((long)value, (char)(fmt - ('X' - 'A' + 10)), digits);
  836. }
  837. else
  838. {
  839. NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
  840. byte* pDigits = stackalloc byte[UInt64NumberBufferLength];
  841. NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt64NumberBufferLength);
  842. UInt64ToNumber(value, ref number);
  843. char* stackPtr = stackalloc char[CharStackBufferSize];
  844. ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
  845. if (fmt != 0)
  846. {
  847. NumberToString(ref sb, ref number, fmt, digits, info);
  848. }
  849. else
  850. {
  851. NumberToStringFormat(ref sb, ref number, format, info);
  852. }
  853. return sb.ToString();
  854. }
  855. }
  856. public static unsafe bool TryFormatUInt64(ulong value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
  857. {
  858. // Fast path for default format
  859. if (format.Length == 0)
  860. {
  861. return TryUInt64ToDecStr(value, digits: -1, destination, out charsWritten);
  862. }
  863. char fmt = ParseFormatSpecifier(format, out int digits);
  864. char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
  865. if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
  866. {
  867. return TryUInt64ToDecStr(value, digits, destination, out charsWritten);
  868. }
  869. else if (fmtUpper == 'X')
  870. {
  871. // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
  872. // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
  873. // produces lowercase.
  874. return TryInt64ToHexStr((long)value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
  875. }
  876. else
  877. {
  878. NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
  879. byte* pDigits = stackalloc byte[UInt64NumberBufferLength];
  880. NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt64NumberBufferLength);
  881. UInt64ToNumber(value, ref number);
  882. char* stackPtr = stackalloc char[CharStackBufferSize];
  883. ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
  884. if (fmt != 0)
  885. {
  886. NumberToString(ref sb, ref number, fmt, digits, info);
  887. }
  888. else
  889. {
  890. NumberToStringFormat(ref sb, ref number, format, info);
  891. }
  892. return sb.TryCopyTo(destination, out charsWritten);
  893. }
  894. }
  895. [MethodImpl(MethodImplOptions.AggressiveInlining)] // called from only one location
  896. private static unsafe void Int32ToNumber(int value, ref NumberBuffer number)
  897. {
  898. number.DigitsCount = Int32Precision;
  899. if (value >= 0)
  900. {
  901. number.IsNegative = false;
  902. }
  903. else
  904. {
  905. number.IsNegative = true;
  906. value = -value;
  907. }
  908. byte* buffer = number.GetDigitsPointer();
  909. byte* p = UInt32ToDecChars(buffer + Int32Precision, (uint)value, 0);
  910. int i = (int)(buffer + Int32Precision - p);
  911. number.DigitsCount = i;
  912. number.Scale = i;
  913. byte* dst = number.GetDigitsPointer();
  914. while (--i >= 0)
  915. *dst++ = *p++;
  916. *dst = (byte)('\0');
  917. number.CheckConsistency();
  918. }
  919. private static unsafe string NegativeInt32ToDecStr(int value, int digits, string sNegative)
  920. {
  921. Debug.Assert(value < 0);
  922. if (digits < 1)
  923. digits = 1;
  924. int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((uint)(-value))) + sNegative.Length;
  925. string result = string.FastAllocateString(bufferLength);
  926. fixed (char* buffer = result)
  927. {
  928. char* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits);
  929. Debug.Assert(p == buffer + sNegative.Length);
  930. for (int i = sNegative.Length - 1; i >= 0; i--)
  931. {
  932. *(--p) = sNegative[i];
  933. }
  934. Debug.Assert(p == buffer);
  935. }
  936. return result;
  937. }
  938. private static unsafe bool TryNegativeInt32ToDecStr(int value, int digits, string sNegative, Span<char> destination, out int charsWritten)
  939. {
  940. Debug.Assert(value < 0);
  941. if (digits < 1)
  942. digits = 1;
  943. int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((uint)(-value))) + sNegative.Length;
  944. if (bufferLength > destination.Length)
  945. {
  946. charsWritten = 0;
  947. return false;
  948. }
  949. charsWritten = bufferLength;
  950. fixed (char* buffer = &MemoryMarshal.GetReference(destination))
  951. {
  952. char* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits);
  953. Debug.Assert(p == buffer + sNegative.Length);
  954. for (int i = sNegative.Length - 1; i >= 0; i--)
  955. {
  956. *(--p) = sNegative[i];
  957. }
  958. Debug.Assert(p == buffer);
  959. }
  960. return true;
  961. }
  962. private static unsafe string Int32ToHexStr(int value, char hexBase, int digits)
  963. {
  964. if (digits < 1)
  965. digits = 1;
  966. int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((uint)value));
  967. string result = string.FastAllocateString(bufferLength);
  968. fixed (char* buffer = result)
  969. {
  970. char* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits);
  971. Debug.Assert(p == buffer);
  972. }
  973. return result;
  974. }
  975. private static unsafe bool TryInt32ToHexStr(int value, char hexBase, int digits, Span<char> destination, out int charsWritten)
  976. {
  977. if (digits < 1)
  978. digits = 1;
  979. int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((uint)value));
  980. if (bufferLength > destination.Length)
  981. {
  982. charsWritten = 0;
  983. return false;
  984. }
  985. charsWritten = bufferLength;
  986. fixed (char* buffer = &MemoryMarshal.GetReference(destination))
  987. {
  988. char* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits);
  989. Debug.Assert(p == buffer);
  990. }
  991. return true;
  992. }
  993. private static unsafe char* Int32ToHexChars(char* buffer, uint value, int hexBase, int digits)
  994. {
  995. while (--digits >= 0 || value != 0)
  996. {
  997. byte digit = (byte)(value & 0xF);
  998. *(--buffer) = (char)(digit + (digit < 10 ? (byte)'0' : hexBase));
  999. value >>= 4;
  1000. }
  1001. return buffer;
  1002. }
  1003. [MethodImpl(MethodImplOptions.AggressiveInlining)] // called from only one location
  1004. private static unsafe void UInt32ToNumber(uint value, ref NumberBuffer number)
  1005. {
  1006. number.DigitsCount = UInt32Precision;
  1007. number.IsNegative = false;
  1008. byte* buffer = number.GetDigitsPointer();
  1009. byte* p = UInt32ToDecChars(buffer + UInt32Precision, value, 0);
  1010. int i = (int)(buffer + UInt32Precision - p);
  1011. number.DigitsCount = i;
  1012. number.Scale = i;
  1013. byte* dst = number.GetDigitsPointer();
  1014. while (--i >= 0)
  1015. *dst++ = *p++;
  1016. *dst = (byte)('\0');
  1017. number.CheckConsistency();
  1018. }
  1019. internal static unsafe byte* UInt32ToDecChars(byte* bufferEnd, uint value, int digits)
  1020. {
  1021. while (--digits >= 0 || value != 0)
  1022. {
  1023. // TODO https://github.com/dotnet/coreclr/issues/3439
  1024. uint newValue = value / 10;
  1025. *(--bufferEnd) = (byte)(value - (newValue * 10) + '0');
  1026. value = newValue;
  1027. }
  1028. return bufferEnd;
  1029. }
  1030. internal static unsafe char* UInt32ToDecChars(char* bufferEnd, uint value, int digits)
  1031. {
  1032. while (--digits >= 0 || value != 0)
  1033. {
  1034. // TODO https://github.com/dotnet/coreclr/issues/3439
  1035. uint newValue = value / 10;
  1036. *(--bufferEnd) = (char)(value - (newValue * 10) + '0');
  1037. value = newValue;
  1038. }
  1039. return bufferEnd;
  1040. }
  1041. private static unsafe string UInt32ToDecStr(uint value, int digits)
  1042. {
  1043. int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value));
  1044. // For single-digit values that are very common, especially 0 and 1, just return cached strings.
  1045. if (bufferLength == 1)
  1046. {
  1047. return s_singleDigitStringCache[value];
  1048. }
  1049. string result = string.FastAllocateString(bufferLength);
  1050. fixed (char* buffer = result)
  1051. {
  1052. char* p = buffer + bufferLength;
  1053. if (digits <= 1)
  1054. {
  1055. do
  1056. {
  1057. // TODO https://github.com/dotnet/coreclr/issues/3439
  1058. uint div = value / 10;
  1059. *(--p) = (char)('0' + value - (div * 10));
  1060. value = div;
  1061. }
  1062. while (value != 0);
  1063. }
  1064. else
  1065. {
  1066. p = UInt32ToDecChars(p, value, digits);
  1067. }
  1068. Debug.Assert(p == buffer);
  1069. }
  1070. return result;
  1071. }
  1072. private static unsafe bool TryUInt32ToDecStr(uint value, int digits, Span<char> destination, out int charsWritten)
  1073. {
  1074. int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value));
  1075. if (bufferLength > destination.Length)
  1076. {
  1077. charsWritten = 0;
  1078. return false;
  1079. }
  1080. charsWritten = bufferLength;
  1081. fixed (char* buffer = &MemoryMarshal.GetReference(destination))
  1082. {
  1083. char* p = buffer + bufferLength;
  1084. if (digits <= 1)
  1085. {
  1086. do
  1087. {
  1088. // TODO https://github.com/dotnet/coreclr/issues/3439
  1089. uint div = value / 10;
  1090. *(--p) = (char)('0' + value - (div * 10));
  1091. value = div;
  1092. }
  1093. while (value != 0);
  1094. }
  1095. else
  1096. {
  1097. p = UInt32ToDecChars(p, value, digits);
  1098. }
  1099. Debug.Assert(p == buffer);
  1100. }
  1101. return true;
  1102. }
  1103. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  1104. private static unsafe bool TryCopyTo(char* src, int length, Span<char> destination, out int charsWritten)
  1105. {
  1106. if (new ReadOnlySpan<char>(src, length).TryCopyTo(destination))
  1107. {
  1108. charsWritten = length;
  1109. return true;
  1110. }
  1111. else
  1112. {
  1113. charsWritten = 0;
  1114. return false;
  1115. }
  1116. }
  1117. private static unsafe void Int64ToNumber(long input, ref NumberBuffer number)
  1118. {
  1119. ulong value = (ulong)input;
  1120. number.IsNegative = input < 0;
  1121. number.DigitsCount = Int64Precision;
  1122. if (number.IsNegative)
  1123. {
  1124. value = (ulong)(-input);
  1125. }
  1126. byte* buffer = number.GetDigitsPointer();
  1127. byte* p = buffer + Int64Precision;
  1128. while (High32(value) != 0)
  1129. p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
  1130. p = UInt32ToDecChars(p, Low32(value), 0);
  1131. int i = (int)(buffer + Int64Precision - p);
  1132. number.DigitsCount = i;
  1133. number.Scale = i;
  1134. byte* dst = number.GetDigitsPointer();
  1135. while (--i >= 0)
  1136. *dst++ = *p++;
  1137. *dst = (byte)('\0');
  1138. number.CheckConsistency();
  1139. }
  1140. private static unsafe string NegativeInt64ToDecStr(long input, int digits, string sNegative)
  1141. {
  1142. Debug.Assert(input < 0);
  1143. if (digits < 1)
  1144. {
  1145. digits = 1;
  1146. }
  1147. ulong value = (ulong)(-input);
  1148. int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value)) + sNegative.Length;
  1149. string result = string.FastAllocateString(bufferLength);
  1150. fixed (char* buffer = result)
  1151. {
  1152. char* p = buffer + bufferLength;
  1153. while (High32(value) != 0)
  1154. {
  1155. p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
  1156. digits -= 9;
  1157. }
  1158. p = UInt32ToDecChars(p, Low32(value), digits);
  1159. Debug.Assert(p == buffer + sNegative.Length);
  1160. for (int i = sNegative.Length - 1; i >= 0; i--)
  1161. {
  1162. *(--p) = sNegative[i];
  1163. }
  1164. Debug.Assert(p == buffer);
  1165. }
  1166. return result;
  1167. }
  1168. private static unsafe bool TryNegativeInt64ToDecStr(long input, int digits, string sNegative, Span<char> destination, out int charsWritten)
  1169. {
  1170. Debug.Assert(input < 0);
  1171. if (digits < 1)
  1172. {
  1173. digits = 1;
  1174. }
  1175. ulong value = (ulong)(-input);
  1176. int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((ulong)(-input))) + sNegative.Length;
  1177. if (bufferLength > destination.Length)
  1178. {
  1179. charsWritten = 0;
  1180. return false;
  1181. }
  1182. charsWritten = bufferLength;
  1183. fixed (char* buffer = &MemoryMarshal.GetReference(destination))
  1184. {
  1185. char* p = buffer + bufferLength;
  1186. while (High32(value) != 0)
  1187. {
  1188. p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
  1189. digits -= 9;
  1190. }
  1191. p = UInt32ToDecChars(p, Low32(value), digits);
  1192. Debug.Assert(p == buffer + sNegative.Length);
  1193. for (int i = sNegative.Length - 1; i >= 0; i--)
  1194. {
  1195. *(--p) = sNegative[i];
  1196. }
  1197. Debug.Assert(p == buffer);
  1198. }
  1199. return true;
  1200. }
  1201. private static unsafe string Int64ToHexStr(long value, char hexBase, int digits)
  1202. {
  1203. int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((ulong)value));
  1204. string result = string.FastAllocateString(bufferLength);
  1205. fixed (char* buffer = result)
  1206. {
  1207. char* p = buffer + bufferLength;
  1208. if (High32((ulong)value) != 0)
  1209. {
  1210. p = Int32ToHexChars(p, Low32((ulong)value), hexBase, 8);
  1211. p = Int32ToHexChars(p, High32((ulong)value), hexBase, digits - 8);
  1212. }
  1213. else
  1214. {
  1215. p = Int32ToHexChars(p, Low32((ulong)value), hexBase, Math.Max(digits, 1));
  1216. }
  1217. Debug.Assert(p == buffer);
  1218. }
  1219. return result;
  1220. }
  1221. private static unsafe bool TryInt64ToHexStr(long value, char hexBase, int digits, Span<char> destination, out int charsWritten)
  1222. {
  1223. int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((ulong)value));
  1224. if (bufferLength > destination.Length)
  1225. {
  1226. charsWritten = 0;
  1227. return false;
  1228. }
  1229. charsWritten = bufferLength;
  1230. fixed (char* buffer = &MemoryMarshal.GetReference(destination))
  1231. {
  1232. char* p = buffer + bufferLength;
  1233. if (High32((ulong)value) != 0)
  1234. {
  1235. p = Int32ToHexChars(p, Low32((ulong)value), hexBase, 8);
  1236. p = Int32ToHexChars(p, High32((ulong)value), hexBase, digits - 8);
  1237. }
  1238. else
  1239. {
  1240. p = Int32ToHexChars(p, Low32((ulong)value), hexBase, Math.Max(digits, 1));
  1241. }
  1242. Debug.Assert(p == buffer);
  1243. }
  1244. return true;
  1245. }
  1246. private static unsafe void UInt64ToNumber(ulong value, ref NumberBuffer number)
  1247. {
  1248. number.DigitsCount = UInt64Precision;
  1249. number.IsNegative = false;
  1250. byte* buffer = number.GetDigitsPointer();
  1251. byte* p = buffer + UInt64Precision;
  1252. while (High32(value) != 0)
  1253. p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
  1254. p = UInt32ToDecChars(p, Low32(value), 0);
  1255. int i = (int)(buffer + UInt64Precision - p);
  1256. number.DigitsCount = i;
  1257. number.Scale = i;
  1258. byte* dst = number.GetDigitsPointer();
  1259. while (--i >= 0)
  1260. *dst++ = *p++;
  1261. *dst = (byte)('\0');
  1262. number.CheckConsistency();
  1263. }
  1264. private static unsafe string UInt64ToDecStr(ulong value, int digits)
  1265. {
  1266. if (digits < 1)
  1267. digits = 1;
  1268. int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value));
  1269. // For single-digit values that are very common, especially 0 and 1, just return cached strings.
  1270. if (bufferLength == 1)
  1271. {
  1272. return s_singleDigitStringCache[value];
  1273. }
  1274. string result = string.FastAllocateString(bufferLength);
  1275. fixed (char* buffer = result)
  1276. {
  1277. char* p = buffer + bufferLength;
  1278. while (High32(value) != 0)
  1279. {
  1280. p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
  1281. digits -= 9;
  1282. }
  1283. p = UInt32ToDecChars(p, Low32(value), digits);
  1284. Debug.Assert(p == buffer);
  1285. }
  1286. return result;
  1287. }
  1288. private static unsafe bool TryUInt64ToDecStr(ulong value, int digits, Span<char> destination, out int charsWritten)
  1289. {
  1290. if (digits < 1)
  1291. digits = 1;
  1292. int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value));
  1293. if (bufferLength > destination.Length)
  1294. {
  1295. charsWritten = 0;
  1296. return false;
  1297. }
  1298. charsWritten = bufferLength;
  1299. fixed (char* buffer = &MemoryMarshal.GetReference(destination))
  1300. {
  1301. char* p = buffer + bufferLength;
  1302. while (High32(value) != 0)
  1303. {
  1304. p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
  1305. digits -= 9;
  1306. }
  1307. p = UInt32ToDecChars(p, Low32(value), digits);
  1308. Debug.Assert(p == buffer);
  1309. }
  1310. return true;
  1311. }
  1312. internal static unsafe char ParseFormatSpecifier(ReadOnlySpan<char> format, out int digits)
  1313. {
  1314. char c = default;
  1315. if (format.Length > 0)
  1316. {
  1317. // If the format begins with a symbol, see if it's a standard format
  1318. // with or without a specified number of digits.
  1319. c = format[0];
  1320. if ((uint)(c - 'A') <= 'Z' - 'A' ||
  1321. (uint)(c - 'a') <= 'z' - 'a')
  1322. {
  1323. // Fast path for sole symbol, e.g. "D"
  1324. if (format.Length == 1)
  1325. {
  1326. digits = -1;
  1327. return c;
  1328. }
  1329. if (format.Length == 2)
  1330. {
  1331. // Fast path for symbol and single digit, e.g. "X4"
  1332. int d = format[1] - '0';
  1333. if ((uint)d < 10)
  1334. {
  1335. digits = d;
  1336. return c;
  1337. }
  1338. }
  1339. else if (format.Length == 3)
  1340. {
  1341. // Fast path for symbol and double digit, e.g. "F12"
  1342. int d1 = format[1] - '0', d2 = format[2] - '0';
  1343. if ((uint)d1 < 10 && (uint)d2 < 10)
  1344. {
  1345. digits = d1 * 10 + d2;
  1346. return c;
  1347. }
  1348. }
  1349. // Fallback for symbol and any length digits. The digits value must be >= 0 && <= 99,
  1350. // but it can begin with any number of 0s, and thus we may need to check more than two
  1351. // digits. Further, for compat, we need to stop when we hit a null char.
  1352. int n = 0;
  1353. int i = 1;
  1354. while (i < format.Length && (((uint)format[i] - '0') < 10) && n < 10)
  1355. {
  1356. n = (n * 10) + format[i++] - '0';
  1357. }
  1358. // If we're at the end of the digits rather than having stopped because we hit something
  1359. // other than a digit or overflowed, return the standard format info.
  1360. if (i == format.Length || format[i] == '\0')
  1361. {
  1362. digits = n;
  1363. return c;
  1364. }
  1365. }
  1366. }
  1367. // Default empty format to be "G"; custom format is signified with '\0'.
  1368. digits = -1;
  1369. return format.Length == 0 || c == '\0' ? // For compat, treat '\0' as the end of the specifier, even if the specifier extends beyond it.
  1370. 'G' :
  1371. '\0';
  1372. }
  1373. internal static unsafe void NumberToString(ref ValueStringBuilder sb, ref NumberBuffer number, char format, int nMaxDigits, NumberFormatInfo info)
  1374. {
  1375. number.CheckConsistency();
  1376. switch (format)
  1377. {
  1378. case 'C':
  1379. case 'c':
  1380. {
  1381. if (nMaxDigits < 0)
  1382. nMaxDigits = info.CurrencyDecimalDigits;
  1383. RoundNumber(ref number, number.Scale + nMaxDigits); // Don't change this line to use digPos since digCount could have its sign changed.
  1384. FormatCurrency(ref sb, ref number, nMaxDigits, info);
  1385. break;
  1386. }
  1387. case 'F':
  1388. case 'f':
  1389. {
  1390. if (nMaxDigits < 0)
  1391. nMaxDigits = info.NumberDecimalDigits;
  1392. RoundNumber(ref number, number.Scale + nMaxDigits);
  1393. if (number.IsNegative)
  1394. sb.Append(info.NegativeSign);
  1395. FormatFixed(ref sb, ref number, nMaxDigits, info, null, info.NumberDecimalSeparator, null);
  1396. break;
  1397. }
  1398. case 'N':
  1399. case 'n':
  1400. {
  1401. if (nMaxDigits < 0)
  1402. nMaxDigits = info.NumberDecimalDigits; // Since we are using digits in our calculation
  1403. RoundNumber(ref number, number.Scale + nMaxDigits);
  1404. FormatNumber(ref sb, ref number, nMaxDigits, info);
  1405. break;
  1406. }
  1407. case 'E':
  1408. case 'e':
  1409. {
  1410. if (nMaxDigits < 0)
  1411. nMaxDigits = DefaultPrecisionExponentialFormat;
  1412. nMaxDigits++;
  1413. RoundNumber(ref number, nMaxDigits);
  1414. if (number.IsNegative)
  1415. sb.Append(info.NegativeSign);
  1416. FormatScientific(ref sb, ref number, nMaxDigits, info, format);
  1417. break;
  1418. }
  1419. case 'G':
  1420. case 'g':
  1421. {
  1422. bool noRounding = false;
  1423. if (nMaxDigits < 1)
  1424. {
  1425. if ((number.Kind == NumberBufferKind.Decimal) && (nMaxDigits == -1))
  1426. {
  1427. noRounding = true; // Turn off rounding for ECMA compliance to output trailing 0's after decimal as significant
  1428. if (number.Digits[0] == 0)
  1429. {
  1430. // -0 should be formatted as 0 for decimal. This is normally handled by RoundNumber (which we are skipping)
  1431. goto SkipSign;
  1432. }
  1433. goto SkipRounding;
  1434. }
  1435. else
  1436. {
  1437. // This ensures that the PAL code pads out to the correct place even when we use the default precision
  1438. nMaxDigits = number.DigitsCount;
  1439. }
  1440. }
  1441. RoundNumber(ref number, nMaxDigits);
  1442. SkipRounding:
  1443. if (number.IsNegative)
  1444. sb.Append(info.NegativeSign);
  1445. SkipSign:
  1446. FormatGeneral(ref sb, ref number, nMaxDigits, info, (char)(format - ('G' - 'E')), noRounding);
  1447. break;
  1448. }
  1449. case 'P':
  1450. case 'p':
  1451. {
  1452. if (nMaxDigits < 0)
  1453. nMaxDigits = info.PercentDecimalDigits;
  1454. number.Scale += 2;
  1455. RoundNumber(ref number, number.Scale + nMaxDigits);
  1456. FormatPercent(ref sb, ref number, nMaxDigits, info);
  1457. break;
  1458. }
  1459. case 'R':
  1460. case 'r':
  1461. {
  1462. if (number.Kind != NumberBufferKind.FloatingPoint)
  1463. {
  1464. goto default;
  1465. }
  1466. format = (char)(format - ('R' - 'G'));
  1467. Debug.Assert((format == 'G') || (format == 'g'));
  1468. goto case 'G';
  1469. }
  1470. default:
  1471. throw new FormatException(SR.Argument_BadFormatSpecifier);
  1472. }
  1473. }
  1474. internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref NumberBuffer number, ReadOnlySpan<char> format, NumberFormatInfo info)
  1475. {
  1476. number.CheckConsistency();
  1477. int digitCount;
  1478. int decimalPos;
  1479. int firstDigit;
  1480. int lastDigit;
  1481. int digPos;
  1482. bool scientific;
  1483. int thousandPos;
  1484. int thousandCount = 0;
  1485. bool thousandSeps;
  1486. int scaleAdjust;
  1487. int adjust;
  1488. int section;
  1489. int src;
  1490. byte* dig = number.GetDigitsPointer();
  1491. char ch;
  1492. section = FindSection(format, dig[0] == 0 ? 2 : number.IsNegative ? 1 : 0);
  1493. while (true)
  1494. {
  1495. digitCount = 0;
  1496. decimalPos = -1;
  1497. firstDigit = 0x7FFFFFFF;
  1498. lastDigit = 0;
  1499. scientific = false;
  1500. thousandPos = -1;
  1501. thousandSeps = false;
  1502. scaleAdjust = 0;
  1503. src = section;
  1504. fixed (char* pFormat = &MemoryMarshal.GetReference(format))
  1505. {
  1506. while (src < format.Length && (ch = pFormat[src++]) != 0 && ch != ';')
  1507. {
  1508. switch (ch)
  1509. {
  1510. case '#':
  1511. digitCount++;
  1512. break;
  1513. case '0':
  1514. if (firstDigit == 0x7FFFFFFF)
  1515. firstDigit = digitCount;
  1516. digitCount++;
  1517. lastDigit = digitCount;
  1518. break;
  1519. case '.':
  1520. if (decimalPos < 0)
  1521. decimalPos = digitCount;
  1522. break;
  1523. case ',':
  1524. if (digitCount > 0 && decimalPos < 0)
  1525. {
  1526. if (thousandPos >= 0)
  1527. {
  1528. if (thousandPos == digitCount)
  1529. {
  1530. thousandCount++;
  1531. break;
  1532. }
  1533. thousandSeps = true;
  1534. }
  1535. thousandPos = digitCount;
  1536. thousandCount = 1;
  1537. }
  1538. break;
  1539. case '%':
  1540. scaleAdjust += 2;
  1541. break;
  1542. case '\x2030':
  1543. scaleAdjust += 3;
  1544. break;
  1545. case '\'':
  1546. case '"':
  1547. while (src < format.Length && pFormat[src] != 0 && pFormat[src++] != ch)
  1548. ;
  1549. break;
  1550. case '\\':
  1551. if (src < format.Length && pFormat[src] != 0)
  1552. src++;
  1553. break;
  1554. case 'E':
  1555. case 'e':
  1556. if ((src < format.Length && pFormat[src] == '0') ||
  1557. (src + 1 < format.Length && (pFormat[src] == '+' || pFormat[src] == '-') && pFormat[src + 1] == '0'))
  1558. {
  1559. while (++src < format.Length && pFormat[src] == '0')
  1560. ;
  1561. scientific = true;
  1562. }
  1563. break;
  1564. }
  1565. }
  1566. }
  1567. if (decimalPos < 0)
  1568. decimalPos = digitCount;
  1569. if (thousandPos >= 0)
  1570. {
  1571. if (thousandPos == decimalPos)
  1572. scaleAdjust -= thousandCount * 3;
  1573. else
  1574. thousandSeps = true;
  1575. }
  1576. if (dig[0] != 0)
  1577. {
  1578. number.Scale += scaleAdjust;
  1579. int pos = scientific ? digitCount : number.Scale + digitCount - decimalPos;
  1580. RoundNumber(ref number, pos);
  1581. if (dig[0] == 0)
  1582. {
  1583. src = FindSection(format, 2);
  1584. if (src != section)
  1585. {
  1586. section = src;
  1587. continue;
  1588. }
  1589. }
  1590. }
  1591. else
  1592. {
  1593. if (number.Kind != NumberBufferKind.FloatingPoint)
  1594. {
  1595. // The integer types don't have a concept of -0 and decimal always format -0 as 0
  1596. number.IsNegative = false;
  1597. }
  1598. number.Scale = 0; // Decimals with scale ('0.00') should be rounded.
  1599. }
  1600. break;
  1601. }
  1602. firstDigit = firstDigit < decimalPos ? decimalPos - firstDigit : 0;
  1603. lastDigit = lastDigit > decimalPos ? decimalPos - lastDigit : 0;
  1604. if (scientific)
  1605. {
  1606. digPos = decimalPos;
  1607. adjust = 0;
  1608. }
  1609. else
  1610. {
  1611. digPos = number.Scale > decimalPos ? number.Scale : decimalPos;
  1612. adjust = number.Scale - decimalPos;
  1613. }
  1614. src = section;
  1615. // Adjust can be negative, so we make this an int instead of an unsigned int.
  1616. // Adjust represents the number of characters over the formatting e.g. format string is "0000" and you are trying to
  1617. // format 100000 (6 digits). Means adjust will be 2. On the other hand if you are trying to format 10 adjust will be
  1618. // -2 and we'll need to fixup these digits with 0 padding if we have 0 formatting as in this example.
  1619. Span<int> thousandsSepPos = stackalloc int[4];
  1620. int thousandsSepCtr = -1;
  1621. if (thousandSeps)
  1622. {
  1623. // We need to precompute this outside the number formatting loop
  1624. if (info.NumberGroupSeparator.Length > 0)
  1625. {
  1626. // We need this array to figure out where to insert the thousands separator. We would have to traverse the string
  1627. // backwards. PIC formatting always traverses forwards. These indices are precomputed to tell us where to insert
  1628. // the thousands separator so we can get away with traversing forwards. Note we only have to compute up to digPos.
  1629. // The max is not bound since you can have formatting strings of the form "000,000..", and this
  1630. // should handle that case too.
  1631. int[] groupDigits = info.numberGroupSizes;
  1632. int groupSizeIndex = 0; // Index into the groupDigits array.
  1633. int groupTotalSizeCount = 0;
  1634. int groupSizeLen = groupDigits.Length; // The length of groupDigits array.
  1635. if (groupSizeLen != 0)
  1636. groupTotalSizeCount = groupDigits[groupSizeIndex]; // The current running total of group size.
  1637. int groupSize = groupTotalSizeCount;
  1638. int totalDigits = digPos + ((adjust < 0) ? adjust : 0); // Actual number of digits in o/p
  1639. int numDigits = (firstDigit > totalDigits) ? firstDigit : totalDigits;
  1640. while (numDigits > groupTotalSizeCount)
  1641. {
  1642. if (groupSize == 0)
  1643. break;
  1644. ++thousandsSepCtr;
  1645. if (thousandsSepCtr >= thousandsSepPos.Length)
  1646. {
  1647. var newThousandsSepPos = new int[thousandsSepPos.Length * 2];
  1648. thousandsSepPos.CopyTo(newThousandsSepPos);
  1649. thousandsSepPos = newThousandsSepPos;
  1650. }
  1651. thousandsSepPos[thousandsSepCtr] = groupTotalSizeCount;
  1652. if (groupSizeIndex < groupSizeLen - 1)
  1653. {
  1654. groupSizeIndex++;
  1655. groupSize = groupDigits[groupSizeIndex];
  1656. }
  1657. groupTotalSizeCount += groupSize;
  1658. }
  1659. }
  1660. }
  1661. if (number.IsNegative && (section == 0) && (number.Scale != 0))
  1662. sb.Append(info.NegativeSign);
  1663. bool decimalWritten = false;
  1664. fixed (char* pFormat = &MemoryMarshal.GetReference(format))
  1665. {
  1666. byte* cur = dig;
  1667. while (src < format.Length && (ch = pFormat[src++]) != 0 && ch != ';')
  1668. {
  1669. if (adjust > 0)
  1670. {
  1671. switch (ch)
  1672. {
  1673. case '#':
  1674. case '0':
  1675. case '.':
  1676. while (adjust > 0)
  1677. {
  1678. // digPos will be one greater than thousandsSepPos[thousandsSepCtr] since we are at
  1679. // the character after which the groupSeparator needs to be appended.
  1680. sb.Append(*cur != 0 ? (char)(*cur++) : '0');
  1681. if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0)
  1682. {
  1683. if (digPos == thousandsSepPos[thousandsSepCtr] + 1)
  1684. {
  1685. sb.Append(info.NumberGroupSeparator);
  1686. thousandsSepCtr--;
  1687. }
  1688. }
  1689. digPos--;
  1690. adjust--;
  1691. }
  1692. break;
  1693. }
  1694. }
  1695. switch (ch)
  1696. {
  1697. case '#':
  1698. case '0':
  1699. {
  1700. if (adjust < 0)
  1701. {
  1702. adjust++;
  1703. ch = digPos <= firstDigit ? '0' : '\0';
  1704. }
  1705. else
  1706. {
  1707. ch = *cur != 0 ? (char)(*cur++) : digPos > lastDigit ? '0' : '\0';
  1708. }
  1709. if (ch != 0)
  1710. {
  1711. sb.Append(ch);
  1712. if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0)
  1713. {
  1714. if (digPos == thousandsSepPos[thousandsSepCtr] + 1)
  1715. {
  1716. sb.Append(info.NumberGroupSeparator);
  1717. thousandsSepCtr--;
  1718. }
  1719. }
  1720. }
  1721. digPos--;
  1722. break;
  1723. }
  1724. case '.':
  1725. {
  1726. if (digPos != 0 || decimalWritten)
  1727. {
  1728. // For compatibility, don't echo repeated decimals
  1729. break;
  1730. }
  1731. // If the format has trailing zeros or the format has a decimal and digits remain
  1732. if (lastDigit < 0 || (decimalPos < digitCount && *cur != 0))
  1733. {
  1734. sb.Append(info.NumberDecimalSeparator);
  1735. decimalWritten = true;
  1736. }
  1737. break;
  1738. }
  1739. case '\x2030':
  1740. sb.Append(info.PerMilleSymbol);
  1741. break;
  1742. case '%':
  1743. sb.Append(info.PercentSymbol);
  1744. break;
  1745. case ',':
  1746. break;
  1747. case '\'':
  1748. case '"':
  1749. while (src < format.Length && pFormat[src] != 0 && pFormat[src] != ch)
  1750. sb.Append(pFormat[src++]);
  1751. if (src < format.Length && pFormat[src] != 0)
  1752. src++;
  1753. break;
  1754. case '\\':
  1755. if (src < format.Length && pFormat[src] != 0)
  1756. sb.Append(pFormat[src++]);
  1757. break;
  1758. case 'E':
  1759. case 'e':
  1760. {
  1761. bool positiveSign = false;
  1762. int i = 0;
  1763. if (scientific)
  1764. {
  1765. if (src < format.Length && pFormat[src] == '0')
  1766. {
  1767. // Handles E0, which should format the same as E-0
  1768. i++;
  1769. }
  1770. else if (src + 1 < format.Length && pFormat[src] == '+' && pFormat[src + 1] == '0')
  1771. {
  1772. // Handles E+0
  1773. positiveSign = true;
  1774. }
  1775. else if (src + 1 < format.Length && pFormat[src] == '-' && pFormat[src + 1] == '0')
  1776. {
  1777. // Handles E-0
  1778. // Do nothing, this is just a place holder s.t. we don't break out of the loop.
  1779. }
  1780. else
  1781. {
  1782. sb.Append(ch);
  1783. break;
  1784. }
  1785. while (++src < format.Length && pFormat[src] == '0')
  1786. i++;
  1787. if (i > 10)
  1788. i = 10;
  1789. int exp = dig[0] == 0 ? 0 : number.Scale - decimalPos;
  1790. FormatExponent(ref sb, info, exp, ch, i, positiveSign);
  1791. scientific = false;
  1792. }
  1793. else
  1794. {
  1795. sb.Append(ch); // Copy E or e to output
  1796. if (src < format.Length)
  1797. {
  1798. if (pFormat[src] == '+' || pFormat[src] == '-')
  1799. sb.Append(pFormat[src++]);
  1800. while (src < format.Length && pFormat[src] == '0')
  1801. sb.Append(pFormat[src++]);
  1802. }
  1803. }
  1804. break;
  1805. }
  1806. default:
  1807. sb.Append(ch);
  1808. break;
  1809. }
  1810. }
  1811. }
  1812. if (number.IsNegative && (section == 0) && (number.Scale == 0) && (sb.Length > 0))
  1813. sb.Insert(0, info.NegativeSign);
  1814. }
  1815. private static void FormatCurrency(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info)
  1816. {
  1817. string fmt = number.IsNegative ?
  1818. s_negCurrencyFormats[info.CurrencyNegativePattern] :
  1819. s_posCurrencyFormats[info.CurrencyPositivePattern];
  1820. foreach (char ch in fmt)
  1821. {
  1822. switch (ch)
  1823. {
  1824. case '#':
  1825. FormatFixed(ref sb, ref number, nMaxDigits, info, info.currencyGroupSizes, info.CurrencyDecimalSeparator, info.CurrencyGroupSeparator);
  1826. break;
  1827. case '-':
  1828. sb.Append(info.NegativeSign);
  1829. break;
  1830. case '$':
  1831. sb.Append(info.CurrencySymbol);
  1832. break;
  1833. default:
  1834. sb.Append(ch);
  1835. break;
  1836. }
  1837. }
  1838. }
  1839. private static unsafe void FormatFixed(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, int[] groupDigits, string sDecimal, string sGroup)
  1840. {
  1841. int digPos = number.Scale;
  1842. byte* dig = number.GetDigitsPointer();
  1843. if (digPos > 0)
  1844. {
  1845. if (groupDigits != null)
  1846. {
  1847. int groupSizeIndex = 0; // Index into the groupDigits array.
  1848. int bufferSize = digPos; // The length of the result buffer string.
  1849. int groupSize = 0; // The current group size.
  1850. // Find out the size of the string buffer for the result.
  1851. if (groupDigits.Length != 0) // You can pass in 0 length arrays
  1852. {
  1853. int groupSizeCount = groupDigits[groupSizeIndex]; // The current total of group size.
  1854. while (digPos > groupSizeCount)
  1855. {
  1856. groupSize = groupDigits[groupSizeIndex];
  1857. if (groupSize == 0)
  1858. break;
  1859. bufferSize += sGroup.Length;
  1860. if (groupSizeIndex < groupDigits.Length - 1)
  1861. groupSizeIndex++;
  1862. groupSizeCount += groupDigits[groupSizeIndex];
  1863. if (groupSizeCount < 0 || bufferSize < 0)
  1864. throw new ArgumentOutOfRangeException(); // If we overflow
  1865. }
  1866. groupSize = groupSizeCount == 0 ? 0 : groupDigits[0]; // If you passed in an array with one entry as 0, groupSizeCount == 0
  1867. }
  1868. groupSizeIndex = 0;
  1869. int digitCount = 0;
  1870. int digLength = number.DigitsCount;
  1871. int digStart = (digPos < digLength) ? digPos : digLength;
  1872. fixed (char* spanPtr = &MemoryMarshal.GetReference(sb.AppendSpan(bufferSize)))
  1873. {
  1874. char* p = spanPtr + bufferSize - 1;
  1875. for (int i = digPos - 1; i >= 0; i--)
  1876. {
  1877. *(p--) = (i < digStart) ? (char)(dig[i]) : '0';
  1878. if (groupSize > 0)
  1879. {
  1880. digitCount++;
  1881. if ((digitCount == groupSize) && (i != 0))
  1882. {
  1883. for (int j = sGroup.Length - 1; j >= 0; j--)
  1884. *(p--) = sGroup[j];
  1885. if (groupSizeIndex < groupDigits.Length - 1)
  1886. {
  1887. groupSizeIndex++;
  1888. groupSize = groupDigits[groupSizeIndex];
  1889. }
  1890. digitCount = 0;
  1891. }
  1892. }
  1893. }
  1894. Debug.Assert(p >= spanPtr - 1, "Underflow");
  1895. dig += digStart;
  1896. }
  1897. }
  1898. else
  1899. {
  1900. do
  1901. {
  1902. sb.Append(*dig != 0 ? (char)(*dig++) : '0');
  1903. }
  1904. while (--digPos > 0);
  1905. }
  1906. }
  1907. else
  1908. {
  1909. sb.Append('0');
  1910. }
  1911. if (nMaxDigits > 0)
  1912. {
  1913. sb.Append(sDecimal);
  1914. if ((digPos < 0) && (nMaxDigits > 0))
  1915. {
  1916. int zeroes = Math.Min(-digPos, nMaxDigits);
  1917. sb.Append('0', zeroes);
  1918. digPos += zeroes;
  1919. nMaxDigits -= zeroes;
  1920. }
  1921. while (nMaxDigits > 0)
  1922. {
  1923. sb.Append((*dig != 0) ? (char)(*dig++) : '0');
  1924. nMaxDigits--;
  1925. }
  1926. }
  1927. }
  1928. private static void FormatNumber(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info)
  1929. {
  1930. string fmt = number.IsNegative ?
  1931. s_negNumberFormats[info.NumberNegativePattern] :
  1932. PosNumberFormat;
  1933. foreach (char ch in fmt)
  1934. {
  1935. switch (ch)
  1936. {
  1937. case '#':
  1938. FormatFixed(ref sb, ref number, nMaxDigits, info, info.numberGroupSizes, info.NumberDecimalSeparator, info.NumberGroupSeparator);
  1939. break;
  1940. case '-':
  1941. sb.Append(info.NegativeSign);
  1942. break;
  1943. default:
  1944. sb.Append(ch);
  1945. break;
  1946. }
  1947. }
  1948. }
  1949. private static unsafe void FormatScientific(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, char expChar)
  1950. {
  1951. byte* dig = number.GetDigitsPointer();
  1952. sb.Append((*dig != 0) ? (char)(*dig++) : '0');
  1953. if (nMaxDigits != 1) // For E0 we would like to suppress the decimal point
  1954. sb.Append(info.NumberDecimalSeparator);
  1955. while (--nMaxDigits > 0)
  1956. sb.Append((*dig != 0) ? (char)(*dig++) : '0');
  1957. int e = number.Digits[0] == 0 ? 0 : number.Scale - 1;
  1958. FormatExponent(ref sb, info, e, expChar, 3, true);
  1959. }
  1960. private static unsafe void FormatExponent(ref ValueStringBuilder sb, NumberFormatInfo info, int value, char expChar, int minDigits, bool positiveSign)
  1961. {
  1962. sb.Append(expChar);
  1963. if (value < 0)
  1964. {
  1965. sb.Append(info.NegativeSign);
  1966. value = -value;
  1967. }
  1968. else
  1969. {
  1970. if (positiveSign)
  1971. sb.Append(info.PositiveSign);
  1972. }
  1973. char* digits = stackalloc char[MaxUInt32DecDigits];
  1974. char* p = UInt32ToDecChars(digits + MaxUInt32DecDigits, (uint)value, minDigits);
  1975. int i = (int)(digits + MaxUInt32DecDigits - p);
  1976. sb.Append(p, (int)(digits + MaxUInt32DecDigits - p));
  1977. }
  1978. private static unsafe void FormatGeneral(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, char expChar, bool bSuppressScientific)
  1979. {
  1980. int digPos = number.Scale;
  1981. bool scientific = false;
  1982. if (!bSuppressScientific)
  1983. {
  1984. // Don't switch to scientific notation
  1985. if (digPos > nMaxDigits || digPos < -3)
  1986. {
  1987. digPos = 1;
  1988. scientific = true;
  1989. }
  1990. }
  1991. byte* dig = number.GetDigitsPointer();
  1992. if (digPos > 0)
  1993. {
  1994. do
  1995. {
  1996. sb.Append((*dig != 0) ? (char)(*dig++) : '0');
  1997. } while (--digPos > 0);
  1998. }
  1999. else
  2000. {
  2001. sb.Append('0');
  2002. }
  2003. if (*dig != 0 || digPos < 0)
  2004. {
  2005. sb.Append(info.NumberDecimalSeparator);
  2006. while (digPos < 0)
  2007. {
  2008. sb.Append('0');
  2009. digPos++;
  2010. }
  2011. while (*dig != 0)
  2012. sb.Append((char)(*dig++));
  2013. }
  2014. if (scientific)
  2015. FormatExponent(ref sb, info, number.Scale - 1, expChar, 2, true);
  2016. }
  2017. private static void FormatPercent(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info)
  2018. {
  2019. string fmt = number.IsNegative ?
  2020. s_negPercentFormats[info.PercentNegativePattern] :
  2021. s_posPercentFormats[info.PercentPositivePattern];
  2022. foreach (char ch in fmt)
  2023. {
  2024. switch (ch)
  2025. {
  2026. case '#':
  2027. FormatFixed(ref sb, ref number, nMaxDigits, info, info.percentGroupSizes, info.PercentDecimalSeparator, info.PercentGroupSeparator);
  2028. break;
  2029. case '-':
  2030. sb.Append(info.NegativeSign);
  2031. break;
  2032. case '%':
  2033. sb.Append(info.PercentSymbol);
  2034. break;
  2035. default:
  2036. sb.Append(ch);
  2037. break;
  2038. }
  2039. }
  2040. }
  2041. internal static unsafe void RoundNumber(ref NumberBuffer number, int pos)
  2042. {
  2043. byte* dig = number.GetDigitsPointer();
  2044. int i = 0;
  2045. while (i < pos && dig[i] != 0)
  2046. i++;
  2047. if (i == pos && dig[i] >= '5')
  2048. {
  2049. while (i > 0 && dig[i - 1] == '9')
  2050. i--;
  2051. if (i > 0)
  2052. {
  2053. dig[i - 1]++;
  2054. }
  2055. else
  2056. {
  2057. number.Scale++;
  2058. dig[0] = (byte)('1');
  2059. i = 1;
  2060. }
  2061. }
  2062. else
  2063. {
  2064. while (i > 0 && dig[i - 1] == '0')
  2065. i--;
  2066. }
  2067. if (i == 0)
  2068. {
  2069. if (number.Kind != NumberBufferKind.FloatingPoint)
  2070. {
  2071. // The integer types don't have a concept of -0 and decimal always format -0 as 0
  2072. number.IsNegative = false;
  2073. }
  2074. number.Scale = 0; // Decimals with scale ('0.00') should be rounded.
  2075. }
  2076. dig[i] = (byte)('\0');
  2077. number.DigitsCount = i;
  2078. number.CheckConsistency();
  2079. }
  2080. private static unsafe int FindSection(ReadOnlySpan<char> format, int section)
  2081. {
  2082. int src;
  2083. char ch;
  2084. if (section == 0)
  2085. return 0;
  2086. fixed (char* pFormat = &MemoryMarshal.GetReference(format))
  2087. {
  2088. src = 0;
  2089. for (; ; )
  2090. {
  2091. if (src >= format.Length)
  2092. {
  2093. return 0;
  2094. }
  2095. switch (ch = pFormat[src++])
  2096. {
  2097. case '\'':
  2098. case '"':
  2099. while (src < format.Length && pFormat[src] != 0 && pFormat[src++] != ch)
  2100. ;
  2101. break;
  2102. case '\\':
  2103. if (src < format.Length && pFormat[src] != 0)
  2104. src++;
  2105. break;
  2106. case ';':
  2107. if (--section != 0)
  2108. break;
  2109. if (src < format.Length && pFormat[src] != 0 && pFormat[src] != ';')
  2110. return src;
  2111. goto case '\0';
  2112. case '\0':
  2113. return 0;
  2114. }
  2115. }
  2116. }
  2117. }
  2118. private static uint Low32(ulong value) => (uint)value;
  2119. private static uint High32(ulong value) => (uint)((value & 0xFFFFFFFF00000000) >> 32);
  2120. private static uint Int64DivMod1E9(ref ulong value)
  2121. {
  2122. uint rem = (uint)(value % 1000000000);
  2123. value /= 1000000000;
  2124. return rem;
  2125. }
  2126. private static ulong ExtractFractionAndBiasedExponent(double value, out int exponent)
  2127. {
  2128. ulong bits = (ulong)(BitConverter.DoubleToInt64Bits(value));
  2129. ulong fraction = (bits & 0xFFFFFFFFFFFFF);
  2130. exponent = ((int)(bits >> 52) & 0x7FF);
  2131. if (exponent != 0)
  2132. {
  2133. // For normalized value, according to https://en.wikipedia.org/wiki/Double-precision_floating-point_format
  2134. // value = 1.fraction * 2^(exp - 1023)
  2135. // = (1 + mantissa / 2^52) * 2^(exp - 1023)
  2136. // = (2^52 + mantissa) * 2^(exp - 1023 - 52)
  2137. //
  2138. // So f = (2^52 + mantissa), e = exp - 1075;
  2139. fraction |= (1UL << 52);
  2140. exponent -= 1075;
  2141. }
  2142. else
  2143. {
  2144. // For denormalized value, according to https://en.wikipedia.org/wiki/Double-precision_floating-point_format
  2145. // value = 0.fraction * 2^(1 - 1023)
  2146. // = (mantissa / 2^52) * 2^(-1022)
  2147. // = mantissa * 2^(-1022 - 52)
  2148. // = mantissa * 2^(-1074)
  2149. // So f = mantissa, e = -1074
  2150. exponent = -1074;
  2151. }
  2152. return fraction;
  2153. }
  2154. private static uint ExtractFractionAndBiasedExponent(float value, out int exponent)
  2155. {
  2156. uint bits = (uint)(BitConverter.SingleToInt32Bits(value));
  2157. uint fraction = (bits & 0x7FFFFF);
  2158. exponent = ((int)(bits >> 23) & 0xFF);
  2159. if (exponent != 0)
  2160. {
  2161. // For normalized value, according to https://en.wikipedia.org/wiki/Single-precision_floating-point_format
  2162. // value = 1.fraction * 2^(exp - 127)
  2163. // = (1 + mantissa / 2^23) * 2^(exp - 127)
  2164. // = (2^23 + mantissa) * 2^(exp - 127 - 23)
  2165. //
  2166. // So f = (2^23 + mantissa), e = exp - 150;
  2167. fraction |= (1U << 23);
  2168. exponent -= 150;
  2169. }
  2170. else
  2171. {
  2172. // For denormalized value, according to https://en.wikipedia.org/wiki/Single-precision_floating-point_format
  2173. // value = 0.fraction * 2^(1 - 127)
  2174. // = (mantissa / 2^23) * 2^(-126)
  2175. // = mantissa * 2^(-126 - 23)
  2176. // = mantissa * 2^(-149)
  2177. // So f = mantissa, e = -149
  2178. exponent = -149;
  2179. }
  2180. return fraction;
  2181. }
  2182. }
  2183. }