首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >财务计算:双精度还是十进制?

财务计算:双精度还是十进制?
EN

Stack Overflow用户
提问于 2019-02-17 00:34:07
回答 4查看 2.8K关注 0票数 4

我们正在处理财务计算。我发现了这篇关于将货币价值存储为小数的帖子:decimal vs double! - Which one should I use and when?

所以我将数量存储为小数。

我的计算如下: 12.000 * (1/12) = 1.000

如果我使用decimal数据类型来存储数量和结果数量,我不会得到预期的结果

代码语言:javascript
复制
// 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

最好的方法是什么?每个人都在谈论金额/金钱必须以小数形式存储。

EN

回答 4

Stack Overflow用户

发布于 2019-02-17 00:45:30

永远,永远不要把财务金额存储在双倍的水平上。下面是一个示例from my blog,它说明了为什么不应该使用double

代码语言:javascript
复制
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:

代码语言:javascript
复制
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),这进一步表明了财务数据类型的适当性。

票数 10
EN

Stack Overflow用户

发布于 2019-02-17 01:49:03

似乎所有这些帖子都很接近,但并没有完全解释问题的症结所在。这并不是说decimal存储值更精确,或者double有更多的数字或类似的东西。它们各自以不同的方式存储值。

decimal类型以十进制形式存储值。比如1234.567double (和float)像1101010.0011001一样以二进制形式存储值。(它们也有存储位数的限制,但这在这里或永远都是不相关的。如果你觉得你的精度快用完了,你可能做错了什么。)

请注意,有些值不能精确地存储在这两种表示法中,因为它们需要在小数点后有无限多的数字。比如1/31/12。这样的值在存储时会有一些舍入,这就是您在这里看到的。

decimal在金融计算中的优势是它可以精确地存储小数,而double不能。例如,0.1可以精确地存储在decimal中,但不能存储在double中。这些都是货币数量通常采用的价值。你永远不需要储存2/3美元,你只需要0.66美元。人类货币是以十进制为基础的,因此decimal类型可以很好地存储它们。

此外,添加和减去十进制值也可以完美地处理decimal类型。这是财务计算中最常见的操作,因此以这种方式编程更容易。

乘以十进制值也很有效,尽管它可以增加用于确保精确值的小数位数。

但是除法是非常危险的,因为通过除法获得的大多数值都不能精确地存储,并且会出现舍入误差。

归根结底,doubledecimal都可以用来存储货币值,您只需要非常小心它们的局限性。对于double类型,您需要在每次计算后对结果进行四舍五入,甚至是加法和减法。无论何时向用户显示值,都需要显式地将其格式化为具有一定数量的小数位数。此外,在比较数字时,请注意仅比较小数的前X位(通常为2或4)。

对于decimal类型,其中一些限制可以放宽,因为您知道您的货币值是精确存储的。你通常可以在加法和减法之后跳过舍入。如果您首先只存储X个小数位数,则无需担心显式显示格式和比较。它确实让事情变得相当简单。但是你仍然需要在乘法和除法之后进行舍入。

还有一种更优雅的方法没有在这里讨论。改变你的货币单位。不是存储美元值,而是存储美分值。或者,如果你使用4位十进制数字,存储1/100美分。

然后你就可以使用intlong来做任何事情了!

这具有decimal的大部分优点(值存储精确,加/减工作精确),但您需要对事物进行舍入的位置将变得更加明显。然而,一个小缺点是格式化这些值以显示变得稍微复杂一些。另一方面,如果你忘记做这件事,那也是显而易见的。到目前为止,这是我首选的方法。

票数 9
EN

Stack Overflow用户

发布于 2019-02-17 01:26:54

每个人都告诉你使用decimal是正确的。甚至the official docs都说decimal是最好的选择:

与其他浮点类型相比,decimal类型具有更高的精度和更小的范围,这使得它适合于金融和货币计算。

您观察到的看似不正确的行为来自于1/12不能完美地表示为小数的事实。

我略微修改了您的示例,并将它们作为xUnit测试提供。示例中的所有断言都通过了。

这就是给你带来麻烦的例子。

代码语言:javascript
复制
[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,所以这似乎是错误的。

只要稍加修改,我们就可以得到“正确”的答案。

代码语言:javascript
复制
[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,尽管更准确地说,它是一个操作顺序问题。括号的添加重新引入了原始代码中的错误...

代码语言:javascript
复制
[Fact]
public void AnotherModifiedFirstApproach()
{
    // Values from first approach,
    // but with intermediate variables removed
    decimal ratioAmount = 12.000m * (1m / 12m);

    Assert.Equal(0.9999999999999999999999999996m, ratioAmount);
}

核心问题可以用一句话来说明...

代码语言:javascript
复制
[Fact]
public void OneTwelfthAsDecimal()
{
    Assert.Equal(0.0833333333333333333333333333m, 1m / 12m);
}

分数1/12只能表示为重复的小数,这使得它不精确。这不是C#的错--它只是在十进制(以10为基数)的数字系统中工作的事实。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/54725239

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档