// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
namespace System.Globalization
{
///
/// Property Default Description
/// PositiveSign '+' Character used to indicate positive values.
/// NegativeSign '-' Character used to indicate negative values.
/// NumberDecimalSeparator '.' The character used as the decimal separator.
/// NumberGroupSeparator ',' The character used to separate groups of
/// digits to the left of the decimal point.
/// NumberDecimalDigits 2 The default number of decimal places.
/// NumberGroupSizes 3 The number of digits in each group to the
/// left of the decimal point.
/// NaNSymbol "NaN" The string used to represent NaN values.
/// PositiveInfinitySymbol"Infinity" The string used to represent positive
/// infinities.
/// NegativeInfinitySymbol"-Infinity" The string used to represent negative
/// infinities.
///
/// Property Default Description
/// CurrencyDecimalSeparator '.' The character used as the decimal
/// separator.
/// CurrencyGroupSeparator ',' The character used to separate groups
/// of digits to the left of the decimal
/// point.
/// CurrencyDecimalDigits 2 The default number of decimal places.
/// CurrencyGroupSizes 3 The number of digits in each group to
/// the left of the decimal point.
/// CurrencyPositivePattern 0 The format of positive values.
/// CurrencyNegativePattern 0 The format of negative values.
/// CurrencySymbol "$" String used as local monetary symbol.
///
public sealed class NumberFormatInfo : IFormatProvider, ICloneable
{
private static volatile NumberFormatInfo s_invariantInfo;
internal int[] _numberGroupSizes = new int[] { 3 };
internal int[] _currencyGroupSizes = new int[] { 3 };
internal int[] _percentGroupSizes = new int[] { 3 };
internal string _positiveSign = "+";
internal string _negativeSign = "-";
internal string _numberDecimalSeparator = ".";
internal string _numberGroupSeparator = ",";
internal string _currencyGroupSeparator = ",";
internal string _currencyDecimalSeparator = ".";
internal string _currencySymbol = "\x00a4"; // U+00a4 is the symbol for International Monetary Fund.
internal string _nanSymbol = "NaN";
internal string _positiveInfinitySymbol = "Infinity";
internal string _negativeInfinitySymbol = "-Infinity";
internal string _percentDecimalSeparator = ".";
internal string _percentGroupSeparator = ",";
internal string _percentSymbol = "%";
internal string _perMilleSymbol = "\u2030";
internal string[] _nativeDigits = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
internal int _numberDecimalDigits = 2;
internal int _currencyDecimalDigits = 2;
internal int _currencyPositivePattern = 0;
internal int _currencyNegativePattern = 0;
internal int _numberNegativePattern = 1;
internal int _percentPositivePattern = 0;
internal int _percentNegativePattern = 0;
internal int _percentDecimalDigits = 2;
internal int _digitSubstitution = (int)DigitShapes.None;
internal bool _isReadOnly = false;
private bool _hasInvariantNumberSigns = true;
public NumberFormatInfo()
{
}
private static void VerifyDecimalSeparator(string decSep, string propertyName)
{
if (decSep == null)
{
throw new ArgumentNullException(propertyName);
}
if (decSep.Length == 0)
{
throw new ArgumentException(SR.Argument_EmptyDecString, propertyName);
}
}
private static void VerifyGroupSeparator(string groupSep, string propertyName)
{
if (groupSep == null)
{
throw new ArgumentNullException(propertyName);
}
}
private static void VerifyNativeDigits(string[] nativeDig, string propertyName)
{
if (nativeDig == null)
{
throw new ArgumentNullException(propertyName, SR.ArgumentNull_Array);
}
if (nativeDig.Length != 10)
{
throw new ArgumentException(SR.Argument_InvalidNativeDigitCount, propertyName);
}
for (int i = 0; i < nativeDig.Length; i++)
{
if (nativeDig[i] == null)
{
throw new ArgumentNullException(propertyName, SR.ArgumentNull_ArrayValue);
}
if (nativeDig[i].Length != 1)
{
if (nativeDig[i].Length != 2)
{
// Not 1 or 2 UTF-16 code points
throw new ArgumentException(SR.Argument_InvalidNativeDigitValue, propertyName);
}
else if (!char.IsSurrogatePair(nativeDig[i][0], nativeDig[i][1]))
{
// 2 UTF-6 code points, but not a surrogate pair
throw new ArgumentException(SR.Argument_InvalidNativeDigitValue, propertyName);
}
}
if (CharUnicodeInfo.GetDecimalDigitValue(nativeDig[i], 0) != i &&
CharUnicodeInfo.GetUnicodeCategory(nativeDig[i], 0) != UnicodeCategory.PrivateUse)
{
// Not the appropriate digit according to the Unicode data properties
// (Digit 0 must be a 0, etc.).
throw new ArgumentException(SR.Argument_InvalidNativeDigitValue, propertyName);
}
}
}
private static void VerifyDigitSubstitution(DigitShapes digitSub, string propertyName)
{
switch (digitSub)
{
case DigitShapes.Context:
case DigitShapes.None:
case DigitShapes.NativeNational:
// Success.
break;
default:
throw new ArgumentException(SR.Argument_InvalidDigitSubstitution, propertyName);
}
}
internal bool HasInvariantNumberSigns => _hasInvariantNumberSigns;
private void UpdateHasInvariantNumberSigns()
{
_hasInvariantNumberSigns = _positiveSign == "+" && _negativeSign == "-";
}
internal NumberFormatInfo(CultureData? cultureData)
{
if (cultureData != null)
{
// We directly use fields here since these data is coming from data table or Win32, so we
// don't need to verify their values (except for invalid parsing situations).
cultureData.GetNFIValues(this);
UpdateHasInvariantNumberSigns();
}
}
private void VerifyWritable()
{
if (_isReadOnly)
{
throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
}
}
///
/// Returns a default NumberFormatInfo that will be universally
/// supported and constant irrespective of the current culture.
/// Used by FromString methods.
///
public static NumberFormatInfo InvariantInfo
{
get
{
if (s_invariantInfo == null)
{
// Lazy create the invariant info. This cannot be done in a .cctor because exceptions can
// be thrown out of a .cctor stack that will need this.
s_invariantInfo = new NumberFormatInfo
{
_isReadOnly = true
};
}
return s_invariantInfo;
}
}
public static NumberFormatInfo GetInstance(IFormatProvider? formatProvider)
{
return formatProvider == null ?
CurrentInfo : // Fast path for a null provider
GetProviderNonNull(formatProvider);
NumberFormatInfo GetProviderNonNull(IFormatProvider provider)
{
// Fast path for a regular CultureInfo
if (provider is CultureInfo cultureProvider && !cultureProvider._isInherited)
{
return cultureProvider._numInfo ?? cultureProvider.NumberFormat;
}
return
provider as NumberFormatInfo ?? // Fast path for an NFI
provider.GetFormat(typeof(NumberFormatInfo)) as NumberFormatInfo ??
CurrentInfo;
}
}
public object Clone()
{
NumberFormatInfo n = (NumberFormatInfo)MemberwiseClone();
n._isReadOnly = false;
return n;
}
public int CurrencyDecimalDigits
{
get => _currencyDecimalDigits;
set
{
if (value < 0 || value > 99)
{
throw new ArgumentOutOfRangeException(
nameof(value),
value,
SR.Format(SR.ArgumentOutOfRange_Range, 0, 99));
}
VerifyWritable();
_currencyDecimalDigits = value;
}
}
public string CurrencyDecimalSeparator
{
get => _currencyDecimalSeparator;
set
{
VerifyWritable();
VerifyDecimalSeparator(value, nameof(value));
_currencyDecimalSeparator = value;
}
}
public bool IsReadOnly => _isReadOnly;
///
/// Check the values of the groupSize array.
/// Every element in the groupSize array should be between 1 and 9
/// except the last element could be zero.
///
internal static void CheckGroupSize(string propName, int[] groupSize)
{
for (int i = 0; i < groupSize.Length; i++)
{
if (groupSize[i] < 1)
{
if (i == groupSize.Length - 1 && groupSize[i] == 0)
{
return;
}
throw new ArgumentException(SR.Argument_InvalidGroupSize, propName);
}
else if (groupSize[i] > 9)
{
throw new ArgumentException(SR.Argument_InvalidGroupSize, propName);
}
}
}
public int[] CurrencyGroupSizes
{
get => ((int[])_currencyGroupSizes.Clone());
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
VerifyWritable();
int[] inputSizes = (int[])value.Clone();
CheckGroupSize(nameof(value), inputSizes);
_currencyGroupSizes = inputSizes;
}
}
public int[] NumberGroupSizes
{
get => ((int[])_numberGroupSizes.Clone());
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
VerifyWritable();
int[] inputSizes = (int[])value.Clone();
CheckGroupSize(nameof(value), inputSizes);
_numberGroupSizes = inputSizes;
}
}
public int[] PercentGroupSizes
{
get => ((int[])_percentGroupSizes.Clone());
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
VerifyWritable();
int[] inputSizes = (int[])value.Clone();
CheckGroupSize(nameof(value), inputSizes);
_percentGroupSizes = inputSizes;
}
}
public string CurrencyGroupSeparator
{
get => _currencyGroupSeparator;
set
{
VerifyWritable();
VerifyGroupSeparator(value, nameof(value));
_currencyGroupSeparator = value;
}
}
public string CurrencySymbol
{
get => _currencySymbol;
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
VerifyWritable();
_currencySymbol = value;
}
}
///
/// Returns the current culture's NumberFormatInfo. Used by Parse methods.
///
public static NumberFormatInfo CurrentInfo
{
get
{
System.Globalization.CultureInfo culture = CultureInfo.CurrentCulture;
if (!culture._isInherited)
{
NumberFormatInfo? info = culture._numInfo;
if (info != null)
{
return info;
}
}
// returns non-nullable when passed typeof(NumberFormatInfo)
return (NumberFormatInfo)culture.GetFormat(typeof(NumberFormatInfo))!;
}
}
public string NaNSymbol
{
get => _nanSymbol;
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
VerifyWritable();
_nanSymbol = value;
}
}
public int CurrencyNegativePattern
{
get => _currencyNegativePattern;
set
{
if (value < 0 || value > 15)
{
throw new ArgumentOutOfRangeException(
nameof(value),
value,
SR.Format(SR.ArgumentOutOfRange_Range, 0, 15));
}
VerifyWritable();
_currencyNegativePattern = value;
}
}
public int NumberNegativePattern
{
get => _numberNegativePattern;
set
{
// NOTENOTE: the range of value should correspond to negNumberFormats[] in vm\COMNumber.cpp.
if (value < 0 || value > 4)
{
throw new ArgumentOutOfRangeException(
nameof(value),
value,
SR.Format(SR.ArgumentOutOfRange_Range, 0, 4));
}
VerifyWritable();
_numberNegativePattern = value;
}
}
public int PercentPositivePattern
{
get => _percentPositivePattern;
set
{
// NOTENOTE: the range of value should correspond to posPercentFormats[] in vm\COMNumber.cpp.
if (value < 0 || value > 3)
{
throw new ArgumentOutOfRangeException(
nameof(value),
value,
SR.Format(SR.ArgumentOutOfRange_Range, 0, 3));
}
VerifyWritable();
_percentPositivePattern = value;
}
}
public int PercentNegativePattern
{
get => _percentNegativePattern;
set
{
// NOTENOTE: the range of value should correspond to posPercentFormats[] in vm\COMNumber.cpp.
if (value < 0 || value > 11)
{
throw new ArgumentOutOfRangeException(
nameof(value),
value,
SR.Format(SR.ArgumentOutOfRange_Range, 0, 11));
}
VerifyWritable();
_percentNegativePattern = value;
}
}
public string NegativeInfinitySymbol
{
get => _negativeInfinitySymbol;
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
VerifyWritable();
_negativeInfinitySymbol = value;
}
}
public string NegativeSign
{
get => _negativeSign;
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
VerifyWritable();
_negativeSign = value;
UpdateHasInvariantNumberSigns();
}
}
public int NumberDecimalDigits
{
get => _numberDecimalDigits;
set
{
if (value < 0 || value > 99)
{
throw new ArgumentOutOfRangeException(
nameof(value),
value,
SR.Format(SR.ArgumentOutOfRange_Range, 0, 99));
}
VerifyWritable();
_numberDecimalDigits = value;
}
}
public string NumberDecimalSeparator
{
get => _numberDecimalSeparator;
set
{
VerifyWritable();
VerifyDecimalSeparator(value, nameof(value));
_numberDecimalSeparator = value;
}
}
public string NumberGroupSeparator
{
get => _numberGroupSeparator;
set
{
VerifyWritable();
VerifyGroupSeparator(value, nameof(value));
_numberGroupSeparator = value;
}
}
public int CurrencyPositivePattern
{
get => _currencyPositivePattern;
set
{
if (value < 0 || value > 3)
{
throw new ArgumentOutOfRangeException(
nameof(value),
value,
SR.Format(SR.ArgumentOutOfRange_Range, 0, 3));
}
VerifyWritable();
_currencyPositivePattern = value;
}
}
public string PositiveInfinitySymbol
{
get => _positiveInfinitySymbol;
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
VerifyWritable();
_positiveInfinitySymbol = value;
}
}
public string PositiveSign
{
get => _positiveSign;
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
VerifyWritable();
_positiveSign = value;
UpdateHasInvariantNumberSigns();
}
}
public int PercentDecimalDigits
{
get => _percentDecimalDigits;
set
{
if (value < 0 || value > 99)
{
throw new ArgumentOutOfRangeException(
nameof(value),
value,
SR.Format(SR.ArgumentOutOfRange_Range, 0, 99));
}
VerifyWritable();
_percentDecimalDigits = value;
}
}
public string PercentDecimalSeparator
{
get => _percentDecimalSeparator;
set
{
VerifyWritable();
VerifyDecimalSeparator(value, nameof(value));
_percentDecimalSeparator = value;
}
}
public string PercentGroupSeparator
{
get => _percentGroupSeparator;
set
{
VerifyWritable();
VerifyGroupSeparator(value, nameof(value));
_percentGroupSeparator = value;
}
}
public string PercentSymbol
{
get => _percentSymbol;
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
VerifyWritable();
_percentSymbol = value;
}
}
public string PerMilleSymbol
{
get => _perMilleSymbol;
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
VerifyWritable();
_perMilleSymbol = value;
}
}
public string[] NativeDigits
{
get => (string[])_nativeDigits.Clone();
set
{
VerifyWritable();
VerifyNativeDigits(value, nameof(value));
_nativeDigits = value;
}
}
public DigitShapes DigitSubstitution
{
get => (DigitShapes)_digitSubstitution;
set
{
VerifyWritable();
VerifyDigitSubstitution(value, nameof(value));
_digitSubstitution = (int)value;
}
}
public object? GetFormat(Type? formatType)
{
return formatType == typeof(NumberFormatInfo) ? this : null;
}
public static NumberFormatInfo ReadOnly(NumberFormatInfo nfi)
{
if (nfi == null)
{
throw new ArgumentNullException(nameof(nfi));
}
if (nfi.IsReadOnly)
{
return nfi;
}
NumberFormatInfo info = (NumberFormatInfo)(nfi.MemberwiseClone());
info._isReadOnly = true;
return info;
}
// private const NumberStyles InvalidNumberStyles = unchecked((NumberStyles) 0xFFFFFC00);
private const NumberStyles InvalidNumberStyles = ~(NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite
| NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingSign
| NumberStyles.AllowParentheses | NumberStyles.AllowDecimalPoint
| NumberStyles.AllowThousands | NumberStyles.AllowExponent
| NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier);
internal static void ValidateParseStyleInteger(NumberStyles style)
{
// Check for undefined flags or invalid hex number flags
if ((style & (InvalidNumberStyles | NumberStyles.AllowHexSpecifier)) != 0
&& (style & ~NumberStyles.HexNumber) != 0)
{
throwInvalid(style);
void throwInvalid(NumberStyles value)
{
if ((value & InvalidNumberStyles) != 0)
{
throw new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style));
}
throw new ArgumentException(SR.Arg_InvalidHexStyle);
}
}
}
internal static void ValidateParseStyleFloatingPoint(NumberStyles style)
{
// Check for undefined flags or hex number
if ((style & (InvalidNumberStyles | NumberStyles.AllowHexSpecifier)) != 0)
{
throwInvalid(style);
void throwInvalid(NumberStyles value)
{
if ((value & InvalidNumberStyles) != 0)
{
throw new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style));
}
throw new ArgumentException(SR.Arg_HexStyleNotSupported);
}
}
}
}
}