Round a decimal to the first decimal position that is not zero

I want to shorten a number to the first significant digit that is not 0. The digits behind should be rounded.

Examples:

0.001 -> 0.001
0.00367 -> 0.004
0.00337 -> 0.003
0.000000564 -> 0.0000006
0.00000432907543029 -> 0.000004


Currently I have the following procedure:

if (value < (decimal) 0.01)
{
value = Math.Round(value, 4);
}


Note:


numbers will always be positive
the number of significant digits will always be 1
values larger 0.01 will always be rounded to two decimal places, hence the if < 0.01


As you can see from the examples above, a rounding to 4 Decimal places might not be enough and the value might vary greatly.

I would declare precision variable and use a loop iteration multiplies that variable by 10 with the original value it didn't hit, that precision will add 1.

then use precision variable be Math.Round second parameter.

static decimal RoundFirstSignificantDigit(decimal input) {
int precision = 0;
var val = input;
while (Math.Abs(val) < 1)
{
val *= 10;
precision++;
}
return Math.Round(input, precision);
}


I would write an extension method for this function.

public static class FloatExtension
{
public static decimal RoundFirstSignificantDigit(this decimal input)
{
int precision = 0;
var val = input;
while (Math.Abs(val) < 1)
{
val *= 10;
precision++;
}
return Math.Round(input, precision);
}
}


then use like

decimal input = 0.00001;
input.RoundFirstSignificantDigit();


c# online

Result

(-0.001m).RoundFirstSignificantDigit() -0.001
(-0.00367m).RoundFirstSignificantDigit() -0.004
(0.000000564m).RoundFirstSignificantDigit() 0.0000006
(0.00000432907543029m).RoundFirstSignificantDigit() 0.000004


Something like that ?

public decimal SpecialRound(decimal value)
{
int posDot = value.ToString().IndexOf('.'); // Maybe use something about cultural (in Fr it's ",")
if(posDot == -1)
return value;

int posFirstNumber = value.ToString().IndexOfAny(new char[9] {'1', '2', '3', '4', '5', '6', '7', '8', '9'}, posDot);

return Math.Round(value, posFirstNumber);
}


var value = 0.000000564;

int cnt = 0;
bool hitNum = false;
var tempVal = value;
while (!hitNum)
{
if(tempVal > 1)
{
hitNum = true;
}
else
{
tempVal *= 10;
cnt++;
}
}

var newValue = (decimal)Math.Round(value, cnt);


code is from R but the algo should be obvious

> x = 0.0004932
> y = log10(x)
> z = ceiling(y)
> a = round(10^(y-z),1)
> finally = a*10^(z)
> finally
[1] 5e-04


the following was basically already provided by Benjamin K

At the risk of being labelled a complete wacko, I would cheerfully announce that regexp is your friend. Convert your number to a char string, search for the location of the first char that is neither "." nor "0" , grab the char at that location and the next char behind it, convert them to a number, round, and (because you were careful), multiply the result by $10^{-(number of zeros you found between "." and the first number)}$

Another approach

decimal RoundToFirstNonNullDecimal(decimal value)
{
var nullDecimals = value.ToString().Split('.').LastOrDefault()?.TakeWhile(c => c == '0').Count();
var roundTo = nullDecimals.HasValue && nullDecimals >= 1 ? nullDecimals.Value + 1 : 2;
return Math.Round(value, roundTo);
}


Result

Console.WriteLine(RoundToFirstNonNullDecimal(0.001m)); 0.001
Console.WriteLine(RoundToFirstNonNullDecimal(0.00367m)); 0.004
Console.WriteLine(RoundToFirstNonNullDecimal(0.000000564m)); 0.0000006
Console.WriteLine(RoundToFirstNonNullDecimal(0.00000432907543029m)); 0.000004
Console.WriteLine(RoundToFirstNonNullDecimal(0.12m)); 0.12
Console.WriteLine(RoundToFirstNonNullDecimal(1.232m)); 1.23
Console.WriteLine(RoundToFirstNonNullDecimal(7)); 7.00

Comments

Popular posts from this blog

Meaning of `{}` for return expression

Get current scroll position of ScrollView in React Native

React Native - Image Cache