Number.Formatting.cs 105 KB

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