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
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
20 /// NegativeInfinitySymbol"-Infinity" The string used to represent negative
23 /// Property Default Description
24 /// CurrencyDecimalSeparator '.' The character used as the decimal
26 /// CurrencyGroupSeparator ',' The character used to separate groups
27 /// of digits to the left of the decimal
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.
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
)
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
)
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
)
147 case DigitShapes
.Context
:
148 case DigitShapes
.None
:
149 case DigitShapes
.NativeNational
:
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()
181 throw new InvalidOperationException(SR
.InvalidOperation_ReadOnly
);
186 /// Returns a default NumberFormatInfo that will be universally
187 /// supported and constant irrespective of the current culture.
188 /// Used by FromString methods.
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
;
210 provider
as NumberFormatInfo
?? // Fast path for an NFI
211 provider
.GetFormat(typeof(NumberFormatInfo
)) as NumberFormatInfo
??
216 public object Clone()
218 NumberFormatInfo n
= (NumberFormatInfo
)MemberwiseClone();
219 n
._isReadOnly
= false;
223 public int CurrencyDecimalDigits
225 get => _currencyDecimalDigits
;
228 if (value < 0 || value > 99)
230 throw new ArgumentOutOfRangeException(
233 SR
.Format(SR
.ArgumentOutOfRange_Range
, 0, 99));
237 _currencyDecimalDigits
= value;
241 public string CurrencyDecimalSeparator
243 get => _currencyDecimalSeparator
;
247 VerifyDecimalSeparator(value, nameof(value));
248 _currencyDecimalSeparator
= value;
252 public bool IsReadOnly
=> _isReadOnly
;
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.
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)
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();
286 throw new ArgumentNullException(nameof(value));
291 int[] inputSizes
= (int[])value.Clone();
292 CheckGroupSize(nameof(value), inputSizes
);
293 _currencyGroupSizes
= inputSizes
;
297 public int[] NumberGroupSizes
299 get => (int[])_numberGroupSizes
.Clone();
304 throw new ArgumentNullException(nameof(value));
309 int[] inputSizes
= (int[])value.Clone();
310 CheckGroupSize(nameof(value), inputSizes
);
311 _numberGroupSizes
= inputSizes
;
315 public int[] PercentGroupSizes
317 get => (int[])_percentGroupSizes
.Clone();
322 throw new ArgumentNullException(nameof(value));
326 int[] inputSizes
= (int[])value.Clone();
327 CheckGroupSize(nameof(value), inputSizes
);
328 _percentGroupSizes
= inputSizes
;
332 public string CurrencyGroupSeparator
334 get => _currencyGroupSeparator
;
338 VerifyGroupSeparator(value, nameof(value));
339 _currencyGroupSeparator
= value;
343 public string CurrencySymbol
345 get => _currencySymbol
;
350 throw new ArgumentNullException(nameof(value));
354 _currencySymbol
= value;
359 /// Returns the current culture's NumberFormatInfo. Used by Parse methods.
362 public static NumberFormatInfo CurrentInfo
366 System
.Globalization
.CultureInfo culture
= CultureInfo
.CurrentCulture
;
367 if (!culture
._isInherited
)
369 NumberFormatInfo
? info
= culture
._numInfo
;
375 // returns non-nullable when passed typeof(NumberFormatInfo)
376 return (NumberFormatInfo
)culture
.GetFormat(typeof(NumberFormatInfo
))!;
380 public string NaNSymbol
387 throw new ArgumentNullException(nameof(value));
395 public int CurrencyNegativePattern
397 get => _currencyNegativePattern
;
400 if (value < 0 || value > 15)
402 throw new ArgumentOutOfRangeException(
405 SR
.Format(SR
.ArgumentOutOfRange_Range
, 0, 15));
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(
424 SR
.Format(SR
.ArgumentOutOfRange_Range
, 0, 4));
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(
443 SR
.Format(SR
.ArgumentOutOfRange_Range
, 0, 3));
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(
462 SR
.Format(SR
.ArgumentOutOfRange_Range
, 0, 11));
466 _percentNegativePattern
= value;
470 public string NegativeInfinitySymbol
472 get => _negativeInfinitySymbol
;
477 throw new ArgumentNullException(nameof(value));
481 _negativeInfinitySymbol
= value;
485 public string NegativeSign
487 get => _negativeSign
;
492 throw new ArgumentNullException(nameof(value));
496 _negativeSign
= value;
497 UpdateHasInvariantNumberSigns();
501 public int NumberDecimalDigits
503 get => _numberDecimalDigits
;
506 if (value < 0 || value > 99)
508 throw new ArgumentOutOfRangeException(
511 SR
.Format(SR
.ArgumentOutOfRange_Range
, 0, 99));
515 _numberDecimalDigits
= value;
519 public string NumberDecimalSeparator
521 get => _numberDecimalSeparator
;
525 VerifyDecimalSeparator(value, nameof(value));
526 _numberDecimalSeparator
= value;
530 public string NumberGroupSeparator
532 get => _numberGroupSeparator
;
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(
551 SR
.Format(SR
.ArgumentOutOfRange_Range
, 0, 3));
555 _currencyPositivePattern
= value;
559 public string PositiveInfinitySymbol
561 get => _positiveInfinitySymbol
;
566 throw new ArgumentNullException(nameof(value));
570 _positiveInfinitySymbol
= value;
574 public string PositiveSign
576 get => _positiveSign
;
581 throw new ArgumentNullException(nameof(value));
585 _positiveSign
= value;
586 UpdateHasInvariantNumberSigns();
590 public int PercentDecimalDigits
592 get => _percentDecimalDigits
;
595 if (value < 0 || value > 99)
597 throw new ArgumentOutOfRangeException(
600 SR
.Format(SR
.ArgumentOutOfRange_Range
, 0, 99));
604 _percentDecimalDigits
= value;
608 public string PercentDecimalSeparator
610 get => _percentDecimalSeparator
;
614 VerifyDecimalSeparator(value, nameof(value));
615 _percentDecimalSeparator
= value;
619 public string PercentGroupSeparator
621 get => _percentGroupSeparator
;
625 VerifyGroupSeparator(value, nameof(value));
626 _percentGroupSeparator
= value;
630 public string PercentSymbol
632 get => _percentSymbol
;
637 throw new ArgumentNullException(nameof(value));
641 _percentSymbol
= value;
645 public string PerMilleSymbol
647 get => _perMilleSymbol
;
652 throw new ArgumentNullException(nameof(value));
656 _perMilleSymbol
= value;
660 public string[] NativeDigits
662 get => (string[])_nativeDigits
.Clone();
666 VerifyNativeDigits(value, nameof(value));
667 _nativeDigits
= value;
671 public DigitShapes DigitSubstitution
673 get => (DigitShapes
)_digitSubstitution
;
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
)
691 throw new ArgumentNullException(nameof(nfi
));
699 NumberFormatInfo info
= (NumberFormatInfo
)(nfi
.MemberwiseClone());
700 info
._isReadOnly
= true;
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)
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)
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
);