以数学方式确定小数值的精度和小数位数
Mathematically determine the precision and scale of a decimal value
我一直在寻找一些方法来确定 C# 中小数的小数位数和精度,这让我想到了几个 SO 问题,但是 none 其中似乎有正确的答案,或者有误导性的标题(它们实际上是关于 SQL 服务器或其他一些数据库,而不是 C#),或者任何答案。以下 post 我认为是最接近我所追求的,但即使这样似乎也是错误的:
Determine the decimal precision of an input number
首先,似乎对比例和精度之间的区别有些混淆。根据 Google(根据 MSDN):
Precision is the number of digits in a number. Scale is the number of digits to the right of the decimal point in a number.
话虽如此,数字 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
值中确定小数位数和精度的计算方式吗?
这是使用 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.
首先,解决 "physical" 问题:您将如何决定哪些数字有效。 事实是,"precision" has no physical meaning unless you know or guess the absolute error。
现在,有两种基本方法可以确定每个数字(以及它们的编号):
- 获取+解读有意义的部分
- 数学计算
第二种方法无法检测小数部分的尾随零(这可能重要也可能不重要,具体取决于您对 "physical" 问题的回答),因此除非有人要求,否则我不会介绍它。
对于第一个,在 Decimal
's interface, I see 2 basic methods to get the parts: ToString()
(a few overloads) and GetBits()
.
ToString(String, IFormatInfo)
实际上是一种可靠的方法,因为您可以准确定义格式。
- 例如使用
F
specifier and pass a culture-neutral NumberFormatInfo
in which you have manually set all the fields that affect this particular format.
- 关于
NumberDecimalDigits
字段:测试表明它是 最小 数字 - 因此将其设置为 0
(文档对此不清楚), - 如果有 ,尾随的零都可以打印出来
GetBits()
result 的语义在 its MSDN article (so laments like "it's Greek to me" won't do ;) ). Decompiling with 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
- "the power of 10 to divide the integer number"(=小数位数)
- (因此
(flags>>16)&0xFF
是该字段的原始值)
- 位
31
- 签名(与我们无关)
如您所见,这与 IEEE 754 floats.
非常相似
所以,小数位数是指数值。 总位数为the number of digits in the decimal representation of the 96-bit integer。
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 };
}
}
我一直在寻找一些方法来确定 C# 中小数的小数位数和精度,这让我想到了几个 SO 问题,但是 none 其中似乎有正确的答案,或者有误导性的标题(它们实际上是关于 SQL 服务器或其他一些数据库,而不是 C#),或者任何答案。以下 post 我认为是最接近我所追求的,但即使这样似乎也是错误的:
Determine the decimal precision of an input number
首先,似乎对比例和精度之间的区别有些混淆。根据 Google(根据 MSDN):
Precision is the number of digits in a number. Scale is the number of digits to the right of the decimal point in a number.
话虽如此,数字 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
值中确定小数位数和精度的计算方式吗?
这是使用 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.
首先,解决 "physical" 问题:您将如何决定哪些数字有效。 事实是,"precision" has no physical meaning unless you know or guess the absolute error。
现在,有两种基本方法可以确定每个数字(以及它们的编号):
- 获取+解读有意义的部分
- 数学计算
第二种方法无法检测小数部分的尾随零(这可能重要也可能不重要,具体取决于您对 "physical" 问题的回答),因此除非有人要求,否则我不会介绍它。
对于第一个,在 Decimal
's interface, I see 2 basic methods to get the parts: ToString()
(a few overloads) and GetBits()
.
ToString(String, IFormatInfo)
实际上是一种可靠的方法,因为您可以准确定义格式。- 例如使用
F
specifier and pass a culture-neutralNumberFormatInfo
in which you have manually set all the fields that affect this particular format.- 关于
NumberDecimalDigits
字段:测试表明它是 最小 数字 - 因此将其设置为0
(文档对此不清楚), - 如果有 ,尾随的零都可以打印出来
- 关于
- 例如使用
GetBits()
result 的语义在 its MSDN article (so laments like "it's Greek to me" won't do ;) ). Decompiling with 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
- "the power of 10 to divide the integer number"(=小数位数)- (因此
(flags>>16)&0xFF
是该字段的原始值)
- (因此
- 位
31
- 签名(与我们无关)
- 位
如您所见,这与 IEEE 754 floats.
非常相似所以,小数位数是指数值。 总位数为the number of digits in the decimal representation of the 96-bit integer。
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 };
}
}