C#decimal,如何添加尾随零

时间:2014-12-03 10:43:31

标签: c# decimal precision digits

我必须将尾随零添加到十进制值。不仅用于显示(因此Format不是一个选项),而是在实际的基础数据中,因为小数精度在我们的应用程序中很重要。

我试过了:

decimal value = 1M
decimal withPrecision = value + 0.000M;

在许多情况下效果很好......奇怪的是并非总体而言。我调试了一个情况,其中withPrecision中的值仍然是1M,没有看到运行时的值和立即窗口中相同的硬编码值有任何差异。我还使用了decimal.GetBits来找到差异 - 没有。

我尝试过(按此处Adjusting decimal precision, .net提议):

decimal value = 1M
decimal withPrecision = value * 1.000M;

效果很好 - 除了案例值为零。然后结果为0M,没有任何尾随零。我也不相信解决方案,在其他情况下也可能不起作用。

目前我与:

decimal value = 1M
decimal withPrecision = (value * 1.000M) + 0.000M;

哪种情况适用于我目前发现的所有情况......但它们看起来并不值得信赖。我也可以为零实现一个例外情况。

我认为FormatParse会起作用。我不喜欢它。它看起来不是很快,我不明白为什么我必须将小数字放入一个字符串只是为了操纵它。

我开始相信这样一个简单的任务没有干净的解决方案。

4 个答案:

答案 0 :(得分:5)

A decimal占用128位(16字节),其中1位用于符号,96位(12字节)用于实际值,5位用于存储位置小数点。

当C#编译器看到1M时,它将其解析为{sign: 0, value: 1, point: 0},而1.0M则解析为{sign: 0, value: 10, point: 1}。但是,两者都表示相同的值(1M == 1.0M返回true),另一个解析器可以轻松地将1M1.0M映射到{sign: 0, value: 1, point: 0}

1M0.1M加在一起会发生什么? 1M{sign: 0, value: 1, point: 0}0.1M{sign: 0, value: 1, point: 1},因此我们有两个精度不同的数字。但是,这不是问题:我们可以通过将1M添加到其点并将其值乘以110来移动{sign: 0, value: 10, point: 1}中的点。既然这两个数字具有相同的点位置,我们可以通过简单地将它们的值相加来将它们加在一起,从而产生{sign: 0, value: 11, point: 1},其对应于1.1M

因此decimal的内部表示不会影响其操作的精度 - 只要有必要,就会移动小数点位置(并调整值)。*

但是,如果由于某种原因你的小数绝对必须具有某个点位置(并且从你到目前为止发布的内容,我看到没有令人信服的理由 - 格式化纯粹是一个显示问题),那么最简单的方法是使用decimal(int, int, int, bool, byte)构造函数(或者decimal(int[]))。这允许您传入值(作为3个整数),符号(作为布尔值)和点位置(作为字节)。如果您将点位置高于0,则必须自己乘以该值:1.000M必须构造为new decimal(1000, 0, 0, false, 3),而不是new decimal(1, 0, 0, false, 3)(因为这会给您{{1} })。

*点位置限制为[0-28],因此0.001M不能代表点后面超过28位的数字。此外,该值必须在点前面和点后面的数字之间“分开”,因此非常大的数字会对可用精度施加限制,可能会将其缩小以支持在点前面表示数字。

答案 1 :(得分:1)

可能不是您希望的答案,但看起来您必须使用ToString()格式化。我建议您阅读this MSDN link中的备注部分。

备注中的最后一段说明:

  

缩放因子还会保留十进制数中的任何尾随零。尾随零不会影响算术或比较运算中的十进制数的值。但是,如果应用了适当的格式字符串,ToString方法可能会显示尾随零。

答案 2 :(得分:1)

正如我从您的评论中理解的那样,您希望通过存储十进制值来避免存储精度的附加字段。 不要这样做。它滥用框架,即使你成功实现了它,它也可以停止在另一个框架版本/ mono / etc中工作。这种编程使您的代码库不可读且难以调试。

只需使用您自己的类型:

struct DecimalEx
{
   public decimal Value;
   public byte Precision;
}

在一种简单的数据类型中设置几个值非常酷且有趣,但如果您与他人共享代码,请尝试避免这种情况,否则您很容易在地狱中获得特殊的地位。

答案 3 :(得分:0)

Decimal中,将0.04m添加到0.06m会产生0.10m而不是0.1m的原因并不是尾随零有意义,而是基于以下事实的优化:

  1. 在小数点后添加相同位数的数字很快,但调整小数点后的位数很慢。

  2. 一个值是小数点后带有一些位数的两个数字的总和,很可能会被添加到小数点后面的位数相同的更多数字。

  3. 由于#2,在算术运算后消耗额外零的费用更有可能增加后续操作所需的工作量而不是减少它。

  4. 即使不存在0.10m和0.100m之间的任何语义差异,多个位模式代表相同的数字而无法区分它们可能会引起一些烦恼和困惑,特别是如果有必要对实施中的错误进行故障排除。

  5. 鉴于上述情况,我会将使用Decimal的ToString() trailing-zeroes行为视为更多的调试辅助工具,而不是可用的功能。