nino
nino

Reputation: 678

How to modify a float to display at least n digits

In bash, how can I modify a float so that the part coming before the dot is having at least two digits?

I want to make numbers in Column A be displayed as in Column B:

A (Current)   B (Desired)
-----         ------
8.456         08.456
4.19          04.19
3.5           03.5

I did a lot of searches, but most of what I found talked about how to display numbers with N decimals (e.g. 17.7647 to 17.76) which was of no help.

Upvotes: 2

Views: 219

Answers (3)

David C. Rankin
David C. Rankin

Reputation: 84579

You can expand a bit and take the great answer by @anubhava a bit further by allowing the width on both the real-part and fractional-part to be set and fully validating the input and handling any errors in your function. The approach though is the same, using the printf format string is key.

For example your function can take the floating-point number as the first argument, the width of the real-part as the second, and optionally take the width of the fractional-part as the third, e.g.

#!/bin/bash

formatfloat () {
    [ -z "$1" -o -z "$2" ] && { ## validate at least 2 arguments given
        printf "error: formatfloat() insufficient arguments.\n" >&2
        printf "usage: formatfloat() float width_real [width_fract]\n" >&2
        return 1;
    }
    [[ $1 =~ ^-*[[:digit:]]*[.][[:digit:]]* ]] || { ## validate float
        printf "error: formatfloat() not a valid floating point number.\n" >&2
        return 1
    }
    [ "$2" -eq "$2" > /dev/null ] || {  ## validate width is an integer
        printf "error: formatfloat() width_real not an integer.\n" >&2
        return 1
    }
    local realpart="${1%.*}"    ## parameter expansion separates real/fraction parts
    local fractpart="${1#*.}"
    local width_real="$2"
    
    if [ -n "$3" ]; then                    ## if fractional part width given
        [ "$3" -eq "$3" > /dev/null ] || {  ## validate it is an integer
            printf "error: formatfloat() width_fract not an integer.\n" >&2
            return 1
        }
        local width_fract="$3"  ## output setting both widths
        local fppad="$((width_fract - ${#fractpart}))"  ## needed fract part rt-pad
        [ "$fppad" -le 0 ] && { ## no padding needed
            printf "%0*d.%d\n" $width_real $realpart $fractpart
            return 0
        }
        for ((i = 0; i < fppad; i++)); do   ## add padding to fract part
            fractpart="${fractpart}0"
        done
        printf "%0*d.%*d\n" $width_real $realpart $width_fract $fractpart
    else                        ## output setting real-part width
        printf "%0*d.%d\n" $width_real $realpart $fractpart
    fi
    
    return 0
}

A short example with your data could be:

printf "A (Current)   B (Desired)\n-----         ------\n"

for n in 8.456 4.19 3.5; do
    printf "%-14s%s\n" "$n" "$(formatfloat "$n" 2)"
done

Output

$ bash formatfloat.sh
A (Current)   B (Desired)
-----         ------
8.456         08.456
4.19          04.19
3.5           03.5

Setting Width of Both


for n in 8.456 4.19 3.5; do
    printf "%-14s%s\n" "$n" "$(formatfloat "$n" 2 3)"
done

Output

$ bash formatfloat.sh
A (Current)   B (Desired)
-----         ------
8.456         08.456
4.19          04.190
3.5           03.500

Upvotes: 2

anubhava
anubhava

Reputation: 785631

Here is a utility function in bash to achieve this using printf:

fpad() {
   local n="${1?needs an argument}"
   [[ $n == [0-9].* ]] && echo "0$n" || echo "$n"
}

Use it as:

fpad "8.456"
08.456

fpad "4.19"
04.19

fpad "3.5"
03.5

fpad "13.25"
13.25

fpad "1325"
1325

Using Glen's suggestion with read:

fpad() {
   local n="${1?needs an argument}"
   local num frac
   IFS=. read num frac <<< "$n"
   printf '%02d.%d\n' "$num" "$frac"
}

Upvotes: 5

KamilCuk
KamilCuk

Reputation: 141523

how can I modify a float so that the part coming before the dot is having at least two digits.

There is no utility for that - write your own code for doing that. You would:

  • count the number of digits before comma
  • if the number of digits is less then 2
    • if the number of digits is one
      • add one leading zero
    • if zero
      • add two leading zeros

I could also see a sed with two expressions that each one matches with a regex the number of digits before a comma.

Upvotes: 0

Related Questions