1

In Mac terminal, I would like to round a large number.

For example,

At 10^13th place:
1234567812345678 --> 1230000000000000

Or at 10^12th place:
1234567812345678 --> 1235000000000000

So I would like to specify the place, and then get the rounded number. How do I do this?

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
Isaac
  • 13
  • 4
  • That looks more like truncation than rounding, and could be done with character substitutions using regexes. – mpez0 Apr 17 '16 at 18:48

3 Answers3

2

You can use arithmetic expansion:

$ val=1234567812345678
$ echo $(( ${val: -13:1} < 5 ? val - val % 10**13 : val - val % 10**13 + 10**13 ))
1230000000000000
$ echo $(( ${val: -12:1} < 5 ? val - val % 10**12 : val - val % 10**12 + 10**12 ))
1235000000000000

This checks if the most significant removed digit is 5 or greater, and if it is, the last significant unremoved digit is increased by one; then we subtract the division remainder from the (potentially modified) initial value.

If you don't want to have to write it this way, you can wrap it in a little function:

round () {
    echo $(( ${1: -$2:1} < 5 ? $1 - $1 % 10**$2 : $1 - $1 % 10**$2 + 10**$2 ))
}

which can then be used like this:

$ round "$val" 13
1230000000000000
$ round "$val" 12
1235000000000000

Notice that quoting $val isn't strictly necessary here, it's just a good habit.

If the one-liner is too cryptic, this is a more readable version of the same:

round () {
    local rounded=$(( $1 - $1 % 10**$2 ))   # Truncate

    # Check if most significant removed digit is >= 5
    if (( ${1: -$2:1} >= 5 )); then
        (( rounded += 10**$2 ))
    fi
    echo $rounded
}

Apart from arithmetic expansion, this also uses parameter expansion to get a substring: ${1: -$2:1} stands for "take $1, count $2 from the back, take one character". There has to be a space before -$2 (or is has to be in parentheses) because otherwise it would be interpreted as a different expansion, checking if $1 is unset or null, which we don't want.

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
1

awk's [s]printf function can do rounding for you, within the limits of double-precision floating-point arithmetic:

$ for p in 13 12; do 
  awk -v p="$p" '{ n = sprintf("%.0f", $0 / 10^p); print n * 10^p }' <<<1234567812345678
done
1230000000000000
1235000000000000

For a pure bash implementation, see Benjamin W.'s helpful answer.

Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775
0

Actually, if you want to round to n significant digits you might be best served by mixing up traditional math and strings.

Serious debugging is left to the student, but this is what I quickly came up with for bash shell and hope MAC is close enough:

function rounder
{
    local value=$1;
    local digits=${2:-3};

    local zeros="$( eval "printf '0%.0s' {1..$digits}" )"; #proper zeros
        # a bit of shell magic that repats the '0' $digits times.

    if (( value > 1$zeros )); then
        # large enough to require rounding
        local length=${#value};
        local digits_1=$(( $digits + 1 ));      #digits + 1
        local tval="${value:0:$digits_1}"; #leading digits, plus one
        tval=$(( $tval + 5 ));          #half-add
        local tlength=${#tval};         #check if carried a digit
        local zerox="";
        if (( tlength > length )); then
            zerox="0";
        fi
        value="${tval:0:$digits}${zeros:0:$((length-$digits))}$zerox";
    fi  

    echo "$value";
}

See how this can be done much shorter, but that's another exercise for the student.

Avoiding floating point math due to the inherit problems within.

All sorts of special cases, like negative numbers, are not covered.

Community
  • 1
  • 1
Gilbert
  • 3,740
  • 17
  • 19