Rob*_*obo 5 .net c# vb.net double internationalization
背景:我最近有幸编写了需要在国际范围内可靠地将字符串转换为双精度数的代码。该功能也必须被分发。即该字符串存储在数据库中,需要在跨不同区域设置运行的众多代理上转换为数字。由于限制原因,更改数据库模式是不可能的,我必须在遗留代码库中进行这项工作,并具有简单的升级路径,并且不破坏现有功能。
我能够通过将存储的字符串规范化为不变格式并在编码中添加一个标志来指示该值是否规范化并应采用新路径或非规范化(sp?)并采用旧路径来解决此问题。
我忘记提到原始值是由最终用户输入的,并且必须在可接受的格式范围内。这意味着存储的值可能有也可能没有数字分组说明符。显然,这是危险的,它目前仅适用于测试版,并且预计很快就会进行正确的 UI 国际化,以便正确发布。
也就是说,我认为我的转换代码应该能够处理数字分组字符,即使最终的规范化形式不包含它们也是合理的。提供适当的区域性格式的 Double.TryParse() 和 Double.ToString() 应该可以毫无问题地处理此问题,并且转换代码可能会因其他原因重用(是的遗留代码!)。
.NET 错误所以我认为围绕国际化字符串到双精度转换代码编写一些单元测试是个好主意。
我编写了两个主要测试(一种伪代码)。
测试1:
Double testValue = 15000.05
foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.AllCultures)
{
string testString = testValue.ToString(ci);
Assert.AreEqual(testValue, Convert(testString, ci));
}
Run Code Online (Sandbox Code Playgroud)
测试2:
foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.AllCultures)
{
string testString = testValue.ToString("N2", ci);
Assert.AreEqual(testValue, Convert(testString, ci));
}
Run Code Online (Sandbox Code Playgroud)
相关转换代码(几乎一行一行):
If Not Double.TryParse(numIn, Globalization.NumberStyles.Any, cultureInfo, numOut) Then Return False
Run Code Online (Sandbox Code Playgroud)
对于测试来说,收集所有文化代码的确切方法可能不同,Convert 的方法签名不同,周围的代码和断言也略有不同。相关部分是 .ToString(ci) 和 .ToString("N2", ci)。对于 en-US,这些版本将分别生成“15000.05”和“15,000.05”。此外,此代码在 .NET 版本 2.0 - 4.5.2 下运行,我们在各种相关版本下运行测试。它的行为全面相同(*可能需要仔细检查这一点,但这绝对是 .NET 4.5.2 中的行为)
测试1通过!
Test2 在这 5 个文化代码上失败:
目前,我们忽略这些不受支持的故障,并跟踪是否出现我们关心的新故障。
诊断 在深入研究并进行一些实验后,我们将问题追溯到数字分组说明符。即千分位分隔符。将 Double.TryParse() 更改为
numOut = Double.Parse(numIn, ci)
Run Code Online (Sandbox Code Playgroud)
作品。所以问题特别出在 Double.TryParse() 上,并且可能与 NumberStyle.Any 说明符有关。用十六进制说明符进行 Or'ing 也不起作用。
因此,在 .NET 中,您可以使用特定的 IFormatProvider 将双精度型转换为字符串,然后尝试使用同一 IFormatProvider 将其转换回双精度型,但会失败。
问题:谁能解释一下为什么会出现这种情况?
运行理论:我当前的两个想法是字符编码错误,其中数字分组字符或这些特定文化的实际双精度表示是不同的(类似于 .NET 中 double x = 0.3 实际上是 0.299...)。
免责声明:我在 VB.NET 和 C# 之间切换,因此请原谅任何语法混淆。另外,我知道该测试没有正确解释“奇数”数字分组,例如印地语中的 1,015,000 写为 10,15,000。
@tarekgh 在GitHub 问题上发布了答案。以下是他写的:
“这里的问题是失败的文化,因为你有以下几点:
小数点分隔符是“,” 组分隔符是“.” 货币小数点分隔符是“.” 货币组分隔符为“,” 请注意,小数点分隔符与货币组分隔符相同。组分隔符也与货币小数点分隔符相同。
现在,当您使用这种区域性格式化数字时,您将得到字符串“15.000,05”。当您尝试解析它时,您将传递 NumberStyles.Any 这意味着该字符串可以是货币数字,也可以是十进制数字。当尝试解析字符“.”时,这会使解析器感到困惑。因为它可以被视为货币小数分隔符,也可以被视为组分隔符。解析器决定将其视为货币小数分隔符。然后解析器将继续,直到点击“,”,并再次将其视为货币组分隔符。因为组分隔符不能出现在小数点分隔符之后,所以解析器将无法解析字符串,并且会从 TryParse 返回 false(或从 Parse 抛出异常)。
解决此问题的方法是从传递的 NumberStyles 中删除货币解析。IE
Double.TryParse(numString, NumberStyles.Any & (~NumberStyles.AllowCurrencySymbol), ci, out numParsed);
Run Code Online (Sandbox Code Playgroud)
我将结束这个问题,但如果您还有任何问题,请随时回复。”