我们正在处理财务计算。我发现了这篇关于将货币价值存储为小数的帖子:decimal vs double! - Which one should I use and when?
所以我将数量存储为小数。
我的计算如下: 12.000 * (1/12) = 1.000
如果我使用decimal数据类型来存储数量和结果数量,我不会得到预期的结果
// First approach:
decimal ratio = 1m / 12m;
decimal amount = 12000;
decimal ratioAmount = amount * ratio;
ratioAmount = 999.9999999999999
// Second approach:
double ratio = 1d / 12d;
decimal amount = 12000;
decimal ratioAmount = (decimal)((double)amount * ratio);
ratioAmount = 1.000
// Third approach:
double ratio = 1d / 12d;
double amount = 12000;
double ratioAmount = amount * ratio;
ratioAmount = 1.000最好的方法是什么?每个人都在谈论金额/金钱必须以小数形式存储。
发布于 2019-02-17 00:45:30
永远,永远不要把财务金额存储在双倍的水平上。下面是一个示例from my blog,它说明了为什么不应该使用double:
var lineValues = new List<double> { 1675.89, 2600.21, 5879.79, 5367.51, 8090.30, 492.97, 7888.60 };
double dblAvailable = 31995.27d;
double dblTotal = 0d;
foreach (var lineValue in lineValues)
{
dblTotal += lineValue;
}
if (dblAvailable < dblTotal)
{
Console.WriteLine("They don't add up!");
}您将看到,由于两次相加实际上等于31995.270000000004.,因此将命中Console.WriteLine正如您可能能够从变量的名称中猜到的那样,此代码示例基于金融系统中的一些实际代码-此问题导致用户无法正确地将金额分配给事务。
使用以下附加代码将数字相加为decimals:
decimal decAvailable = (decimal)dblAvailable;
decimal decTotal = (decimal)dblTotal;
if (decAvailable < decTotal)
{
Console.WriteLine("They still don't add up!");
}不会击中Console.WriteLine。这个故事的寓意是:使用decimal进行财务计算!
language reference for the decimal keyword的第一部分声明:
与其他浮点类型相比,
decimal类型具有更高的精度和更小的范围,这使得它适合于金融和货币计算。
同样值得注意的是,对于要被视为小数的数字文字,应该使用后缀m (代表money),这进一步表明了财务数据类型的适当性。
发布于 2019-02-17 01:49:03
似乎所有这些帖子都很接近,但并没有完全解释问题的症结所在。这并不是说decimal存储值更精确,或者double有更多的数字或类似的东西。它们各自以不同的方式存储值。
decimal类型以十进制形式存储值。比如1234.567。double (和float)像1101010.0011001一样以二进制形式存储值。(它们也有存储位数的限制,但这在这里或永远都是不相关的。如果你觉得你的精度快用完了,你可能做错了什么。)
请注意,有些值不能精确地存储在这两种表示法中,因为它们需要在小数点后有无限多的数字。比如1/3或1/12。这样的值在存储时会有一些舍入,这就是您在这里看到的。
decimal在金融计算中的优势是它可以精确地存储小数,而double不能。例如,0.1可以精确地存储在decimal中,但不能存储在double中。这些都是货币数量通常采用的价值。你永远不需要储存2/3美元,你只需要0.66美元。人类货币是以十进制为基础的,因此decimal类型可以很好地存储它们。
此外,添加和减去十进制值也可以完美地处理decimal类型。这是财务计算中最常见的操作,因此以这种方式编程更容易。
乘以十进制值也很有效,尽管它可以增加用于确保精确值的小数位数。
但是除法是非常危险的,因为通过除法获得的大多数值都不能精确地存储,并且会出现舍入误差。
归根结底,double和decimal都可以用来存储货币值,您只需要非常小心它们的局限性。对于double类型,您需要在每次计算后对结果进行四舍五入,甚至是加法和减法。无论何时向用户显示值,都需要显式地将其格式化为具有一定数量的小数位数。此外,在比较数字时,请注意仅比较小数的前X位(通常为2或4)。
对于decimal类型,其中一些限制可以放宽,因为您知道您的货币值是精确存储的。你通常可以在加法和减法之后跳过舍入。如果您首先只存储X个小数位数,则无需担心显式显示格式和比较。它确实让事情变得相当简单。但是你仍然需要在乘法和除法之后进行舍入。
还有一种更优雅的方法没有在这里讨论。改变你的货币单位。不是存储美元值,而是存储美分值。或者,如果你使用4位十进制数字,存储1/100美分。
然后你就可以使用int或long来做任何事情了!
这具有decimal的大部分优点(值存储精确,加/减工作精确),但您需要对事物进行舍入的位置将变得更加明显。然而,一个小缺点是格式化这些值以显示变得稍微复杂一些。另一方面,如果你忘记做这件事,那也是显而易见的。到目前为止,这是我首选的方法。
发布于 2019-02-17 01:26:54
每个人都告诉你使用decimal是正确的。甚至the official docs都说decimal是最好的选择:
与其他浮点类型相比,decimal类型具有更高的精度和更小的范围,这使得它适合于金融和货币计算。
您观察到的看似不正确的行为来自于1/12不能完美地表示为小数的事实。
我略微修改了您的示例,并将它们作为xUnit测试提供。示例中的所有断言都通过了。
这就是给你带来麻烦的例子。
[Fact]
public void FirstApproach()
{
// First approach:
decimal ratio = 1m / 12m;
decimal amount = 12.000m;
decimal ratioAmount = amount * ratio;
Assert.Equal(0.9999999999999999999999999996m, ratioAmount);
}显然,12 * (1/12)应该是1,所以这似乎是错误的。
只要稍加修改,我们就可以得到“正确”的答案。
[Fact]
public void ModifiedFirstApproach()
{
// Values from first approach,
// but with intermediate variables removed
decimal ratioAmount = 12.000m * 1m / 12m;
Assert.Equal(1.000m, ratioAmount);
}那么问题看起来是中间变量ratio,尽管更准确地说,它是一个操作顺序问题。括号的添加重新引入了原始代码中的错误...
[Fact]
public void AnotherModifiedFirstApproach()
{
// Values from first approach,
// but with intermediate variables removed
decimal ratioAmount = 12.000m * (1m / 12m);
Assert.Equal(0.9999999999999999999999999996m, ratioAmount);
}核心问题可以用一句话来说明...
[Fact]
public void OneTwelfthAsDecimal()
{
Assert.Equal(0.0833333333333333333333333333m, 1m / 12m);
}分数1/12只能表示为重复的小数,这使得它不精确。这不是C#的错--它只是在十进制(以10为基数)的数字系统中工作的事实。
https://stackoverflow.com/questions/54725239
复制相似问题