Development Tip

float를 위치 형식의 문자열로 변환 (과학 표기법 및 잘못된 정밀도 없음)

yourdevel 2021. 1. 7. 20:05
반응형

float를 위치 형식의 문자열로 변환 (과학 표기법 및 잘못된 정밀도 없음)


부동 소수점 숫자를 인쇄하여 항상 10 진수 형식 (예 : 과학 표기법이 아닌 12345000000000000000000.0또는 ,하지만 IEEE 754 double의 유효 숫자최대 15.7 개)이되도록하고 싶습니다 . 그리고 더 이상.0.000000000000012345

내가 원하는 것은 이상적 그래서 결과는 것을 짧은 같은 값에 아직 결과가로 변환하는 것이 위치 진수 형식의 문자열float .

지수가 15보다 크거나 -4보다 작 으면 reprof a float가 과학적 표기법으로 작성 된다는 것은 잘 알려져 있습니다 .

>>> n = 0.000000054321654321
>>> n
5.4321654321e-08  # scientific notation

경우에 str사용되며, 결과 문자열은 다시 과학적 표기법입니다 :

>>> str(n)
'5.4321654321e-08'

내가 사용할 수있는 제안되었다 formatf과학 표기법을 제거하는 플래그와 충분한 정밀도 :

>>> format(0.00000005, '.20f')
'0.00000005000000000000'

추가 후행 0이 있지만 해당 숫자에 대해 작동합니다. 그러나 동일한 형식이에서 실패하여 .1float의 실제 기계 정밀도를 초과하는 십진수를 제공합니다.

>>> format(0.1, '.20f')
'0.10000000000000000555'

내 번호가 4.5678e-20이면 .20f여전히 상대 정밀도를 잃을 것입니다.

>>> format(4.5678e-20, '.20f')
'0.00000000000000000005'

따라서 이러한 접근 방식은 내 요구 사항과 일치하지 않습니다 .


이것은 질문으로 이어집니다 : 임의의 부동 소수점 숫자를 10 진수 형식으로 인쇄하는 가장 쉽고 효과적인 방법은 무엇입니까? repr(n)(또는 str(n)Python 3) 와 동일한 숫자를 가지지 만 항상 과학 표기법이 아닌 10 진수 형식을 사용합니다. .

즉, 예를 들어 float 값 0.00000005을 문자열 로 변환하는 함수 또는 연산입니다 '0.00000005'. 0.1'0.1'; 420000000000000000.0'420000000000000000.0'또는 420000000000000000부동 소수점 값 -4.5678e-5'-0.000045678'.


현상금 기간 이후 : Karin이 문자열 조작을 사용하면 Python 2의 초기 알고리즘에 비해 상당한 속도 향상을 달성 할 수 있음을 입증했듯이 적어도 두 가지 실행 가능한 접근 방식이있는 것 같습니다.

그러므로,

나는 주로 Python 3에서 개발 중이므로 내 답변을 수락하고 Karin에게 현상금을 수여 할 것입니다.


불행히도 새로운 스타일의 서식조차도 float.__format__이것을 지원 하지 않는 것 같습니다 . floats 의 기본 형식은 with와 동일합니다 repr. 와 함께 f플래그 기본적으로 6 소수 자릿수가있다 :

>>> format(0.0000000005, 'f')
'0.000000'

그러나 원하는 결과를 얻기위한 해킹이 있습니다. 가장 빠른 것은 아니지만 비교적 간단합니다.

  • 먼저 float는 str()or를 사용하여 문자열로 변환됩니다.repr()
  • 그런 다음 Decimal해당 문자열에서 인스턴스가 생성됩니다.
  • Decimal.__format__f원하는 결과를 제공하는 플래그를 지원하며 floats 와 달리 기본 정밀도 대신 실제 정밀도를 인쇄합니다.

따라서 간단한 유틸리티 함수를 만들 수 있습니다 float_to_str.

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

전역 십진 컨텍스트를 사용하지 않도록주의해야하므로이 함수에 대한 새 컨텍스트가 구성됩니다. 이것이 가장 빠른 방법입니다. 다른 방법은 사용하는 decimal.local_context것이지만 속도가 느려 각 변환에 대해 새로운 스레드 로컬 컨텍스트와 컨텍스트 관리자를 생성합니다.

이제이 함수는 가수에서 가능한 모든 자릿수가있는 문자열을 가장 짧은 등가 표현으로 반올림하여 반환합니다 .

>>> float_to_str(0.1)
'0.1'
>>> float_to_str(0.00000005)
'0.00000005'
>>> float_to_str(420000000000000000.0)
'420000000000000000'
>>> float_to_str(0.000000000123123123123123123123)
'0.00000000012312312312312313'

마지막 결과는 마지막 자리에서 반올림됩니다.

@Karin이 언급했듯이 float_to_str(420000000000000000.0)는 예상 한 형식과 엄격하게 일치하지 않습니다. 420000000000000000후행없이 반환 됩니다 .0.


과학적 표기법의 정확성에 만족한다면 간단한 문자열 조작 접근 방식을 취할 수 있습니까? 끔찍하게 영리하지는 않지만 작동하는 것처럼 보이며 (제시 한 모든 사용 사례를 통과 함) 상당히 이해할 수 있다고 생각합니다.

def float_to_str(f):
    float_string = repr(f)
    if 'e' in float_string:  # detect scientific notation
        digits, exp = float_string.split('e')
        digits = digits.replace('.', '').replace('-', '')
        exp = int(exp)
        zero_padding = '0' * (abs(int(exp)) - 1)  # minus 1 for decimal point in the sci notation
        sign = '-' if f < 0 else ''
        if exp > 0:
            float_string = '{}{}{}.0'.format(sign, digits, zero_padding)
        else:
            float_string = '{}0.{}{}'.format(sign, zero_padding, digits)
    return float_string

n = 0.000000054321654321
assert(float_to_str(n) == '0.000000054321654321')

n = 0.00000005
assert(float_to_str(n) == '0.00000005')

n = 420000000000000000.0
assert(float_to_str(n) == '420000000000000000.0')

n = 4.5678e-5
assert(float_to_str(n) == '0.000045678')

n = 1.1
assert(float_to_str(n) == '1.1')

n = -4.5678e-5
assert(float_to_str(n) == '-0.000045678')

성능 :

이 접근 방식이 너무 느릴 수 있다고 걱정했기 때문에 실행 timeit하여 OP의 십진 컨텍스트 솔루션과 비교했습니다. 문자열 조작이 실제로 훨씬 더 빠릅니다. 편집 : 파이썬 2에서만 훨씬 더 빠른 것 같습니다. 파이썬 3에서도 결과는 비슷했지만 십진법으로 약간 더 빠릅니다.

결과 :

  • Python 2 : 사용 ctx.create_decimal():2.43655490875

  • Python 2 : 문자열 조작 사용 : 0.305557966232

  • Python 3 : 사용 ctx.create_decimal():0.19519368198234588

  • Python 3 : 문자열 조작 사용 : 0.2661344590014778

타이밍 코드는 다음과 같습니다.

from timeit import timeit

CODE_TO_TIME = '''
float_to_str(0.000000054321654321)
float_to_str(0.00000005)
float_to_str(420000000000000000.0)
float_to_str(4.5678e-5)
float_to_str(1.1)
float_to_str(-0.000045678)
'''
SETUP_1 = '''
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')
'''
SETUP_2 = '''
def float_to_str(f):
    float_string = repr(f)
    if 'e' in float_string:  # detect scientific notation
        digits, exp = float_string.split('e')
        digits = digits.replace('.', '').replace('-', '')
        exp = int(exp)
        zero_padding = '0' * (abs(int(exp)) - 1)  # minus 1 for decimal point in the sci notation
        sign = '-' if f < 0 else ''
        if exp > 0:
            float_string = '{}{}{}.0'.format(sign, digits, zero_padding)
        else:
            float_string = '{}0.{}{}'.format(sign, zero_padding, digits)
    return float_string
'''

print(timeit(CODE_TO_TIME, setup=SETUP_1, number=10000))
print(timeit(CODE_TO_TIME, setup=SETUP_2, number=10000))

NumPy 1.14.0부터는 numpy.format_float_positional. 예를 들어 질문의 입력에 대해 실행합니다.

>>> numpy.format_float_positional(0.000000054321654321)
'0.000000054321654321'
>>> numpy.format_float_positional(0.00000005)
'0.00000005'
>>> numpy.format_float_positional(0.1)
'0.1'
>>> numpy.format_float_positional(4.5678e-20)
'0.000000000000000000045678'

numpy.format_float_positionalDragon4 알고리즘을 사용하여 원래 float 입력으로 다시 왕복하는 위치 형식으로 가장 짧은 10 진수 표현을 생성합니다. 이 또한의 numpy.format_float_scientific과학 표기하고, 두 함수는 반올림 1과 0의 트리밍 등의 작업을 사용자 정의하는 선택적 인수를 제공합니다.


str()float 숫자 를 호출 하여 임의의 정밀도를 잃을 준비가 되었다면 다음과 같이 할 수 있습니다 .

import decimal

def float_to_string(number, precision=20):
    return '{0:.{prec}f}'.format(
        decimal.Context(prec=100).create_decimal(str(number)),
        prec=precision,
    ).rstrip('0').rstrip('.') or '0'

It doesn't include global variables and allows you to choose the precision yourself. Decimal precision 100 is chosen as an upper bound for str(float) length. The actual supremum is much lower. The or '0' part is for the situation with small numbers and zero precision.

Note that it still has its consequences:

>> float_to_string(0.10101010101010101010101010101)
'0.10101010101'

Otherwise, if the precision is important, format is just fine:

import decimal

def float_to_string(number, precision=20):
    return '{0:.{prec}f}'.format(
        number, prec=precision,
    ).rstrip('0').rstrip('.') or '0'

It doesn't miss the precision being lost while calling str(f). The or

>> float_to_string(0.1, precision=10)
'0.1'
>> float_to_string(0.1)
'0.10000000000000000555'
>>float_to_string(0.1, precision=40)
'0.1000000000000000055511151231257827021182'

>>float_to_string(4.5678e-5)
'0.000045678'

>>float_to_string(4.5678e-5, precision=1)
'0'

Anyway, maximum decimal places are limited, since the float type itself has its limits and cannot express really long floats:

>> float_to_string(0.1, precision=10000)
'0.1000000000000000055511151231257827021181583404541015625'

Also, whole numbers are being formatted as-is.

>> float_to_string(100)
'100'

Interesting question, to add a little bit more of content to the question, here's a litte test comparing @Antti Haapala and @Harold solutions outputs:

import decimal
import math

ctx = decimal.Context()


def f1(number, prec=20):
    ctx.prec = prec
    return format(ctx.create_decimal(str(number)), 'f')


def f2(number, prec=20):
    return '{0:.{prec}f}'.format(
        number, prec=prec,
    ).rstrip('0').rstrip('.')

k = 2*8

for i in range(-2**8,2**8):
    if i<0:
        value = -k*math.sqrt(math.sqrt(-i))
    else:
        value = k*math.sqrt(math.sqrt(i))

    value_s = '{0:.{prec}E}'.format(value, prec=10)

    n = 10

    print ' | '.join([str(value), value_s])
    for f in [f1, f2]:
        test = [f(value, prec=p) for p in range(n)]
        print '\t{0}'.format(test)

Neither of them gives "consistent" results for all cases.

  • With Anti's you'll see strings like '-000' or '000'
  • With Harolds's you'll see strings like ''

I'd prefer consistency even if I'm sacrificing a little bit of speed. Depends which tradeoffs you want to assume for your use-case.


I think rstrip can get the job done.

a=5.4321654321e-08
'{0:.40f}'.format(a).rstrip("0") # float number and delete the zeros on the right
# '0.0000000543216543210000004442039220863003' # there's roundoff error though

Let me know if that works for you.

ReferenceURL : https://stackoverflow.com/questions/38847690/convert-float-to-string-in-positional-format-without-scientific-notation-and-fa

반응형