常に10進形式で書き込まれるように、浮動小数点数を印刷したい(例:12345000000000000000000.0
または0.000000000000012345
、 科学表記法 ではなく、まだ10進数の15.7桁の精度を維持します。
repr
のfloat
は、指数が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、
decimal
モジュールを使用できない場合は、 文字列操作を使用したカリンのアプローチ がその方法です。私は主にPython 3で開発しているので、自分の答えを受け入れ、カリンに賞金を授与します。
残念ながら、float.__format__
を使用した新しいスタイルのフォーマットでさえこれをサポートしていないようです。 float
sのデフォルトのフォーマットは、repr
と同じです。 f
フラグを使用すると、デフォルトで6桁の小数部があります。
>>> format(0.0000000005, 'f')
'0.000000'
しかし、望ましい結果を得るためのハックがあります-最速のものではなく、比較的簡単です:
str()
またはrepr()
を使用して、フロートが文字列に変換されますDecimal
インスタンスがその文字列から作成されます。Decimal.__format__
は、希望する結果を与えるf
フラグをサポートし、float
sとは異なり、デフォルトの精度ではなく実際の精度を出力します。したがって、単純なユーティリティ関数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
を返します。
科学表記法の精度に満足している場合、単純な文字列操作アプローチを採用できますか?多分それはひどく賢くないかもしれませんが、それはうまくいくようです(あなたが提示したすべてのユースケースをパスします)、そして私はそれがかなり理解できると思います:
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))
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
もあり、どちらの関数もゼロの丸めやトリミングなどをカスタマイズするオプションの引数を提供します。
浮動小数点数で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'
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
それがあなたのために働くかどうか私に知らせてください。
興味深い質問です。質問にコンテンツをもう少し追加するために、@ 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)
いずれの場合も、すべてのケースで「一貫した」結果は得られません。
速度を少し犠牲にしても、一貫性を優先します。ユースケースで想定するトレードオフに依存します。