How to round System.Decimal in .Net to a number of significant figures

后端 未结 4 554
爱一瞬间的悲伤
爱一瞬间的悲伤 2021-01-19 15:00

I have a System.Decimal number

0.00123456789

and I wish to round to 3 significant figures. I expect

0.00123

with the behaviour to be a roundi

相关标签:
4条回答
  • 2021-01-19 15:34

    in example:

    decimal a = 1.9999M;
    decimal b = Math.Round(a, 2); //returns 2
    
    0 讨论(0)
  • 2021-01-19 15:39

    SqlDecimal has fast methods to calculate and adjust precision.

    public static decimal RoundToSignificantFigures(decimal num, int n)
    {
        SqlDecimal value = New SqlDecimal(num);
        if (value.Precision > num){
            int digits = num - (value.Precision - value.Scale);
            value = SqlDecimal.Round(value, digits);
            value = SqlDecimal.AdjustScale(value, (digits>0 ? digits : 0) - dstValue.Scale, True);
        }
        return value.Value;
    }
    
    0 讨论(0)
  • 2021-01-19 15:45

    try this ... decimalVar.ToString ("#.##");

    0 讨论(0)
  • 2021-01-19 15:54

    You can try this... But I don't guarantee anything... Written and tested in 20 minutes and based on Pyrolistical's code from https://stackoverflow.com/a/1581007/613130 There is a big difference in that he uses a long for the shifted variable (because a double has a precision of 15-16 digits, while a long has 18-19, so a long is enough), while I use a decimal (because decimal has a precision of 28-29 digits).

    public static decimal RoundToSignificantFigures(decimal num, int n)
    {
        if (num == 0)
        {
            return 0;
        }
    
        // We are only looking for the next power of 10... 
        // The double conversion could impact in some corner cases,
        // but I'm not able to construct them...
        int d = (int)Math.Ceiling(Math.Log10((double)Math.Abs(num)));
        int power = n - d;
    
        // Same here, Math.Pow(10, *) is an integer number
        decimal magnitude = (decimal)Math.Pow(10, power);
    
        // I'm using the MidpointRounding.AwayFromZero . I'm not sure
        // having a MidpointRounding.ToEven would be useful (is Banker's
        // rounding used for significant figures?)
        decimal shifted = Math.Round(num * magnitude, 0, MidpointRounding.AwayFromZero);
        decimal ret = shifted / magnitude;
    
        return ret;
    }
    

    If you don't trust the (int)Math.Ceiling(Math.Log10((double) you could use this:

    private static readonly decimal[] Pows = Enumerable.Range(-28, 57)
        .Select(p => (decimal)Math.Pow(10, p))
        .ToArray();
    
    public static int Log10Ceiling(decimal num)
    {
        int log10 = Array.BinarySearch(Pows, num);
        return (log10 >= 0 ? log10 : ~log10) - 28;
    }
    

    I have written it in another 20 minutes (and yes, I have tested all the Math.Pow((double), p) for all the values -28 - +28). It seems to work, and it's only 20% slower than the C# formula based on doubles). It's based on a static array of pows and a BinarySearch. Luckily the BinarySearch already "suggests" the next element when it can't find one :-), so the Ceiling is for free.

    0 讨论(0)
提交回复
热议问题