3

These three numbers 1.2, 0.0034, 56000 all have one thing in common: two digits of accuracy. Lets say I want to report computed bandwidth of several very different devices that range from kilobytes per second to gigabytes per second, all of which vary by up to %10. The easiest way for my readers to quickly grasp the results is for me to convert everything to a common unit (say MB/s), line up the decimal points,

    1.2
    0.0034
56000

and avoid printing extraneous digits; if my computed values are 1.193225, 0.00344791, and 56188.5622, then my readers only need to see the above - the rest is noise. Despite extensive formatting options for floating point numbers, Python doesn't seem to have a clean way to print numbers with a fixed-accuracy. What would be the best way to do this?

A note on scoring this question: I'll chose the best (ie. simple, understandable, elegant) answer over the first answer. No need to rush.

duanev
  • 936
  • 13
  • 17
  • 3
    You could print them in scientific notation, using the `"%.1e"` format. There should also be some related question about ["human-readable numbers"](http://stackoverflow.com/search?q=python+human+readable+numbers). – tobias_k Apr 04 '16 at 21:22
  • Scientific notation requires readers to understand the 'e' part and recognize that say 1e99 is insanely larger than 1e13. It doesn't reveal by how many orders of magnitude numbers differ. – duanev Apr 04 '16 at 21:26
  • 1
    Do you output in real time or get all the data first? – Padraic Cunningham Apr 04 '16 at 21:31
  • Real time would be nice. Yes, there are two problems here, the first is to print the number with N digits of accuracy, the second it to align the decimal point. – duanev Apr 04 '16 at 21:35
  • 1
    You could at least use scientific format to round your numbers to two significant digits. `[float("%.1e" % x) for x in nums]` – tobias_k Apr 04 '16 at 21:44
  • Without knowing the length of the longest number I don't see an easy way to align bar hard coding the amount digits to pad which would not really be a solution. If you first gathered the numbers then it would be easy to align – Padraic Cunningham Apr 04 '16 at 21:54
  • Padraic, expanding the width to accommodate too big numbers (and in this case also too small numbers), the way current format specifiers do now, is acceptable. – duanev Apr 04 '16 at 22:01
  • @duanev, yes, my point was though that you cannot align output without knowing the amount of digits you will have to pad for, if you pass a long string of digits with the first answer below you will see the alignment breaks – Padraic Cunningham Apr 04 '16 at 22:04
  • 1
    They return different strings, because one just prints the number in scientific notation, while the other takes that output to create a new float (with exactly two significant digits), which is then printed in whatever format Python deems best for that float. – tobias_k Apr 04 '16 at 22:07

3 Answers3

2
import math


def round_s(n):
    if not n:
        return n
    e = int(math.floor(math.log10(abs(n)))) - 1
    return round(n, -e)


def fmt(n, size):
    return '{n:{size}f}'.format(size=size, n=n).rstrip('0').rstrip('.')


numbers = [
    1.193225,
    1.0,
    0,
    -1.0,
    0.00344791,
    -0.00344791,
    56188.5622,
    -56188.5622,
]

for n in numbers:
    print '{:12.5f} => {}'.format(n, fmt(round_s(n), 14))

Output:

     1.19322 =>       1.2
     1.00000 =>       1
     0.00000 =>       0
    -1.00000 =>      -1
     0.00345 =>       0.0034
    -0.00345 =>      -0.0034
 56188.56220 =>   56000
-56188.56220 =>  -56000

Here you go.

GaretJax
  • 7,462
  • 1
  • 38
  • 47
1

I would combine two stack overflow answers (with the assumption that each is the best way to perform the two halves of this task): Round to a specific number of significant figures and align the numbers using the formatting language

from math import log10, floor
NUM_DIGITS = 2 # Number of significant digits

# An example input
number = 1.193225

# Round the number
rounded = round(number, -int(floor(log10(number))) + (NUM_DIGITS - 1)) 

# Print aligned to the decimal point
print('{part[0]:>3}.{part[1]:<3}'.format(part=str(rounded).split('.')))

You must tweak the two widths in the string formatter to be at least as wide as the widest integer and decimal part of the numbers. Full explainations can be found on the respective linked pages.

Community
  • 1
  • 1
Marc J
  • 1,303
  • 11
  • 11
0

If you want to work for any lengths string you need to know the max count of the amount of digits that will be before the decimal:

def my_format(numbers):
    mxi = max(len(str(num).split(".")[0]) for num in numbers)
    for num in numbers:
        if isinstance(num, float):
            t = "{}".format(float("{:.2g}".format(num)))
            print(t.rjust((mxi + len(t.split(".")[-1]) + 1), " "))
        else:
            print("{:{mx}}".format(num, mx=mxi)

Which should align for any length:

In [10]: numbers = [
    1.193225,
    1.0,
    0,
    -1.0,
    0.00344791,
    -0.00344791,
    56188.5622,
    -56188.5622,
    123456789,
    12,
    123]

In [11]: my_format(numbers)
        1.2
        1.0
        0
       -1.0
        0.0034
       -0.0034
    56000.0
   -56000.0
123456789
       12
      123
Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321