2

Below is the code for formatting an x value that I have been using.

Examples of what does it do:

  • It formats 7,500,000 into 7.5 M
  • It formats 800,000 into 800 K It

    def Formatting(self, x, pos):
        formats = ((1e-12,'%d%s T','%1.1f T'),
                   (1e-9, '%d%s B','%1.1f B'),
                   (1e-6, '%d%s M','%1.1f M'),
                   (1e-3, '%d%s k','%1.1f K'  ))
    
        for i, (N, A, B) in enumerate(formats):
            if abs(x) > (1./N):
                result = ''
                x = x * N
    
                if abs(x) >= 1000:
                    x, r = divmod(x, 1000)
                    result = ",%03d%s" % (r, result)
                    return A % (x, result)
    
                else: return B % (x)
    
    
            elif 1   <= abs(x) < 1e3: return '%1.0f' % (x)
            elif 0.1 <= abs(x) < 1:   return '%1.1f' % (x)
            elif 0   <  abs(x) < 0.1: return '%1.3f' % (x)
            elif x == 0:              return '%1.0f' % (x)
    

Now, I have been struggling to do the following improvements to it:

  • Instead of 550 M, I would like to be able to print .55 B
  • Instead of 550 B, I would like to be able to print .55 T
  • Instead of 550 K, I would like to be able to print .55 M
  • Instead of 0.001, I would like to be able to print .001 without the zero
  • However 55.5 M, 55.5 B, 55.5 K should still be printed - not .055 M, or .055 B..

Suggestions as to how to perform this change or improve this piece of code to have more meaning printouts (that are used in a chart)?

Thank you very much!

relima
  • 3,462
  • 5
  • 34
  • 53

2 Answers2

4

There's probably a shorter way to generate the format strings; but they're easy enough to just map to each magnitude. I don't fully understand the behavior you want w/r/t decimal point length, but the logic for that should be easy.

Since what you had was a method, I incorporated this into a class. (This also avoids defining formats every time the function is called.)

from math import log10

class Formatter(object):
    def __init__(self):
        self.formats = (('%1.1f', 0),
                        ('%2.1f', 0),
                        ('%1.2f K', 3),
                        ('%1.2f K', 3),
                        ('%2.1f K', 3),
                        ('%1.2f M', 6),
                        ('%1.2f M', 6),
                        ('%2.1f M', 6),
                        ('%1.2f B', 9),
                        ('%1.2f B', 9),
                        ('%2.1f B', 9),
                        ('%1.2f T', 12),
                        ('%1.2f T', 12),
                        ('%2.1f T', 12))

    def human_readable(self, x):
        if x == 0: return '0'
        magnitude = int(log10(abs(x)))
        if magnitude > 13: format_str, denominator_mag = '%i T', 12
        else: format_str, denominator_mag = self.formats[magnitude]
        return (format_str % (x * 1.0 / (10 ** denominator_mag))).lstrip('0')

Edit: Here's one that doesn't use a lookup table:

def human_readable(self, x):
    if x == 0: return '0'
    magnitude = int(log10(abs(x)))
    if magnitude > 13: 
        format_str = '%i T'
        denominator_mag = 12
    else: 
        float_fmt = '%2.1f ' if magnitude % 3 == 1 else '%1.2f '
        illion = (magnitude + 1) // 3
        format_str = float_fmt + ['', 'K', 'M', 'B', 'T'][illion]
        denominator_mag = illion * 3
    return (format_str % (x * 1.0 / (10 ** denominator_mag))).lstrip('0')
senderle
  • 145,869
  • 36
  • 209
  • 233
  • Thank you very much for this, but sometime I get OverflowError: math range error. Any ideas? – relima Mar 24 '11 at 02:59
  • Oh, wait, are you feeding it negative numbers? – senderle Mar 24 '11 at 03:04
  • Ok, now it tests for length and calls `abs` before passing `x` to `log10`. I can't think of any other inputs that could cause problems. Let me know if this didn't fix the problem. – senderle Mar 24 '11 at 03:08
  • Thank you so so very much for this! I will leave your name in the comments of the code :-D ; this alone did the trick for fixing my errors: if x == 0: return '%1.0f' % (x) THANK YOU! – relima Mar 24 '11 at 03:15
  • Ok, I had to come back and say thanks again. It does exactly what I needed!!! You are a genius! – relima Mar 24 '11 at 03:18
  • Haha, oh, right `0`. Well good, glad you figured that one out. – senderle Mar 24 '11 at 03:21
0

Try all the different possible ways to format it and pick the shortest one length-wise, with preference given to larger units (e.g., prefer .55 B to 550 M, since they are the same length).

As for stripping leading zeros, one possible solution is checking if s[:2] == '0.' and replacing it with '.'

Ankit
  • 1,861
  • 18
  • 18