问题
I have to add trailing zeros to a decimal value. Not only for displaying (so Format
is not an option), but in the actual underlying data, because the decimal precision is important in our application.
I tried:
decimal value = 1M
decimal withPrecision = value + 0.000M;
Which works well in many cases ... strangely not in all. I debugged into a case where the value in withPrecision was still 1M, by not seeing any difference in the value at runtime and the same hardcoded value in the immediate window. I also used decimal.GetBits to find differences - there are none.
I tried (as proposed here Adjusting decimal precision, .net):
decimal value = 1M
decimal withPrecision = value * 1.000M;
which works well - except for the case value is zero. Then the result is 0M without any trailing zeros. I also do not trust the solution, it may also not work in other cases.
Currently I'm with:
decimal value = 1M
decimal withPrecision = (value * 1.000M) + 0.000M;
Which works in all cases I currently found ... but doesn't look very trustworthy neither. I could also implement an exceptional case for zeros.
I think Format
and Parse
would work. I don't like it much. It doesn't look very fast and I don't understand why I have to put the decimal into a string just to manipulate it.
I start to believe that there is no clean solution for such a simple task.
回答1:
A decimal
occupies 128 bits (16 bytes), of which 1 bit is used for the sign, 96 bits (12 bytes) are used for the actual value and 5 bits are used to store the position of the decimal point.
When the C# compiler sees 1M
, it parses it as {sign: 0, value: 1, point: 0}
, while 1.0M
is parsed as {sign: 0, value: 10, point: 1}
. However, both represent the same value (1M == 1.0M
returns true), and another parser could easily have mapped both 1M
and 1.0M
to {sign: 0, value: 1, point: 0}
.
What happens when you add 1M
and 0.1M
together? 1M
is {sign: 0, value: 1, point: 0}
and 0.1M
is {sign: 0, value: 1, point: 1}
, so we have two numbers with different precision. That's no problem however: we can move the point in 1M
by adding 1
to its point and by multiplying its value by 10
: {sign: 0, value: 10, point: 1}
. Now that both numbers have the same point position, we can add them together by simply adding up their values, which results in {sign: 0, value: 11, point: 1}
, which corresponds to 1.1M
.
So the internal representation of a decimal
does not affect the precision of its operations - the decimal point position is moved (and the value adjusted) whenever this becomes necessary.*
However, if for some reason your decimals absolutely must have a certain point position (and from what you've posted so far, I see no compelling reason - formatting is purely a display issue), then the easiest approach is to use the decimal(int, int, int, bool, byte)
constructor (or alternately decimal(int[])
). This allows you to pass in the value (as 3 integers), the sign (as a boolean) and the point position (as a byte). You will have to multiply the value yourself if you pass a point position higher than 0: 1.000M
must be constructed as new decimal(1000, 0, 0, false, 3)
, not as new decimal(1, 0, 0, false, 3)
(because that would give you 0.001M
).
*the point position is limited to [0-28], so a decimal
cannot represent numbers with more than 28 digits behind the dot. Also, the value has to be 'split' between digits in front of the dot and behind the dot, so very large numbers will put restrictions on the available precision, possibly cutting it down in favor of representing the digits in front of the dot.
回答2:
Probably not the answer you were hoping for, but it looks like you will have to use formatting with ToString(). I recommend you read the Remarks section in this MSDN link.
The last paragraph in Remarks states:
The scaling factor also preserves any trailing zeros in a Decimal number. Trailing zeros do not affect the value of a Decimal number in arithmetic or comparison operations. However, trailing zeros might be revealed by the ToString method if an appropriate format string is applied.
回答3:
As I understand from your comment, you want to avoid additional field for storing precision by storing in decimal value. Don't do this. It's abusing the framework and even if you successfully implement this, it can stop working in another framework version/mono/etc. This kind of programming makes your code base unreadable and hard to debug.
Just use your own type:
struct DecimalEx
{
public decimal Value;
public byte Precision;
}
It's cool and fun to fit couple of values in one simple data type, but if your sharing code with others, try avoiding that, or you will easily earn your special place in hell for that.
回答4:
The reason that, in Decimal
, adding 0.04m
to 0.06m
yields 0.10m
rather than 0.1m
is not that the trailing zero is meaningful, but instead an optimization based upon the facts that:
Adding numbers with the same number of digits after the decimal point is fast, but adjusting the number of digits after the decimal point is slow.
A value which is the sum of two numbers with some number of digits after the decimal point is likely to be added to more numbers with the same number of digits after the decimal point.
Because of #2, effort expended to remove extra zeroes following an arithmetic operation would more likely increase the effort required for a succeeding operation than reduce it.
Even if there isn't really supposed to be any semantic difference between 0.10m and 0.100m, having multiple bit patterns represent the same number without having any way to distinguish them could cause some annoyance and confusion, especially if it is ever necessary to troubleshoot bugs in the implementation.
Given the above, I would view the ToString()
trailing-zeroes behavior with Decimal as being more of a debugging aid than a usable feature.
来源:https://stackoverflow.com/questions/27269607/c-sharp-decimal-how-to-add-trailing-zeros