Skip to main content
typo fix
Source Link
Toby Speight
  • 9.4k
  • 3
  • 32
  • 54

All the working above assumes that the radix character (also known as the decimal separator) is ., as in most English locales. Other locales use , instead, and some shells have a built-in printf that respects locale. In thisthese shells, you may need to set LC_NUMERIC=C to force the use of . as radix character, or write /usr/bin/printf to prevent use of the built-in version. This latter is complicated by the fact that (at least some versions) seem to always parse arguments using ., but print using the current locale settings.

All the working above assumes that the radix character (also known as the decimal separator) is ., as in most English locales. Other locales use , instead, and some shells have a built-in printf that respects locale. In this shells, you may need to set LC_NUMERIC=C to force the use of . as radix character, or write /usr/bin/printf to prevent use of the built-in version. This latter is complicated by the fact that (at least some versions) seem to always parse arguments using ., but print using the current locale settings.

All the working above assumes that the radix character (also known as the decimal separator) is ., as in most English locales. Other locales use , instead, and some shells have a built-in printf that respects locale. In these shells, you may need to set LC_NUMERIC=C to force the use of . as radix character, or write /usr/bin/printf to prevent use of the built-in version. This latter is complicated by the fact that (at least some versions) seem to always parse arguments using ., but print using the current locale settings.

Convert dc output to localised form for test - verified with de_DE
Source Link
Toby Speight
  • 9.4k
  • 3
  • 32
  • 54

(but see the section below on decimal separator and locale, and note that non-Bash printf need not support %f and %g).

radix=$(printf %.1f 0)
for i in $(seq 12 | sed -e 's/.*/dc -e "12k 1.234 10 & 6 -^*p"/e' -e "y/_._/$radix/")
do
    echo $i "->" $(round 2 $i)
done

(but see the section below on decimal separator and locale)

for i in $(seq 12 | sed -e 's/.*/dc -e "12k 1.234 10 & 6 -^*p"/e')
do
    echo $i "->" $(round 2 $i)
done

(but see the section below on decimal separator and locale, and note that non-Bash printf need not support %f and %g).

radix=$(printf %.1f 0)
for i in $(seq 12 | sed -e 's/.*/dc -e "12k 1.234 10 & 6 -^*p"/e' -e "y/_._/$radix/")
do
    echo $i "->" $(round 2 $i)
done
Let's just remove the shebang, since it's tagged bash
Source Link
Toby Speight
  • 9.4k
  • 3
  • 32
  • 54

This answer to the first linked question has the almost-throwaway line at the end:

See also %g for rounding to a specified number of significant digits.

So you can simply write

printf "%.2g" "$n"

(but see the section below on decimal separator and locale)

Examples:

$ printf "%.2g\n" 76543 0.0076543
7.7e+04
0.0077

Of course, you now have mantissa-exponent representation rather than pure decimal, so you'll want to convert back:

$ printf "%0.f\n" 7.7e+06
7700000

$ printf "%0.7f\n" 7.7e-06
0.0000077

Putting all this together, and wrapping it in a function:

#!/bin/sh

# Function round(precision, number)
round() {
    n=$(printf "%.${1}g" "$2")
    if [ "$n" != "${n#*e}" ]
    then
        f="${n##*e-}"
        test "$n" = "$f" && f= || f=$(( ${f#0}+$1-1 ))
        printf "%0.${f}f" "$n"
    else
        printf "%s" "$n"
    fi
}

(Note - this function is written in portable (POSIX) shell, but assumes that printf handles the floating-point conversions. Bash has a built-in printf that does, so you're okay here, and the GNU implementation also works, so most GNU/Linux systems can safely use Dash).

Test cases

for i in $(seq 12 | sed -e 's/.*/dc -e "12k 1.234 10 & 6 -^*p"/e')
do
    echo $i "->" $(round 2 $i)
done

Test results

.000012340000 -> 0.000012
.000123400000 -> 0.00012
.001234000000 -> 0.0012
.012340000000 -> 0.012
.123400000000 -> 0.12
1.234 -> 1.2
12.340 -> 12
123.400 -> 120
1234.000 -> 1200
12340.000 -> 12000
123400.000 -> 120000
1234000.000 -> 1200000

A note on decimal separator and locale

All the working above assumes that the radix character (also known as the decimal separator) is ., as in most English locales. Other locales use , instead, and some shells have a built-in printf that respects locale. In this shells, you may need to set LC_NUMERIC=C to force the use of . as radix character, or write /usr/bin/printf to prevent use of the built-in version. This latter is complicated by the fact that (at least some versions) seem to always parse arguments using ., but print using the current locale settings.

This answer to the first linked question has the almost-throwaway line at the end:

See also %g for rounding to a specified number of significant digits.

So you can simply write

printf "%.2g" "$n"

(but see the section below on decimal separator and locale)

Examples:

$ printf "%.2g\n" 76543 0.0076543
7.7e+04
0.0077

Of course, you now have mantissa-exponent representation rather than pure decimal, so you'll want to convert back:

$ printf "%0.f\n" 7.7e+06
7700000

$ printf "%0.7f\n" 7.7e-06
0.0000077

Putting all this together, and wrapping it in a function:

#!/bin/sh

# Function round(precision, number)
round() {
    n=$(printf "%.${1}g" "$2")
    if [ "$n" != "${n#*e}" ]
    then
        f="${n##*e-}"
        test "$n" = "$f" && f= || f=$(( ${f#0}+$1-1 ))
        printf "%0.${f}f" "$n"
    else
        printf "%s" "$n"
    fi
}

Test cases

for i in $(seq 12 | sed -e 's/.*/dc -e "12k 1.234 10 & 6 -^*p"/e')
do
    echo $i "->" $(round 2 $i)
done

Test results

.000012340000 -> 0.000012
.000123400000 -> 0.00012
.001234000000 -> 0.0012
.012340000000 -> 0.012
.123400000000 -> 0.12
1.234 -> 1.2
12.340 -> 12
123.400 -> 120
1234.000 -> 1200
12340.000 -> 12000
123400.000 -> 120000
1234000.000 -> 1200000

A note on decimal separator and locale

All the working above assumes that the radix character (also known as the decimal separator) is ., as in most English locales. Other locales use , instead, and some shells have a built-in printf that respects locale. In this shells, you may need to set LC_NUMERIC=C to force the use of . as radix character, or write /usr/bin/printf to prevent use of the built-in version. This latter is complicated by the fact that (at least some versions) seem to always parse arguments using ., but print using the current locale settings.

This answer to the first linked question has the almost-throwaway line at the end:

See also %g for rounding to a specified number of significant digits.

So you can simply write

printf "%.2g" "$n"

(but see the section below on decimal separator and locale)

Examples:

$ printf "%.2g\n" 76543 0.0076543
7.7e+04
0.0077

Of course, you now have mantissa-exponent representation rather than pure decimal, so you'll want to convert back:

$ printf "%0.f\n" 7.7e+06
7700000

$ printf "%0.7f\n" 7.7e-06
0.0000077

Putting all this together, and wrapping it in a function:

# Function round(precision, number)
round() {
    n=$(printf "%.${1}g" "$2")
    if [ "$n" != "${n#*e}" ]
    then
        f="${n##*e-}"
        test "$n" = "$f" && f= || f=$(( ${f#0}+$1-1 ))
        printf "%0.${f}f" "$n"
    else
        printf "%s" "$n"
    fi
}

(Note - this function is written in portable (POSIX) shell, but assumes that printf handles the floating-point conversions. Bash has a built-in printf that does, so you're okay here, and the GNU implementation also works, so most GNU/Linux systems can safely use Dash).

Test cases

for i in $(seq 12 | sed -e 's/.*/dc -e "12k 1.234 10 & 6 -^*p"/e')
do
    echo $i "->" $(round 2 $i)
done

Test results

.000012340000 -> 0.000012
.000123400000 -> 0.00012
.001234000000 -> 0.0012
.012340000000 -> 0.012
.123400000000 -> 0.12
1.234 -> 1.2
12.340 -> 12
123.400 -> 120
1234.000 -> 1200
12340.000 -> 12000
123400.000 -> 120000
1234000.000 -> 1200000

A note on decimal separator and locale

All the working above assumes that the radix character (also known as the decimal separator) is ., as in most English locales. Other locales use , instead, and some shells have a built-in printf that respects locale. In this shells, you may need to set LC_NUMERIC=C to force the use of . as radix character, or write /usr/bin/printf to prevent use of the built-in version. This latter is complicated by the fact that (at least some versions) seem to always parse arguments using ., but print using the current locale settings.

The function *is* POSIX shell, not just Bash - but some shells have a bulit-in printf that handles locale differently from standard printf
Source Link
Toby Speight
  • 9.4k
  • 3
  • 32
  • 54
Loading
missing quotes, %f/%g not POSIX, so using bash where we know the printf builtin supports it, not about radix character.
Source Link
Stéphane Chazelas
  • 584.8k
  • 96
  • 1.1k
  • 1.7k
Loading
Fixed a bashism and a bug with exponents of 8 or 9 (leading zero implies octal in arithmetic expansion)
Source Link
Toby Speight
  • 9.4k
  • 3
  • 32
  • 54
Loading
Made the test cases a bit clearer
Source Link
Toby Speight
  • 9.4k
  • 3
  • 32
  • 54
Loading
Bug fix; simplify test loop
Source Link
Toby Speight
  • 9.4k
  • 3
  • 32
  • 54
Loading
Source Link
Toby Speight
  • 9.4k
  • 3
  • 32
  • 54
Loading