C#decimal,如何添加尾随零

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

我试过了:

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

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

我试过(这里提出调整小数精度,.net ):

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

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

目前我在:

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

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

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

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

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} ,所以我们有两个精度不同的数字。 然而,这没有问题:我们可以通过在其点加1并将其值乘以10来移动1M的点: {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必须自己乘以该值: 1.000M必须构造为new decimal(1000, 0, 0, false, 3) 1.000M new decimal(1000, 0, 0, false, 3) ,而不是new decimal(1, 0, 0, false, 3) 1.000M new decimal(1, 0, 0, false, 3) (因为那会给你0.001M )。

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

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

备注中的最后一段说明

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

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

只需使用您自己的类型:

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

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

原因是,在Decimal ,加0.04m0.06m产生0.10m而不是0.1m并不是尾随零是有意义的,而是基于以下事实的优化:

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

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

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

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

鉴于上述情况,我将使用Decimal查看ToString() trailing-zeroes行为,因为它更像是一个调试辅助工具,而不是一个可用的function。