printf – Does printf Have an Engineering Notation Format for Numbers?

printf

Say I have this number: 105000000, in this form I can't easily see how big it is, so using printf at a prompt I try using the "scientific" notation:

% printf "%.3E" 105000000
1.050E+08

This is better but I would like to use an "engineering" notation where the output is formatted in powers of 103 millions, billions, trillions etc.
e.g. I want to format it to look like this:

105000        => 105.0E+03      (105 thousand)
105000000     => 105.0E+06      (105 million)
105000000000  => 105.0E+09      (105 billion)
...

can printf do this?

Best Answer

I don't know of any printf implementation that does. Note that POSIX doesn't even guarantee printf '%E\n' 123 to work at all as support for floating point formats is optional.

With several printf implementations, you can use %'f to output thousand separators in locales that have one:

$ LC_NUMERIC=en_GB.UTF-8 printf "%'.0f\n" 105000000
105,000,000
$ LC_NUMERIC=fr_FR.UTF-8 printf "%'.0f\n" 105000000
105 000 000
$ LC_NUMERIC=da_DK.UTF-8 printf "%'.0f\n" 105000000
105.000.000
$ LC_NUMERIC=de_CH.UTF-8 printf "%'.0f\n" 105000000
105'000'000
$ LC_NUMERIC=ps_AF.UTF-8 printf "%'.0f\n" 105000000
105٬000٬000

With the printf builtin of ksh93, you can also use %#d for K/M/G... suffixes and %#i for Ki/Mi/Gi ones:

$ printf '%#d\n' 105000000 $((2**22))
105M
4.2M
$ printf '%#i\n' 105000000 $((2**22))
100Mi
4.0Mi

(note however that you can't change the precision and the transition from Ki to Mi for instance is at 1000 Ki, not 1024 Ki which can be surprising if you're used to the GNU format (like in GNU ls -lh). It's also limited to integer numbers up to 263-1 (8Ei - 1)).

As to how to implement it by hand, with zsh:

eng() {
  local n="${(j: :)argv}" exp
  zmodload zsh/mathfunc
  if ((n)) && ((exp = int(floor(log10(abs(n)) / 3)) * 3)); then
    printf '%.10ge%d\n' "n / 1e$exp" exp
  else
    printf '%.10g\n' "$n"
  fi
}

And then:

$ eng 123
123
$ eng 12345
12.345e3
$ eng 0.000000123123
123.123e-9
$ eng 1. / -1234
-810.3727715e-6

Note that in zsh like in many other languages, operations involving floats are done in floating point arithmetics (with your processor's double type) while those involving integers only are done in integer arithmetics (with your processor's long type). That has some implications like:

$ eng 1  / -1234
0
$ eng 1. / -1234
-810.3727715e-6

but also:

$ eng 1 \*{2..28}.  # factorial 28
304.8883446e27
$ eng 1 \*{2..28}
-5.968160533e18  # 64bit signed integer overflow

(though that's not specific to that eng function)

Or as a POSIX shell function using POSIX bc, so allowing arbitrary precision:

eng() (
  IFS=" "
  scale=$1; shift
  bc -l << EOF |
  s = scale = $scale
  if (scale < 20) s = 20
  n = $*
  if (n != 0) {
    scale = s
    a = n; if (a < 0) a = -a
    e = l(a) / l(10) / 3 + 10 ^ -15
    if (e < 0) e -= 1
    scale = 0
    e = e / 1 * 3
    scale = s
    if (scale <= -e) scale = 1 - e
    n = n / 10^e
    scale = $scale
  }
  n/1
  if (e != 0) e
EOF
    sed '
      :1
      /\\$/{
        N;b1
      }
      s/\\\n//g
      /\./s/0*$//
      s/\.$//
      $!N
      s/\n/e/'
)

(with a 1e-15 shift to offset rounding errors when calculating log10(n) for the exponent for values of n like 0.001)

Here with the first argument being taken as the scale:

$ eng 2 1/3
330e-3
$ eng 20 1/3
333.33333333333333333e-3

Note that bc itself doesn't understand the engineering notation, you have to write:

$ eng 20 "1.123123 * 10^2000"
112.3123e1998