How to round System.Decimal in .Net to a series of significant digits

I have a System.Decimal number

+0.00123456789

and I want to round to 3 significant digits. I expect

0.00123

with behavior that should be rounding rather than truncation. Is there a bulletproof way to do this in .Net?

+4
source share
4 answers

You can try this ... But I can’t guarantee anything ... I wrote and tested after 20 minutes and based on the Pyrolistic code from fooobar.com/questions/72830 / ... There is a big difference in that it uses long for a variable shifted (since a double has an accuracy of 15-16 digits and long is 18-19, so long enough), while I use decimal (because decimal has an accuracy 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 do not trust (int)Math.Ceiling(Math.Log10((double) , you can 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 wrote it after another 20 minutes (and yes, I tested all Math.Pow((double), p) for all values ​​-28 - +28). It seems to work and it is only 20% slower than the C # formula based on double s). It is based on a static array of guidance and BinarySearch . Fortunately, BinarySearch already “offers” the following element when it cannot find one :-), so Ceiling is free.

+5
source

SqlDecimal has quick methods for calculating and adjusting accuracy.

 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; } 
+1
source

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

-one
source

in the example:

 decimal a = 1.9999M; decimal b = Math.Round(a, 2); //returns 2 
-one
source

Source: https://habr.com/ru/post/1496116/


All Articles