我一直在寻找某种方法来确定C#中小数的比例和精度,这导致了几个SO问题,但它们似乎没有正确的答案,或者有误导性的标题(它们真的是关于SQL的服务器或其他一些数据库,而不是C#),或任何答案。我认为,以下帖子与我之后的内容最为接近,但即使这样也是错误的:
Determine the decimal precision of an input number
首先,似乎对比例和精度之间的差异存在一些混淆。每Google(根据MSDN):
精确度是数字中的位数。 Scale是数字中小数点右边的位数。
话虽如此,数字12345.67890M
的标度为5,精度为10.我没有发现一个能在C#中准确计算出来的代码示例。
我想制作两个辅助方法decimal.Scale()
和decimal.Precision()
,以便通过以下单元测试:
[TestMethod]
public void ScaleAndPrecisionTest()
{
//arrange
var number = 12345.67890M;
//act
var scale = number.Scale();
var precision = number.Precision();
//assert
Assert.IsTrue(precision == 10);
Assert.IsTrue(scale == 5);
}
但我还没有找到一个可以执行此操作的代码段,虽然有些人建议使用decimal.GetBits()
,而其他人已经说过,将其转换为字符串并解析它。
将它转换为字符串并解析它在我看来是一个糟糕的想法,甚至忽略了小数点的本地化问题。然而,GetBits()
方法背后的数学对我来说就像希腊语。
有人可以描述在C#的decimal
值中确定比例和精度的计算结果吗?
答案 0 :(得分:4)
这是使用GetBits()
function:
decimal x = 12345.67890M;
int[] bits = decimal.GetBits(x);
byte scale = (byte) ((bits[3] >> 16) & 0x7F);
我能想到获得精度的最好方法是删除分数点(即使用Decimal Constructor重构十进制数而不使用上面提到的比例)然后使用对数:
decimal x = 12345.67890M;
int[] bits = decimal.GetBits(x);
//We will use false for the sign (false = positive), because we don't care about it.
//We will use 0 for the last argument instead of bits[3] to eliminate the fraction point.
decimal xx = new Decimal(bits[0], bits[1], bits[2], false, 0);
int precision = (int)Math.Floor(Math.Log10((double)xx)) + 1;
现在我们可以将它们加入扩展名:
public static class Extensions{
public static int GetScale(this decimal value){
if(value == 0)
return 0;
int[] bits = decimal.GetBits(value);
return (int) ((bits[3] >> 16) & 0x7F);
}
public static int GetPrecision(this decimal value){
if(value == 0)
return 0;
int[] bits = decimal.GetBits(value);
//We will use false for the sign (false = positive), because we don't care about it.
//We will use 0 for the last argument instead of bits[3] to eliminate the fraction point.
decimal d = new Decimal(bits[0], bits[1], bits[2], false, 0);
return (int)Math.Floor(Math.Log10((double)d)) + 1;
}
}
这是一个fiddle。
答案 1 :(得分:1)
首先,解决“物理”问题:你将如何决定哪些数字是重要的。事实是,"precision" has no physical meaning unless you know or guess the absolute error。
现在,有两种基本方法可以确定每个数字(因此,它们的数量):
第二种方法无法检测小数部分中的尾随零(根据您对“物理”问题的回答,可能会有或可能不重要),所以除非有要求,否则我不会覆盖它。
对于第一个,在Decimal
's interface中,我看到了两种获取部分的基本方法:ToString()
(一些重载)和GetBits()
。
ToString(String, IFormatInfo)
实际上是一种可靠的方式,因为您可以准确定义格式。
F
specifier并传递您已手动设置culture-neutral NumberFormatInfo
的all the fields that affect this particular format。
NumberDecimalDigits
字段:测试显示它是最小号码 - 因此将其设置为0
(文档不清楚),以及尾随如果有任何 GetBits()
结果的语义清楚地记录在its MSDN article中(所以感叹“这对我来说是希腊语”不会这样做;))。使用ILSpy进行反编译表明它实际上是对象的原始数据字段的元组:
public static int[] GetBits(decimal d)
{
return new int[]
{
d.lo,
d.mid,
d.hi,
d.flags
};
}
他们的语义是:
|high|mid|low|
- 二进制数字(96位),解释为整数(=右对齐)flags
:
16
到23
- “10的幂除以整数”(=小数位数)
(flags>>16)&0xFF
是此字段的原始值)31
- 签名(与我们无关)如您所见,这与IEEE 754 floats非常相似。
因此,小数位数 指数值。 总数字数为 the number of digits in the decimal representation of the 96-bit integer 。
答案 2 :(得分:-2)
Racil的回答为您提供了decimal
的内部比例值的正确值,尽管如果内部表示发生变化,它将会很有趣。
在当前格式中,decimal
的精度部分固定为96位,取决于数字,在28到29位十进制数之间。所有.NET decimal
值都共享此精度。由于这是常数,因此没有内部值可用于确定它。
你所看到的是数字位数,我们可以从字符串表示中轻松确定。我们也可以同时获得比例或至少使用相同的方法。
public struct DecimalInfo
{
public int Scale;
public int Length;
public override string ToString()
{
return string.Format("Scale={0}, Length={1}", Scale, Length);
}
}
public static class Extensions
{
public static DecimalInfo GetInfo(this decimal value)
{
string decStr = value.ToString().Replace("-", "");
int decpos = decStr.IndexOf(".");
int length = decStr.Length - (decpos < 0 ? 0 : 1);
int scale = decpos < 0 ? 0 : length - decpos;
return new DecimalInfo { Scale = scale, Length = length };
}
}