web-dev-qa-db-ja.com

科学表記法と偽精度なしでフロートを文字列に変換する

常に10進形式で書き込まれるように、浮動小数点数を印刷したい(例:12345000000000000000000.0または0.000000000000012345科学表記法 ではなく、まだ10進数の15.7桁の精度を維持します。

reprfloatは、指数が15より大きい場合、または-4より小さい場合、科学表記法で記述されることはよく知られています。

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

strが使用されている場合、結果の文字列は再び科学表記法になります。

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

formatフラグとfフラグを使用して、科学表記法を取り除くのに十分な精度を使用できることが提案されています。

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

それはその数に対して機能しますが、いくつかの余分な末尾のゼロがあります。ただし、.1に対して同じ形式が失敗します。これは、実際のマシン精度のfloatを超える10進数を与えます。

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

そして、私の番号が4.5678e-20の場合、.20fを使用しても相対的な精度が失われます。

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

したがって、これらのアプローチは私の要件と一致しません


これは疑問につながります: repr(n)(またはstr(n) on _と同じ桁数を持つ10進形式で任意の浮動小数点数を印刷する最も簡単でパフォーマンスの良い方法は何ですか?Python 3) 。ただし、科学表記法ではなく、常に10進形式を使用します。

つまり、たとえば浮動小数点値0.00000005を文字列'0.00000005'に変換する関数または操作。 0.1から'0.1'; 420000000000000000.0'420000000000000000.0'または420000000000000000に変換し、フロート値-4.5678e-5'-0.000045678'としてフォーマットします。


バウンティ期間後:少なくとも2つの実行可能なアプローチがあるようです。カリンは、ストリング操作を使用すると、Python 2での初期アルゴリズムと比較して大幅な速度向上を達成できることを実証しました。

副<文>この[前述の事実の]結果として、それ故に、従って、だから◆【同】consequently; therefore <文>このような方法で、このようにして、こんなふうに、上に述べたように◆【同】in this manner <文>そのような程度まで<文> AひいてはB◆【用法】A and thus B <文>例えば◆【同】for example; as an example、

私は主にPython 3で開発しているので、自分の答えを受け入れ、カリンに賞金を授与します。

48
Antti Haapala

残念ながら、float.__format__を使用した新しいスタイルのフォーマットでさえこれをサポートしていないようです。 floatsのデフォルトのフォーマットは、reprと同じです。 fフラグを使用すると、デフォルトで6桁の小数部があります。

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

しかし、望ましい結果を得るためのハックがあります-最速のものではなく、比較的簡単です:

  • 最初に、str()または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')

グローバル10進数コンテキストを使用しないように注意する必要があります。そのため、この関数に対して新しいコンテキストが構築されます。これが最速の方法です。別の方法は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を返します。

36
Antti Haapala

科学表記法の精度に満足している場合、単純な文字列操作アプローチを採用できますか?多分それはひどく賢くないかもしれませんが、それはうまくいくようです(あなたが提示したすべてのユースケースをパスします)、そして私はそれがかなり理解できると思います:

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の10進コンテキストのソリューションと比較しました。文字列操作は実際にはかなり速いようです。 編集:Python 2の場合のみ、はるかに高速に見えます。Python 3では、結果は同様でしたが、10進数のアプローチではわずかに高速です。

結果

  • 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))
25
Karin

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_positionalは、Dragon4アルゴリズムを使用して、元の浮動小数点入力に往復する位置形式で最短の10進数表現を生成します。科学表記法用のnumpy.format_float_scientificもあり、どちらの関数もゼロの丸めやトリミングなどをカスタマイズするオプションの引数を提供します。

5
user2357112

浮動小数点数でstr()を呼び出すことで任意の精度を失う準備ができているなら、それは行く方法です:

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'

グローバル変数は含まれておらず、自分で精度を選択できます。 str(float)長さの上限として、10進精度100が選択されます。実際の上限ははるかに低いです。 or '0'部分は、数値が小さく精度がゼロの状況用です。

まだ結果があることに注意してください:

>> float_to_string(0.10101010101010101010101010101)
'0.10101010101'

それ以外の場合、精度が重要な場合、formatは問題ありません。

import decimal

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

str(f)の呼び出し中に失われる精度を見逃すことはありません。 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'

とにかく、float型自体には制限があり、実際に長い浮動小数点数を表現できないため、小数点以下の最大桁数は制限されています。

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

また、整数はそのままフォーマットされています。

>> float_to_string(100)
'100'
3
gukoff

rstripで仕事を終わらせることができると思います。

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

それがあなたのために働くかどうか私に知らせてください。

0
silgon

興味深い質問です。質問にコンテンツをもう少し追加するために、@ Antti Haapalaと@Haroldソリューションの出力を比較するリッテテストを以下に示します。

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)

いずれの場合も、すべてのケースで「一貫した」結果は得られません。

  • Anti'sを使用すると、「-000」や「000」などの文字列が表示されます
  • ハロルズでは、 ''のような文字列が表示されます

速度を少し犠牲にしても、一貫性を優先します。ユースケースで想定するトレードオフに依存します。

0
BPL