Search code examples
pythonformatting

Formatting numbers so they align on the decimal point


In Python, I need to format numbers so they align on the decimal point, like so:

  4.8
 49.723
456.781
-72.18
  5
 13

What is the most straightforward way to do this?


Solution

  • I don't think there's a straight-forward way to do it, since you need to know the position of the decimal point in all the numbers before you start printing them. (I just had a look at Caramiriel's link, and some of the links from that page, but I couldn't find anything particularly applicable to this case).

    So it looks like you have to do some string-based inspection & manipulation of the numbers in the list. Eg,

    def dot_aligned(seq):
        snums = [str(n) for n in seq]
        dots = [s.find('.') for s in snums]
        m = max(dots)
        return [' '*(m - d) + s for s, d in zip(snums, dots)]
    
    nums = [4.8, 49.723, 456.781, -72.18]
    
    for s in dot_aligned(nums):
        print(s)
    

    output

      4.8
     49.723
    456.781
    -72.18
    

    If you want to handle a list of floats with some plain ints mixed in, then this approach gets a bit messier.

    def dot_aligned(seq):
        snums = [str(n) for n in seq]
        dots = []
        for s in snums:
            p = s.find('.')
            if p == -1:
                p = len(s)
            dots.append(p)
        m = max(dots)
        return [' '*(m - d) + s for s, d in zip(snums, dots)]
    
    nums = [4.8, 49.723, 456.781, -72.18, 5, 13]
    
    for s in dot_aligned(nums):
        print(s)
        
    

    output

      4.8
     49.723
    456.781
    -72.18
      5
     13
    

    As Mark Ransom notes in the comments, we can simplify handling ints by using .split:

    def dot_aligned(seq):
        snums = [str(n) for n in seq]
        dots = [len(s.split('.', 1)[0]) for s in snums]
        m = max(dots)
        return [' '*(m - d) + s for s, d in zip(snums, dots)]
    

    Masher mentions in a comment that it can be useful to add padding on the right so that the numbers can be printed in aligned columns. However, we don't need to compute the size of that padding for each string, we can use the str.ljust method.

    def dot_aligned(seq):
        snums = [str(n) for n in seq]
        dots = [len(s.split('.', 1)[0]) for s in snums]
        m = max(dots)
        left_pad = [' '*(m - d) + s for s, d in zip(snums, dots)]
        ms = max(map(len, left_pad))
        return [s.ljust(ms) for s in left_pad]
    
    nums = [4.8, 49.723, 456.781, -72.18, 5, 13, 1.2345] * 3
    cols = 4
    # Get number of cells in the output grid, using ceiling division
    size = len(nums) // -cols * -cols
    
    padded = dot_aligned(nums)
    for i in range(0, size, cols):
        print(*padded[i:i+cols])
    

    output

      4.8     49.723  456.781  -72.18  
      5       13        1.2345   4.8   
     49.723  456.781  -72.18     5     
     13        1.2345   4.8     49.723 
    456.781  -72.18     5       13     
      1.2345