Oddities of LOCALE_SGROUPING, NumberGroupSizes and NUMBERFMT

Many people (such as myself) get confused when trying to figure out the correlation between the LOCALE_SGROUPING used by GetLocaleInfoEx, NUMBERFMT Grouping field used by GetNumberFormatEx, and NumberFormatInfo.NumberGroupSizes property.  Hopefully this post will clear some of the confusion.

We define a "Grouping" of digits in our APIs.  The grouping is used to describe the spacing between the number separators, i.e.: 123,456,789 vs. 12,34,56,789

Unfortunately these groupings are described by a few of our APIs in different ways, which can get a bit confusing:

  • Grouping values can be accessed by GetLocaleInfo() in Windows and GetLocaleInfoEx() in Windows Vista using LOCALE_SGROUPING or LOCALE_SMONGROUPING ("MON" is for monetary, i.e.: currency values).
  • They can also be specified by the NUMBERFMT and CURRENCYFMT structure in GetNumberFormat / GetNumberFormatEx and GetCurrencyFormat / GetCurrencyFormatEx.  The structures have a Grouping value that provides the grouping information.
  • In the .Net Framework number groupings are represented by the NumberGroupSizes and CurrencyGroupSizes properties.

The basic concept is similar for all of these.  The grouping is represented as a sequence of numbers.  Each grouping digit represents the number of digits to be grouped together, i.e.: 3 would result in 1,234 and 2 would result in 12,34.  Subsequent grouping digits represent the next higher value group, so a grouping of 1-2-3-4 would result in a number formatted like 1234,567,89,0.

The last digit of the grouping may be 0 (zero), which is used as a flag to indicate whether the previous grouping is repeated or not.  Unfortunately this is a place where the APIs differ.

The NUMBERFMT, CURRENCYFMT, .Net NumberGroupSizes, CurrencyGroupSizes and our internal representation all use a trailing zero to indicate that no more groups follow, so 3 would result in groups like 123,456,789 while 3-0 would result in groupings like 123456,789.

Unfortunately LOCALE_SGROUPING and LOCALE_SMONGROUPING as used by GetLocaleInfo and GetLocaleInfoEx exhibit the opposite behavior.  For GetLocaleInfo/Ex a trailing 0 (zero) is used to indicate that the previous group should repeat.  So 3-0 results in groupings like 123,456,789 and 3 results in 123456,789.

As another complication, all three APIs differ in their storage of the grouping values.

GetLocaleInfo() expects a string of ASCII digits from '0' through '9', separated by semi-colons for LOCALE_SGROUPING and LOCALE_SMONGROUPING.  I.e.: "3;0" or "3;2;0".

The .Net's NumberFormatInfo.NumberGroupSizes and CurrencyGroupSizes expect an array of integers, i.e.: int[] {3, 2, 0}

The NUMBERFMT and CURRENCYFMT structures use a 32 bit UINT named Grouping.  Each grouping digit is combined to create a base-10 number, such as 320.  A NUMBERFMT.Grouping of 320 is equivilent to a LOCALE_SGROUPING of "3;2"

All of these APIs are fairly flexible and allow "strange" groupings, but in practice only 3 values are used in our locales and only 3 values are displayed in Regional And Language Options.  In Vista this could cause problems if a custom locale selects a value that Regional and Language Options cannot display.

These are the expected values for the grouping formats for each representation and the number 1234567890 formatted with those groupings.

GetCurrencyFormatEx

NumberFormatInfo

GetNumberFormatEx

GetLocaleInfoEx

Result

{ 0 } or {}

0

0 or empty

1234567890

{ 3 }

3

3;0

1,234,567,890

{ 3, 2 }

32

3;2;0

1,23,45,67,890

I'm uncertain if other groupings are in common use in some locales, but other values aren't well tested and may have limitations such as the Regional and Language Options support.

Hope this helps anyone needing clarification about these APIs, like Beth :-)

<Edited 7/17/2006 3:49>

Since the tables of values supported by Regional and Language Options (intl.cpl) doesn't cover many scenarios, here are a few more, note that the 1st two don't repeat the groups.

GetCurrencyFormatEx

NumberFormatInfo

GetNumberFormatEx

GetLocaleInfoEx

Result

{ 3, 0 }

30

3

1234567,890

{ 3, 2, 0 }

320

3;2

12345,67,890

{ 1, 2, 3, 4 }

1234

1;2;3;4;0

1234,567,89,0