Форматирование числа с плавающей точкой в ​​Python фиксированной ширины - PullRequest
0 голосов
/ 11 октября 2018

Меня не волнует расположение десятичной точки, и я хотел бы напечатать в научной записи, когда это уместно, для отображения с максимальной точностью.Тем не менее, я хотел бы округлить соответствующим образом, чтобы сохранить фиксированную ширину, неровная влево или неровная в порядке.

Например,

>>> my_format("{:10f}", 0.0000000456)
"  4.56e-08"
>>> my_format("{:10f}", 12.345678987654321)
" 12.345679"
#or "12.34567890" because significant digits
#but not "1.2346e+01", since it is less accurate than the above representations
>>> my_format("{:10f}", 12345678987654321)
"1.2345e+16"

РЕДАКТИРОВАТЬ , чтобы уточнить примеры,спецификатор форматирования width не обеспечивает фиксированную ширину.Это обеспечивает минимальную ширину.Как получить представление с фиксированной шириной?

Ответы [ 4 ]

0 голосов
/ 11 октября 2018

Вы можете проверить форматирование числа в {:f} и {:e}, а затем проанализировать полученные строки, чтобы определить, какая из них подходит лучше:

import re


def format(spec, number):
    def _decimal(s):
        return re.search(r'^\s*-?([0-9]+(\.[0-9]+)?)', s).group(1)

    def _significant_digits(s):
        return _decimal(s).rstrip('0')

    def _fit_to_width(s):
        decimal, significant = _decimal(s), _significant_digits(s)
        stripped = s.replace(decimal, significant)
        excess = len(stripped) - spec
        if excess > 0:
            # Replace excess digits from the right.
            significant = significant[::-1].replace(
                re.match(
                    r'[0-9]{{,{}}}'.format(excess),
                    significant[::-1]
                ).group(0), ''
            )[::-1]
        return s.replace(decimal, significant)

    formats = [
        _fit_to_width('{{:{}f}}'.format(spec).format(number)),
        _fit_to_width('{{:{}e}}'.format(spec).format(number)),
    ]
    return max(
        filter(
            lambda x: len(x[0]) <= spec,
            [(f, len(_significant_digits(f))) for f in formats]
        ),
        key=lambda x: x[-1]
    )[0].rjust(spec)


print(format(10, 0.0000000456))
print(format(10, 12.345678987654321))
print(format(10, 12345678987654321))

#   4.56e-08
#  12.345679
# 1.2345e+16
0 голосов
/ 11 октября 2018

вам нужен способ сохранить количество отображаемых символов.Поэтому создайте функцию для этого.

import decimal

# create a new context for this task
ctx = decimal.Context()

# 20 digits should be enough for everyone :D
ctx.prec = 20

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')

print float_to_str(0.0000000456)
print float_to_str(12.345678987654321)
0 голосов
/ 11 октября 2018

Это похоже на работу.Можно избавиться от numpy, но для округления потребуется немного больше работы.

import numpy as np

SCIENTIFIC_NOTATION_WIDTH = 4

def my_format(number, n):
    places = np.log10(np.abs(number))
    if abs(places) == np.inf:
        places = 0
    highest_place = -int(places)
    if 1 <= highest_place < 3:
        rounded = np.round(number, n - highest_place - 1)
    elif highest_place >= 3:
        rounded = np.round(number, highest_place + n - 5)
    elif -n < highest_place < 1:
        rounded = np.round(number, n + highest_place - 2)
    else:
        rounded = np.round(number, highest_place + n - 6)

    return "{{:{}.{}g}}".format(n,n).format(rounded)
print(my_format(12345678987654321, 10))
print(my_format(12.345678987654321,10))
print(my_format(0.0000000456,10))

#1.2346e+16
# 12.345679
#  4.56e-08
0 голосов
/ 11 октября 2018

спецификатор формата g обычно хорош, если вам нужна научная запись, то есть:

my_format = "{:.10g}".format

должен делать правильные вещи:

>>> list(my_format(v) for v in (0.0000000456, 12.345678987654321, 12345678987654321))
['4.56e-08', '12.34567899', '1.234567899e+16']

Позже я понял, что вышефункция не делает то, что хотел ОП

Основываясь на полезных комментариях @a_guest, я пришел к следующему:

def my_format(v, length=10):
    n = length
    while n > 0:
        i = len('%#.*g' % (n, v))
        s = '%.*g' % (n + n - i, v)
        if len(s) <= length:
            return s
        n -= 1
    return s

Теперь я получаю ['4.56e-08', '12.345679', '1.2346e+16'] назад, что ближек тому, что было нужно.

Я проверил это, сгенерировав много случайных чисел, используя:

from random import uniform
def rnd_float():
    return uniform(-10, 10) * 10 ** (uniform(-1.6, 1.6) ** 11)

довольно произвольно, но генерирует числа, достаточно близкие к тому распределению, которое мне небезразлично.Т.е. в основном около 1, но некоторые очень маленькие и большие с приличной вероятностью.

Я передал числа от этого до my_format 100k раз и получаю обратно соответственно отформатированные числа.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...