2

Whenever I use yaml dump to dump floats, they start to look ugly.

Input:

a.yaml

a: 0.000000015

When I read it in and then dump it to file again, it will look like:

dumped.yaml

a: 1.5e-08

Note that there's no fixed size I can go for, i.e. maybe someone wants to put a lot of zeros (and I am not "afraid" of e.g. a small fraction with 20 leading zeros)

Also, if you choose a fixed size, then it might look like the following (which I am trying to avoid as well)

a: 0.0000000150000
PascalVKooten
  • 20,643
  • 17
  • 103
  • 160

2 Answers2

2

I haven't been able to find, in the format specification mini-language documentation, a way to format numbers the way you want them.

I propose a simple hack: to replace, in the output of PyYAML, all numbers in scientific notation with their fixed-point equivalent, like this:

import re
import yaml

# only accepts Python's/PyYAML's default scientific format, on purpose:
E_REGEX = re.compile(r'(\b|-)([1-9])(?:\.(\d+))?e([+-])(\d+)\b')

def e_to_f(match):
    sf, f0, f1, se, e = match.groups()
    if f1 is None: f1 = ''
    f = f0 + f1
    n = int(e)
    if se == '-':
        z = n - 1  # i.e., n - len(f0)
        if z < 0:
            return sf + f[:-z] + '.' + f[-z:]
        else:
            return sf + '0.' + '0' * z + f
    else:
        z = n - len(f1)
        if z < 0:
            return sf + f[:z] + '.' + f[z:]
        else:
            return sf + f + '0' * z + '.0'

e_dict = {
    'example': 1.5e-15,
    'another': -3.14e+16
}
e_txt = yaml.dump(e_dict, sort_keys=False)
f_txt = E_REGEX.sub(e_to_f, e_txt)
print(e_txt)
print(f_txt)

# output:
#
# example: 1.5e-15
# another: -3.14e+16
#
# example: 0.0000000000000015
# another: -31400000000000000.0

Walter Tross
  • 12,237
  • 2
  • 40
  • 64
1

You can subclass yaml.Dumper to override the representer method for floats with one that only outputs standard notation. This method should be less hacky than regex substitution.

import decimal

import yaml

# from https://stackoverflow.com/a/38847691/7941251
ctx = decimal.Context()
ctx.prec = 20  # change for more precision

def float_to_str(f):
    """
    Convert the given float to a string,
    without resorting to scientific notation
    """
    d1 = ctx.create_decimal(repr(f))
    return format(d1, 'f')

class CustomDumper(yaml.SafeDumper):
    # modified from https://github.com/yaml/pyyaml/blob/master/lib3/yaml/representer.py#L171
    def represent_float(self, data):
        if data != data or (data == 0.0 and data == 1.0):
            value = '.nan'
        elif data == self.inf_value:
            value = '.inf'
        elif data == -self.inf_value:
            value = '-.inf'
        else:
            value = float_to_str(data).lower()
        return self.represent_scalar('tag:yaml.org,2002:float', value)

CustomDumper.add_representer(float, CustomDumper.represent_float)

print(yaml.dump({"a": 0.000000015}, Dumper=CustomDumper)) # a: 0.000000015

Feel free to change float_to_str to a different method for converting floats to standard notation.

SuperStormer
  • 4,997
  • 5
  • 25
  • 35