C# 查找十进制值中的小数位数,而不考虑区域性
我想知道是否有一种简洁而准确的方法来提取十进制值(作为int)中的小数位数,这样可以安全地跨不同的区域性信息使用 例如:C# 查找十进制值中的小数位数,而不考虑区域性,c#,decimal,cultureinfo,C#,Decimal,Cultureinfo,我想知道是否有一种简洁而准确的方法来提取十进制值(作为int)中的小数位数,这样可以安全地跨不同的区域性信息使用 例如: 19.0应返回1, 27.5999应返回4, 19.12应返回2, 等等 我编写了一个查询,在句点上拆分字符串以查找小数点: int priceDecimalPlaces = price.ToString().Split('.').Count() > 1 ? price.ToString().Split('.').ToList().
19.0应返回1,
27.5999应返回4,
19.12应返回2,
等等 我编写了一个查询,在句点上拆分字符串以查找小数点:
int priceDecimalPlaces = price.ToString().Split('.').Count() > 1
? price.ToString().Split('.').ToList().ElementAt(1).Length
: 0;
但我觉得这只适用于使用“.”作为十进制分隔符的区域,因此在不同的系统中非常脆弱。您可以使用不变量区域性
string priceSameInAllCultures = price.ToString(System.Globalization.CultureInfo.InvariantCulture);
另一种可能是这样做:
private int GetDecimals(decimal d, int i = 0)
{
decimal multiplied = (decimal)((double)d * Math.Pow(10, i));
if (Math.Round(multiplied) == multiplied)
return i;
return GetDecimals(d, i+1);
}
您可以尝试:
int priceDecimalPlaces =
price.ToString(System.Globalization.CultureInfo.InvariantCulture)
.Split('.')[1].Length;
我可能会用这个解决方案 但是,虽然Decimal结构没有获取小数位数的方法,但是可以调用提取二进制表示,然后使用整数值和小数位数计算小数位数 这可能比格式化为字符串要快,不过您必须处理大量的小数才能注意到差异 我将把实现留作练习。我曾经解决过这个问题:)
我昨天写了一个简洁的小方法,它还返回小数位数,而不必依赖任何字符串拆分或区域性,这是理想的:
public int GetDecimalPlaces(decimal decimalNumber) { //
try {
// PRESERVE:BEGIN
int decimalPlaces = 1;
decimal powers = 10.0m;
if (decimalNumber > 0.0m) {
while ((decimalNumber * powers) % 1 != 0.0m) {
powers *= 10.0m;
++decimalPlaces;
}
}
return decimalPlaces;
我在代码中使用以下机制
public static int GetDecimalLength(string tempValue)
{
int decimalLength = 0;
if (tempValue.Contains('.') || tempValue.Contains(','))
{
char[] separator = new char[] { '.', ',' };
string[] tempstring = tempValue.Split(separator);
decimalLength = tempstring[1].Length;
}
return decimalLength;
}
十进制输入=3.376;
var instring=input.ToString()
调用GetDecimalLength(instring)查找小数点后位数的最佳解决方案之一,如所示 在这里,我使用的部分来自STDB论坛文章: 在MSDN中,我们可以阅读以下说明: “十进制数是一种浮点值,它由一个符号和一个数字组成,其中每个数字的范围从0到9, 以及一个比例因子,用于指示分隔数值的整数部分和小数部分的浮点小数点的位置。” 而且: 十进制值的二进制表示法由1位符号、96位整数和用于除以96位整数的比例因子组成 并指定它的哪一部分是小数。比例因子隐式地是数字10,提升为0到28的指数。” 在内部级别,十进制值由四个整数值表示 有一个公开可用的GetBits函数用于获取内部表示。函数返回一个int[]数组:
[__DynamicallyInvokable]
public static int[] GetBits(decimal d)
{
return new int[] { d.lo, d.mid, d.hi, d.flags };
}
返回数组的第四个元素包含比例因子和符号。正如MSDN所说,比例因子隐含着数字10,它的指数范围从0到28。这正是我们需要的
因此,基于上述所有调查,我们可以构建我们的方法:
private const int SIGN_MASK = ~Int32.MinValue;
public static int GetDigits4(decimal value)
{
return (Decimal.GetBits(value)[3] & SIGN_MASK) >> 16;
}
这里使用符号屏蔽来忽略符号。在逻辑和之后,我们还将结果右移16位,以接收实际的比例因子。最后,该值表示小数点后的位数
请注意,这里MSDN还表示缩放因子还保留十进制数中的任何尾随零。尾随零不会影响算术或比较运算中十进制数的值。但是,如果应用了适当的格式字符串,ToString方法可能会显示尾随的零
这个解决方案看起来是最好的,但是等等,还有更多。通过,我们可以使用表达式构建对flags字段的直接访问,并避免构建int数组:
public delegate int GetDigitsDelegate(ref Decimal value);
public class DecimalHelper
{
public static readonly DecimalHelper Instance = new DecimalHelper();
public readonly GetDigitsDelegate GetDigits;
public readonly Expression<GetDigitsDelegate> GetDigitsLambda;
public DecimalHelper()
{
GetDigitsLambda = CreateGetDigitsMethod();
GetDigits = GetDigitsLambda.Compile();
}
private Expression<GetDigitsDelegate> CreateGetDigitsMethod()
{
var value = Expression.Parameter(typeof(Decimal).MakeByRefType(), "value");
var digits = Expression.RightShift(
Expression.And(Expression.Field(value, "flags"), Expression.Constant(~Int32.MinValue, typeof(int))),
Expression.Constant(16, typeof(int)));
//return (value.flags & ~Int32.MinValue) >> 16
return Expression.Lambda<GetDigitsDelegate>(digits, value);
}
}
这是获取十进制值小数点后位数的最快方法。因为提供的答案都不足以将神奇数字“-0.01f”转换为十进制。。i、 e:
GetDecimal((十进制)-0.01f)代码>
我只能假设3年前,一个巨大的“心灵放屁”病毒袭击了所有人:)
这似乎是一个解决这个邪恶而可怕的问题的有效方法,这个非常复杂的问题是计算小数点后的小数位-没有字符串,没有文化,不需要计算位,也不需要阅读数学论坛。。只是简单的三年级数学
public static class MathDecimals
{
public static int GetDecimalPlaces(decimal n)
{
n = Math.Abs(n); //make sure it is positive.
n -= (int)n; //remove the integer part of the number.
var decimalPlaces = 0;
while (n > 0)
{
decimalPlaces++;
n *= 10;
n -= (int)n;
}
return decimalPlaces;
}
}
这里的大多数人似乎没有意识到,十进制认为尾随的零对于存储和打印非常重要
因此,0.1m、0.10m和0.100m可能比较相等,它们的存储方式不同(分别为值/刻度1/1、10/2和100/3),并将通过ToString()
分别打印为0.1、0.10和0.100
因此,报告“精度太高”的解决方案实际上报告了正确的精度,以十进制
的术语来说
此外,基于数学的解决方案(如乘以10的幂)可能会非常慢(对于算术,十进制比双精度慢约40倍,并且您也不希望混合使用浮点,因为这可能会引入不精确性)。类似地,将强制转换为int
或long
作为截断的一种方法也容易出错(decimal
的范围比这两种方法的范围大得多-它基于96位整数)
虽然这样做并不优雅,但以下可能是获得精度的最快方法之一(定义为“小数位数,不包括尾随的零”):
公共静态整数精度(十进制d){
var text=d.ToString(System.Globalization.CultureInfo.InvariantCulture.TrimEnd('0');
var decpoint=text.IndexOf('.');
if(decpoint<0)
返回0;
返回text.Length-decpoint-1;
}
不变区域性保证将“.”作为小数点,尾部的零将被修剪,然后只需查看小数点后保留了多少个位置(如果有)
编辑:将返回类型更改为int依赖小数的内部表示并不酷
这个怎么样:
decimal value = 3.14159m;
int digits = DecimalHelper.Instance.GetDigits(ref value);
public static class MathDecimals
{
public static int GetDecimalPlaces(decimal n)
{
n = Math.Abs(n); //make sure it is positive.
n -= (int)n; //remove the integer part of the number.
var decimalPlaces = 0;
while (n > 0)
{
decimalPlaces++;
n *= 10;
n -= (int)n;
}
return decimalPlaces;
}
}
private static void Main(string[] args)
{
Console.WriteLine(1/3m); //this is 0.3333333333333333333333333333
Console.WriteLine(1/3f); //this is 0.3333333
Console.WriteLine(MathDecimals.GetDecimalPlaces(0.0m)); //0
Console.WriteLine(MathDecimals.GetDecimalPlaces(1/3m)); //28
Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)(1 / 3f))); //7
Console.WriteLine(MathDecimals.GetDecimalPlaces(-1.123m)); //3
Console.WriteLine(MathDecimals.GetDecimalPlaces(43.12345m)); //5
Console.WriteLine(MathDecimals.GetDecimalPlaces(0)); //0
Console.WriteLine(MathDecimals.GetDecimalPlaces(0.01m)); //2
Console.WriteLine(MathDecimals.GetDecimalPlaces(-0.001m)); //3
Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)-0.00000001f)); //8
Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)0.0001234f)); //7
Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)0.01f)); //2
Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)-0.01f)); //2
}
public static int PrecisionOf(decimal d) {
var text = d.ToString(System.Globalization.CultureInfo.InvariantCulture).TrimEnd('0');
var decpoint = text.IndexOf('.');
if (decpoint < 0)
return 0;
return text.Length - decpoint - 1;
}
int CountDecimalDigits(decimal n)
{
return n.ToString(System.Globalization.CultureInfo.InvariantCulture)
//.TrimEnd('0') uncomment if you don't want to count trailing zeroes
.SkipWhile(c => c != '.')
.Skip(1)
.Count();
}
public static int GetNumberOfDecimalPlaces(decimal value, int maxNumber)
{
if (maxNumber == 0)
return 0;
if (maxNumber > 28)
maxNumber = 28;
bool isEqual = false;
int placeCount = maxNumber;
while (placeCount > 0)
{
decimal vl = Math.Round(value, placeCount - 1);
decimal vh = Math.Round(value, placeCount);
isEqual = (vl == vh);
if (isEqual == false)
break;
placeCount--;
}
return Math.Min(placeCount, maxNumber);
}
((SqlDecimal)(decimal)yourValue).Scale
private int GetDecimals(decimal n, int decimals = 0)
{
return n % 1 != 0 ? GetDecimals(n * 10, decimals + 1) : decimals;
}
// NOTE: Do not change the order in which these fields are declared. The
// native methods in this class rely on this particular order.
private int flags;
private int hi;
private int lo;
private int mid;
internal static class DecimalExtensions
{
public static byte GetScale(this decimal value)
{
unsafe
{
byte* v = (byte*)&value;
return v[2];
}
}
}
[StructLayout(LayoutKind.Explicit)]
public struct DecimalHelper
{
const byte k_SignBit = 1 << 7;
[FieldOffset(0)]
public decimal Value;
[FieldOffset(0)]
public readonly uint Flags;
[FieldOffset(0)]
public readonly ushort Reserved;
[FieldOffset(2)]
byte m_Scale;
public byte Scale
{
get
{
return m_Scale;
}
set
{
if(value > 28)
throw new System.ArgumentOutOfRangeException("value", "Scale can't be bigger than 28!")
m_Scale = value;
}
}
[FieldOffset(3)]
byte m_SignByte;
public int Sign
{
get
{
return m_SignByte > 0 ? -1 : 1;
}
}
public bool Positive
{
get
{
return (m_SignByte & k_SignBit) > 0 ;
}
set
{
m_SignByte = value ? (byte)0 : k_SignBit;
}
}
[FieldOffset(4)]
public uint Hi;
[FieldOffset(8)]
public uint Lo;
[FieldOffset(12)]
public uint Mid;
public DecimalHelper(decimal value) : this()
{
Value = value;
}
public static implicit operator DecimalHelper(decimal value)
{
return new DecimalHelper(value);
}
public static implicit operator decimal(DecimalHelper value)
{
return value.Value;
}
}
string number = "123.456789"; // Convert to string
int length = number.Substring(number.IndexOf(".") + 1).Length; // 6
private int GetSignificantDecimalPlaces(decimal number, bool trimTrailingZeros = true)
{
string stemp = Convert.ToString(number);
if (trimTrailingZeros)
stemp = stemp.TrimEnd('0');
return stemp.Length - 1 - stemp.IndexOf(
Application.CurrentCulture.NumberFormat.NumberDecimalSeparator);
}