5

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?

  • 4
    no it doesn't, but it's been implemented several times - for example cs.tut.fi/~jkorpela/c/eng.html (I did it as part of a plotting package in the 1970s) - Fortran, of course. – Thomas Dickey Sep 28 '17 at 1:39
  • @ThomasDickey nice! ... i'd like to get the same in python. – Skaperen Sep 28 '17 at 2:28
7

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
  • 1
    thanks, using %'f to format into thousands separators is really useful to know, as it covers most of the situations I would have needed an engineering notation for. – the_velour_fog Sep 28 '17 at 10:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.