如何快速获取默认的NUMBERFMT设置?

3
我正在使用Windows API GetNumberFormatEx 来格式化一些数字,以便与当前用户的区域设置相匹配(例如,确保在正确的位置使用正确的分隔符)。 如果您需要完全按照用户默认设置,则此操作很简单。
但是,在某些情况下,我有时必须覆盖小数点后的数字位数。 这需要提供一个NUMBERFMT结构。 我想做的是调用一个API,返回填充了适当默认值的NUMBERFMT,然后仅覆盖我需要更改的字段。 但似乎没有API可以获取默认值。
目前,我一遍又一遍地调用GetLocaleInfoEx,然后将该数据转换为NUMBERFMT所需的格式。
NUMBERFMT fmt = {0};
::GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT,
                  LOCALE_IDIGITS | LOCALE_RETURN_NUMBER,
                  reinterpret_cast<LPWSTR>(&fmt.NumDigits),
                  sizeof(fmt.NumDigits)/sizeof(WCHAR));
::GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT,
                  LOCALE_ILZERO | LOCALE_RETURN_NUMBER,
                  reinterpret_cast<LPWSTR>(&fmt.LeadingZero),
                  sizeof(fmt.LeadingZero)/sizeof(WCHAR));
WCHAR szGrouping[32] = L"";
::GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, LOCALE_SGROUPING, szGrouping,
                  ARRAYSIZE(szGrouping));
if (::lstrcmp(szGrouping, L"3;0") == 0 ||
    ::lstrcmp(szGrouping, L"3") == 0
) {
    fmt.Grouping = 3;
} else if (::lstrcmp(szGrouping, L"3;2;0") == 0) {
    fmt.Grouping = 32;
} else {
    assert(false);  // unexpected grouping string
}
WCHAR szDecimal[16] = L"";
::GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, LOCALE_SDECIMAL, szDecimal,
                  ARRAYSIZE(szDecimal));
fmt.lpDecimalSep = szDecimal;
WCHAR szThousand[16] = L"";
::GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, LOCALE_STHOUSAND, szThousand,
                  ARRAYSIZE(szThousand));
fmt.lpThousandSep = szThousand;
::GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT,
                  LOCALE_INEGNUMBER | LOCALE_RETURN_NUMBER,
                  reinterpret_cast<LPWSTR>(&fmt.NegativeOrder),
                  sizeof(fmt.NegativeOrder)/sizeof(WCHAR));

难道没有已经能做到这一点的API吗?
2个回答

2

我上周刚写了一些代码来完成这个任务。可惜的是,似乎没有一个GetDefaultNumberFormat(LCID lcid, NUMBERFMT* fmt)函数;你必须自己编写代码。另外,分组字符串有一个明确定义的格式,可以很容易地解析;你当前的代码对于“3”是错误的(应该是30),而且在更奇特的分组情况下显然会失败(尽管这可能不是真正的问题)。


糟糕,我希望有一个API。我相当确定我正确地设置了分组字段。请参见http://msdn.microsoft.com/en-us/library/dd319095(v=vs.85).aspx,其中开始说:“范围在0到9和32之间的值是有效的。”(因此,30无效。) - Adrian McCarthy
实际上,你只会真正看到0、3和可能的32。但是如果你想为了以防万一处理所有情况,请查看这个链接:http://blogs.msdn.com/b/shawnste/archive/2006/07/17/668741.aspx。 - Luke
那是一个不错的链接,尽管它有点与MSDN相矛盾,但似乎很准确。 - Adrian McCarthy

0

如果你只想从字符串的末尾截断小数位,你可以选择其中一个默认格式(比如LOCALE_NAME_USER_DEFAULT),然后检查结果字符串中是否存在小数分隔符(在大陆语言中是逗号,在英语中是点),然后用空字节替换小数部分来截断它:

#define cut_off_decimals(sz, cch) \
    if (cch >= 5 && (sz[cch-4] == _T('.') || sz[cch-4] == _T(','))) \
        sz[cch-4] = _T('\0');

(匈牙利警告:sz是C字符串,cch是字符计数,包括终止的空字节。而_T是Windows通用文本宏,根据是否定义了UNICODE,只需要与charwchar_t兼容,仅用于与Windows 9x/ME兼容性。)

请注意,对于用户定义格式的非常奇怪的情况,其中倒数第三个字符是点或逗号,并且具有用户以外的某些特殊含义,而不是小数分隔符,这将产生不正确的结果。我从未在我的整个生命中见过这样的数字格式,因此我得出结论,这已经足够好和安全。

当然,如果倒数第三个字符既不是点也不是逗号,则不会执行任何操作。


这对我来说太具体了。我真的想选择小数位数,而不是将它们全部截断。我可以使用类似于此的方法,但仍需要查找小数位数,以便知道要检查基数分隔符的位置(并确保我没有将其与分组分隔符混淆)。总的来说,这似乎是一种hack方法。 - Adrian McCarthy
1
另外,一个小细节:_UNICODE 实际上是 MS CRT 宏,用于控制 _T_TEXT 的定义方式。UNICODE 是 Windows 宏,用于控制 TEXT 的定义方式。 - Adrian McCarthy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接