简体   繁体   中英

Adaptive floating point formatting

This seems like a really stupid question, but I was honestly not able to find a satisfactory solution. All the "obvious" solutions I've found so far have some hidden problems.

I would like to format floating point values either to an arbitrary width or relative precision , while keeping the adaptive formatting of the {:g} formatting specifier. (Use scientific or normal notation, depending on the magnitude of the number)

Given:

import math               # Here is how the default (str) formatting displays these values

# Small values
long_small = 10**-16/3    # 3.3333333333333335e-17
short_small = 10**-16     # 1e-16

# Medium values
long_medium = math.pi     # 3.141592653589793
short_medium = 130.0      # 130.0

# Large values
long_large = 10.0**16 + 2 # 1.0000000000000002e+16
short_large = 10.0**16    # 1e+16

In the arbitrary width case, I would like to format these numbers as following (for example, width=8 ):

long_small    # 3.33e-17
short_small   # 1.00e-16
long_medium   # 3.141593
short_medium  # 130.0000
long_large    # 1.00e+16
short_large   # 1.00e+16

In the arbitrary relative precision case, I would like to format these numbers as following ( precision=8 ):

long_small    # 3.33333333e-17
short_small   # 1.00000000e-16
long_medium   # 3.14159265
short_medium  # 130.000000
long_large    # 1.00000000e+16
short_large   # 1.00000000e+16
  • {:N.Mf} isn't good enough, because I would like to keep the scientific notation formatting for very large and small numbers
  • {:N.Mg} and {:NM} aren't good enough, because they discard trailing zeros, also the "width" specifier pads with spaces, instead of adding digits
  • {:N.Me} always uses the scientific notation
  • any "clever hack", that uses >0 or <0 doesn't work, because it either adds leading zeroes, or adds zeroes after the mantissa
  • any "clever hack", that attempts to format with either {:f} or {:e} and attempts to either .replace(...) or [:fixed_width] the resulting string doesn't work, because of various edge cases, where it changes the number, cuts off the mantissa or the floating point ( . )

Sigh. I think I found a solution, while I was writing the question, but I am not too happy with how it works.

The arbitrary relative precision part turned out to be quite easy. With a little bit of digging, I found the # formatting modifier in the docs. Which uses an "alternate form" form for the conversion (whatever that means).

So with {:#.9g} you can get what I called precision=8 . Unfortunately, it behaves a little weird, when the floating point dot ( . ) is at the end. For example, f"{10.0**8:#.9g}" yields 100000000. , where as I would prefer it to go straight to 1.00000000e+08 . But oh, well.

The arbitrary width part was a little bit harder. And in the end, the best I was able to come up with is:

def fmt_fixed_width(value, width):
    assert width >= 7
    result = "{:#.{}g}".format(value, width - 1) 
    if len(result) != width: 
        result = "{:#.{}g}".format(value, width - 1 + width - len(result)) 
    return result

Obviously, this is not the pretty, elegant solution I wished for. If somebody knows, how to get proper arbitrary width floating point formatting, please tell me.

You are using the formatting type, "g", which is closest to your requirements but it has some arbitrary hardcoded logic which does not match your logic. See here: https://docs.python.org/3.7/library/string.html#format-specification-mini-language

Specifically when it chooses to format as "f" or as "e".

Regarding the fixed width case, your criteria is very peculiar. For numbers, generally precision is considered, not the actual number of characters used for encoding. What you call "relative precision". There's no built in functionality for this, AFAIK.

I think it can be accomplished more easily than in your approach, though. Just use "f" for numbers >=1 and <10 and "e" for the rest.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM