以数学方式确定十进制值的精度和小数位数
本文关键字:精度 小数 十进制 方式确 | 更新日期: 2023-09-27 18:20:14
我一直在寻找某种方法来确定 C# 中小数的小数位数和精度,这让我想到了几个 SO 问题,但它们似乎都没有正确答案,或者有误导性的标题(它们实际上是关于 SQL Server 或其他一些数据库,而不是 C#(,或者根本没有任何答案。我认为,以下帖子最接近我所追求的,但即使这样似乎也是错误的:
确定输入数字的小数精度
首先,对于比例和精度之间的区别似乎存在一些混淆。每个谷歌(每个MSDN(:
精度是数字中的位数。小数位数是数字中小数点右侧的位数。
话虽如此,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()
函数获取比例的方式:
decimal x = 12345.67890M;
int[] bits = decimal.GetBits(x);
byte scale = (byte) ((bits[3] >> 16) & 0x7F);
我能想到的获得精度的最好方法是删除分数点(即使用十进制构造函数重建没有上述小数位数的十进制数(,然后使用对数:
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;
}
}
这是一个小提琴。
首先,解决"物理"问题:你将如何决定哪些数字是重要的。事实是,"精度"没有物理意义,除非你知道或猜测绝对误差。
现在,有两种基本方法可以确定每个数字(以及它们的数量(:
- 获取+解释有意义的部分
- 数学计算
第二种方法无法检测小数部分中的尾随零(根据您对"物理"问题的回答,这可能很重要,也可能不重要(,因此除非要求,否则我不会涵盖它。
对于第一个,在Decimal
的界面中,我看到了 2 种获取零件的基本方法:ToString()
(一些重载(和 GetBits()
。
-
ToString(String, IFormatInfo)
实际上是一种可靠的方法,因为您可以准确定义格式。- 例如,使用
F
说明符并传递一个非特定区域性NumberFormatInfo
,在该中,您手动设置了影响此特定格式的所有字段。- 关于
NumberDecimalDigits
字段:测试显示它是最小数字 - 因此将其设置为0
(文档对此不清楚(,- 如果有的话,尾随零打印得很好
- 关于
- 例如,使用
-
结果的语义在其 MSDN 文章中清楚地记录
GetBits()
(因此像"对我来说是希腊语"这样的哀叹不会;)(。使用 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浮点数非常相似。
因此,小数位数是指数值。总位数是 96 位整数的十进制表示形式的位数。
-
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 };
}
}