More Corelib cleanup (dotnet/coreclr#26872)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Globalization / NumberFormatInfo.cs
blobd5fa16edcdcb3d59d944fc635f0dac0e27b9b6f0
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.
5 namespace System.Globalization
7 /// <remarks>
8 /// Property Default Description
9 /// PositiveSign '+' Character used to indicate positive values.
10 /// NegativeSign '-' Character used to indicate negative values.
11 /// NumberDecimalSeparator '.' The character used as the decimal separator.
12 /// NumberGroupSeparator ',' The character used to separate groups of
13 /// digits to the left of the decimal point.
14 /// NumberDecimalDigits 2 The default number of decimal places.
15 /// NumberGroupSizes 3 The number of digits in each group to the
16 /// left of the decimal point.
17 /// NaNSymbol "NaN" The string used to represent NaN values.
18 /// PositiveInfinitySymbol"Infinity" The string used to represent positive
19 /// infinities.
20 /// NegativeInfinitySymbol"-Infinity" The string used to represent negative
21 /// infinities.
22 ///
23 /// Property Default Description
24 /// CurrencyDecimalSeparator '.' The character used as the decimal
25 /// separator.
26 /// CurrencyGroupSeparator ',' The character used to separate groups
27 /// of digits to the left of the decimal
28 /// point.
29 /// CurrencyDecimalDigits 2 The default number of decimal places.
30 /// CurrencyGroupSizes 3 The number of digits in each group to
31 /// the left of the decimal point.
32 /// CurrencyPositivePattern 0 The format of positive values.
33 /// CurrencyNegativePattern 0 The format of negative values.
34 /// CurrencySymbol "$" String used as local monetary symbol.
35 /// </remarks>
36 public sealed class NumberFormatInfo : IFormatProvider, ICloneable
38 private static volatile NumberFormatInfo? s_invariantInfo;
40 internal int[] _numberGroupSizes = new int[] { 3 };
41 internal int[] _currencyGroupSizes = new int[] { 3 };
42 internal int[] _percentGroupSizes = new int[] { 3 };
43 internal string _positiveSign = "+";
44 internal string _negativeSign = "-";
45 internal string _numberDecimalSeparator = ".";
46 internal string _numberGroupSeparator = ",";
47 internal string _currencyGroupSeparator = ",";
48 internal string _currencyDecimalSeparator = ".";
49 internal string _currencySymbol = "\x00a4"; // U+00a4 is the symbol for International Monetary Fund.
50 internal string _nanSymbol = "NaN";
51 internal string _positiveInfinitySymbol = "Infinity";
52 internal string _negativeInfinitySymbol = "-Infinity";
53 internal string _percentDecimalSeparator = ".";
54 internal string _percentGroupSeparator = ",";
55 internal string _percentSymbol = "%";
56 internal string _perMilleSymbol = "\u2030";
58 internal string[] _nativeDigits = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
60 internal int _numberDecimalDigits = 2;
61 internal int _currencyDecimalDigits = 2;
62 internal int _currencyPositivePattern = 0;
63 internal int _currencyNegativePattern = 0;
64 internal int _numberNegativePattern = 1;
65 internal int _percentPositivePattern = 0;
66 internal int _percentNegativePattern = 0;
67 internal int _percentDecimalDigits = 2;
69 internal int _digitSubstitution = (int)DigitShapes.None;
71 internal bool _isReadOnly = false;
73 private bool _hasInvariantNumberSigns = true;
75 public NumberFormatInfo()
79 private static void VerifyDecimalSeparator(string decSep, string propertyName)
81 if (decSep == null)
83 throw new ArgumentNullException(propertyName);
86 if (decSep.Length == 0)
88 throw new ArgumentException(SR.Argument_EmptyDecString, propertyName);
92 private static void VerifyGroupSeparator(string groupSep, string propertyName)
94 if (groupSep == null)
96 throw new ArgumentNullException(propertyName);
100 private static void VerifyNativeDigits(string[] nativeDig, string propertyName)
102 if (nativeDig == null)
104 throw new ArgumentNullException(propertyName, SR.ArgumentNull_Array);
107 if (nativeDig.Length != 10)
109 throw new ArgumentException(SR.Argument_InvalidNativeDigitCount, propertyName);
112 for (int i = 0; i < nativeDig.Length; i++)
114 if (nativeDig[i] == null)
116 throw new ArgumentNullException(propertyName, SR.ArgumentNull_ArrayValue);
119 if (nativeDig[i].Length != 1)
121 if (nativeDig[i].Length != 2)
123 // Not 1 or 2 UTF-16 code points
124 throw new ArgumentException(SR.Argument_InvalidNativeDigitValue, propertyName);
126 else if (!char.IsSurrogatePair(nativeDig[i][0], nativeDig[i][1]))
128 // 2 UTF-6 code points, but not a surrogate pair
129 throw new ArgumentException(SR.Argument_InvalidNativeDigitValue, propertyName);
133 if (CharUnicodeInfo.GetDecimalDigitValue(nativeDig[i], 0) != i &&
134 CharUnicodeInfo.GetUnicodeCategory(nativeDig[i], 0) != UnicodeCategory.PrivateUse)
136 // Not the appropriate digit according to the Unicode data properties
137 // (Digit 0 must be a 0, etc.).
138 throw new ArgumentException(SR.Argument_InvalidNativeDigitValue, propertyName);
143 private static void VerifyDigitSubstitution(DigitShapes digitSub, string propertyName)
145 switch (digitSub)
147 case DigitShapes.Context:
148 case DigitShapes.None:
149 case DigitShapes.NativeNational:
150 // Success.
151 break;
153 default:
154 throw new ArgumentException(SR.Argument_InvalidDigitSubstitution, propertyName);
158 internal bool HasInvariantNumberSigns => _hasInvariantNumberSigns;
160 private void UpdateHasInvariantNumberSigns()
162 _hasInvariantNumberSigns = _positiveSign == "+" && _negativeSign == "-";
165 internal NumberFormatInfo(CultureData? cultureData)
167 if (cultureData != null)
169 // We directly use fields here since these data is coming from data table or Win32, so we
170 // don't need to verify their values (except for invalid parsing situations).
171 cultureData.GetNFIValues(this);
173 UpdateHasInvariantNumberSigns();
177 private void VerifyWritable()
179 if (_isReadOnly)
181 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
185 /// <summary>
186 /// Returns a default NumberFormatInfo that will be universally
187 /// supported and constant irrespective of the current culture.
188 /// Used by FromString methods.
189 /// </summary>
190 public static NumberFormatInfo InvariantInfo => s_invariantInfo ??=
191 // Lazy create the invariant info. This cannot be done in a .cctor because exceptions can
192 // be thrown out of a .cctor stack that will need this.
193 new NumberFormatInfo { _isReadOnly = true };
195 public static NumberFormatInfo GetInstance(IFormatProvider? formatProvider)
197 return formatProvider == null ?
198 CurrentInfo : // Fast path for a null provider
199 GetProviderNonNull(formatProvider);
201 static NumberFormatInfo GetProviderNonNull(IFormatProvider provider)
203 // Fast path for a regular CultureInfo
204 if (provider is CultureInfo cultureProvider && !cultureProvider._isInherited)
206 return cultureProvider._numInfo ?? cultureProvider.NumberFormat;
209 return
210 provider as NumberFormatInfo ?? // Fast path for an NFI
211 provider.GetFormat(typeof(NumberFormatInfo)) as NumberFormatInfo ??
212 CurrentInfo;
216 public object Clone()
218 NumberFormatInfo n = (NumberFormatInfo)MemberwiseClone();
219 n._isReadOnly = false;
220 return n;
223 public int CurrencyDecimalDigits
225 get => _currencyDecimalDigits;
228 if (value < 0 || value > 99)
230 throw new ArgumentOutOfRangeException(
231 nameof(value),
232 value,
233 SR.Format(SR.ArgumentOutOfRange_Range, 0, 99));
236 VerifyWritable();
237 _currencyDecimalDigits = value;
241 public string CurrencyDecimalSeparator
243 get => _currencyDecimalSeparator;
246 VerifyWritable();
247 VerifyDecimalSeparator(value, nameof(value));
248 _currencyDecimalSeparator = value;
252 public bool IsReadOnly => _isReadOnly;
254 /// <summary>
255 /// Check the values of the groupSize array.
256 /// Every element in the groupSize array should be between 1 and 9
257 /// except the last element could be zero.
258 /// </summary>
259 internal static void CheckGroupSize(string propName, int[] groupSize)
261 for (int i = 0; i < groupSize.Length; i++)
263 if (groupSize[i] < 1)
265 if (i == groupSize.Length - 1 && groupSize[i] == 0)
267 return;
270 throw new ArgumentException(SR.Argument_InvalidGroupSize, propName);
272 else if (groupSize[i] > 9)
274 throw new ArgumentException(SR.Argument_InvalidGroupSize, propName);
279 public int[] CurrencyGroupSizes
281 get => (int[])_currencyGroupSizes.Clone();
284 if (value == null)
286 throw new ArgumentNullException(nameof(value));
289 VerifyWritable();
291 int[] inputSizes = (int[])value.Clone();
292 CheckGroupSize(nameof(value), inputSizes);
293 _currencyGroupSizes = inputSizes;
297 public int[] NumberGroupSizes
299 get => (int[])_numberGroupSizes.Clone();
302 if (value == null)
304 throw new ArgumentNullException(nameof(value));
307 VerifyWritable();
309 int[] inputSizes = (int[])value.Clone();
310 CheckGroupSize(nameof(value), inputSizes);
311 _numberGroupSizes = inputSizes;
315 public int[] PercentGroupSizes
317 get => (int[])_percentGroupSizes.Clone();
320 if (value == null)
322 throw new ArgumentNullException(nameof(value));
325 VerifyWritable();
326 int[] inputSizes = (int[])value.Clone();
327 CheckGroupSize(nameof(value), inputSizes);
328 _percentGroupSizes = inputSizes;
332 public string CurrencyGroupSeparator
334 get => _currencyGroupSeparator;
337 VerifyWritable();
338 VerifyGroupSeparator(value, nameof(value));
339 _currencyGroupSeparator = value;
343 public string CurrencySymbol
345 get => _currencySymbol;
348 if (value == null)
350 throw new ArgumentNullException(nameof(value));
353 VerifyWritable();
354 _currencySymbol = value;
358 /// <summary>
359 /// Returns the current culture's NumberFormatInfo. Used by Parse methods.
360 /// </summary>
362 public static NumberFormatInfo CurrentInfo
366 System.Globalization.CultureInfo culture = CultureInfo.CurrentCulture;
367 if (!culture._isInherited)
369 NumberFormatInfo? info = culture._numInfo;
370 if (info != null)
372 return info;
375 // returns non-nullable when passed typeof(NumberFormatInfo)
376 return (NumberFormatInfo)culture.GetFormat(typeof(NumberFormatInfo))!;
380 public string NaNSymbol
382 get => _nanSymbol;
385 if (value == null)
387 throw new ArgumentNullException(nameof(value));
390 VerifyWritable();
391 _nanSymbol = value;
395 public int CurrencyNegativePattern
397 get => _currencyNegativePattern;
400 if (value < 0 || value > 15)
402 throw new ArgumentOutOfRangeException(
403 nameof(value),
404 value,
405 SR.Format(SR.ArgumentOutOfRange_Range, 0, 15));
408 VerifyWritable();
409 _currencyNegativePattern = value;
413 public int NumberNegativePattern
415 get => _numberNegativePattern;
418 // NOTENOTE: the range of value should correspond to negNumberFormats[] in vm\COMNumber.cpp.
419 if (value < 0 || value > 4)
421 throw new ArgumentOutOfRangeException(
422 nameof(value),
423 value,
424 SR.Format(SR.ArgumentOutOfRange_Range, 0, 4));
427 VerifyWritable();
428 _numberNegativePattern = value;
432 public int PercentPositivePattern
434 get => _percentPositivePattern;
437 // NOTENOTE: the range of value should correspond to posPercentFormats[] in vm\COMNumber.cpp.
438 if (value < 0 || value > 3)
440 throw new ArgumentOutOfRangeException(
441 nameof(value),
442 value,
443 SR.Format(SR.ArgumentOutOfRange_Range, 0, 3));
446 VerifyWritable();
447 _percentPositivePattern = value;
451 public int PercentNegativePattern
453 get => _percentNegativePattern;
456 // NOTENOTE: the range of value should correspond to posPercentFormats[] in vm\COMNumber.cpp.
457 if (value < 0 || value > 11)
459 throw new ArgumentOutOfRangeException(
460 nameof(value),
461 value,
462 SR.Format(SR.ArgumentOutOfRange_Range, 0, 11));
465 VerifyWritable();
466 _percentNegativePattern = value;
470 public string NegativeInfinitySymbol
472 get => _negativeInfinitySymbol;
475 if (value == null)
477 throw new ArgumentNullException(nameof(value));
480 VerifyWritable();
481 _negativeInfinitySymbol = value;
485 public string NegativeSign
487 get => _negativeSign;
490 if (value == null)
492 throw new ArgumentNullException(nameof(value));
495 VerifyWritable();
496 _negativeSign = value;
497 UpdateHasInvariantNumberSigns();
501 public int NumberDecimalDigits
503 get => _numberDecimalDigits;
506 if (value < 0 || value > 99)
508 throw new ArgumentOutOfRangeException(
509 nameof(value),
510 value,
511 SR.Format(SR.ArgumentOutOfRange_Range, 0, 99));
514 VerifyWritable();
515 _numberDecimalDigits = value;
519 public string NumberDecimalSeparator
521 get => _numberDecimalSeparator;
524 VerifyWritable();
525 VerifyDecimalSeparator(value, nameof(value));
526 _numberDecimalSeparator = value;
530 public string NumberGroupSeparator
532 get => _numberGroupSeparator;
535 VerifyWritable();
536 VerifyGroupSeparator(value, nameof(value));
537 _numberGroupSeparator = value;
541 public int CurrencyPositivePattern
543 get => _currencyPositivePattern;
546 if (value < 0 || value > 3)
548 throw new ArgumentOutOfRangeException(
549 nameof(value),
550 value,
551 SR.Format(SR.ArgumentOutOfRange_Range, 0, 3));
554 VerifyWritable();
555 _currencyPositivePattern = value;
559 public string PositiveInfinitySymbol
561 get => _positiveInfinitySymbol;
564 if (value == null)
566 throw new ArgumentNullException(nameof(value));
569 VerifyWritable();
570 _positiveInfinitySymbol = value;
574 public string PositiveSign
576 get => _positiveSign;
579 if (value == null)
581 throw new ArgumentNullException(nameof(value));
584 VerifyWritable();
585 _positiveSign = value;
586 UpdateHasInvariantNumberSigns();
590 public int PercentDecimalDigits
592 get => _percentDecimalDigits;
595 if (value < 0 || value > 99)
597 throw new ArgumentOutOfRangeException(
598 nameof(value),
599 value,
600 SR.Format(SR.ArgumentOutOfRange_Range, 0, 99));
603 VerifyWritable();
604 _percentDecimalDigits = value;
608 public string PercentDecimalSeparator
610 get => _percentDecimalSeparator;
613 VerifyWritable();
614 VerifyDecimalSeparator(value, nameof(value));
615 _percentDecimalSeparator = value;
619 public string PercentGroupSeparator
621 get => _percentGroupSeparator;
624 VerifyWritable();
625 VerifyGroupSeparator(value, nameof(value));
626 _percentGroupSeparator = value;
630 public string PercentSymbol
632 get => _percentSymbol;
635 if (value == null)
637 throw new ArgumentNullException(nameof(value));
640 VerifyWritable();
641 _percentSymbol = value;
645 public string PerMilleSymbol
647 get => _perMilleSymbol;
650 if (value == null)
652 throw new ArgumentNullException(nameof(value));
655 VerifyWritable();
656 _perMilleSymbol = value;
660 public string[] NativeDigits
662 get => (string[])_nativeDigits.Clone();
665 VerifyWritable();
666 VerifyNativeDigits(value, nameof(value));
667 _nativeDigits = value;
671 public DigitShapes DigitSubstitution
673 get => (DigitShapes)_digitSubstitution;
676 VerifyWritable();
677 VerifyDigitSubstitution(value, nameof(value));
678 _digitSubstitution = (int)value;
682 public object? GetFormat(Type? formatType)
684 return formatType == typeof(NumberFormatInfo) ? this : null;
687 public static NumberFormatInfo ReadOnly(NumberFormatInfo nfi)
689 if (nfi == null)
691 throw new ArgumentNullException(nameof(nfi));
694 if (nfi.IsReadOnly)
696 return nfi;
699 NumberFormatInfo info = (NumberFormatInfo)(nfi.MemberwiseClone());
700 info._isReadOnly = true;
701 return info;
704 // private const NumberStyles InvalidNumberStyles = unchecked((NumberStyles) 0xFFFFFC00);
705 private const NumberStyles InvalidNumberStyles = ~(NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite
706 | NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingSign
707 | NumberStyles.AllowParentheses | NumberStyles.AllowDecimalPoint
708 | NumberStyles.AllowThousands | NumberStyles.AllowExponent
709 | NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier);
711 internal static void ValidateParseStyleInteger(NumberStyles style)
713 // Check for undefined flags or invalid hex number flags
714 if ((style & (InvalidNumberStyles | NumberStyles.AllowHexSpecifier)) != 0
715 && (style & ~NumberStyles.HexNumber) != 0)
717 throwInvalid(style);
719 void throwInvalid(NumberStyles value)
721 if ((value & InvalidNumberStyles) != 0)
723 throw new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style));
726 throw new ArgumentException(SR.Arg_InvalidHexStyle);
731 internal static void ValidateParseStyleFloatingPoint(NumberStyles style)
733 // Check for undefined flags or hex number
734 if ((style & (InvalidNumberStyles | NumberStyles.AllowHexSpecifier)) != 0)
736 throwInvalid(style);
738 void throwInvalid(NumberStyles value)
740 if ((value & InvalidNumberStyles) != 0)
742 throw new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style));
745 throw new ArgumentException(SR.Arg_HexStyleNotSupported);