Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Round a decimal number 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.

like image 839
julian bechtold Avatar asked Sep 21 '18 09:09

julian bechtold


People also ask

What does it mean to round to zero decimal places?

Rounding to zero decimal places means rounding so there are no numbers after the decimal point, which means rounding to the nearest 1. In the previous example of 362.715, rounding to 0 decimal places would mean rounding to the nearest 1. So, 362.715 → 363.000 → 363.

What does it mean to round to the first decimal?

When you round to the first decimal place, or to the nearest tenth, the number in the hundredth point place will determine whether you round up or down. If the hundredths digit is a number between 5 and 9, round up to the nearest whole number.

How do you round a decimal number?

There are certain rules to follow when rounding a decimal number. Put simply, if the last digit is less than 5, round the previous digit down. However, if it's 5 or more than you should round the previous digit up. So, if the number you are about to round is followed by 5, 6, 7, 8, 9 round the number up.


4 Answers

I would declare precision variable and use a 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.

I had added some modifications to the method which can support not only zero decimal points but also all decimal numbers.

static decimal RoundFirstSignificantDigit(decimal input) {

    if(input == 0)
       return input;

    int precision = 0;
    var val = input - Math.Round(input,0);
    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)
    {
        if(input == 0)
            return input;

        int precision = 0;
        var val = input - Math.Round(input,0);
        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
like image 164
D-Shih Avatar answered Oct 23 '22 19:10

D-Shih


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);
    }
like image 37
Benjamin K Avatar answered Oct 23 '22 21:10

Benjamin K


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);
like image 1
ManishM Avatar answered Oct 23 '22 19:10

ManishM


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)}$

like image 1
Carl Witthoft Avatar answered Oct 23 '22 19:10

Carl Witthoft