問題描述
我想打印一些浮點數(shù),以便它們始終以十進制形式寫入(例如 12345000000000000000000.0
或 0.000000000000012345
,而不是 科學(xué)記數(shù)法,但我希望結(jié)果達到~15.7 重要數(shù)字 IEEE 754 double,僅此而已.
I want to print some floating point numbers so that they're always written in decimal form (e.g. 12345000000000000000000.0
or 0.000000000000012345
, not in scientific notation, yet I'd want to the result to have the up to ~15.7 significant figures of a IEEE 754 double, and no more.
我想要的是 ideally 以便結(jié)果是位置十進制格式的 最短 字符串,當(dāng)轉(zhuǎn)換為 浮動
.
What I want is ideally so that the result is the shortest string in positional decimal format that still results in the same value when converted to a float
.
眾所周知,如果指數(shù)大于 15 或小于 -4,則 float
的 repr
以科學(xué)計數(shù)法編寫:
It is well-known that the repr
of a float
is written in scientific notation if the exponent is greater than 15, or less than -4:
>>> n = 0.000000054321654321
>>> n
5.4321654321e-08 # scientific notation
如果使用了str
,結(jié)果字符串又是科學(xué)計數(shù)法:
If str
is used, the resulting string again is in scientific notation:
>>> str(n)
'5.4321654321e-08'
<小時>
有人建議我可以使用帶有 f
標(biāo)志的 format
和足夠的精度來擺脫科學(xué)記數(shù)法:
It has been suggested that I can use format
with f
flag and sufficient precision to get rid of the scientific notation:
>>> format(0.00000005, '.20f')
'0.00000005000000000000'
它適用于該數(shù)字,盡管它有一些額外的尾隨零.但是對于 .1
,同樣的格式會失敗,它給出的十進制數(shù)字超出了 float 的實際機器精度:
It works for that number, though it has some extra trailing zeroes. But then the same format fails for .1
, which gives decimal digits beyond the actual machine precision of float:
>>> format(0.1, '.20f')
'0.10000000000000000555'
如果我的號碼是 4.5678e-20
,使用 .20f
仍然會失去相對精度:
And if my number is 4.5678e-20
, using .20f
would still lose relative precision:
>>> format(4.5678e-20, '.20f')
'0.00000000000000000005'
因此這些方法不符合我的要求.
這導(dǎo)致了一個問題:以十進制格式打印任意浮點數(shù)的最簡單且性能良好的方法是什么,其數(shù)字與 repr(n)
(或 Python 3 上的 str(n)
),但始終使用十進制格式,而不是科學(xué)記數(shù)法.
This leads to the question: what is the easiest and also well-performing way to print arbitrary floating point number in decimal format, having the same digits as in repr(n)
(or str(n)
on Python 3), but always using the decimal format, not the scientific notation.
即例如將浮點值0.00000005
轉(zhuǎn)換為字符串'0.00000005'
的函數(shù)或操作;0.1
到 '0.1'
;420000000000000000.0
到 '420000000000000000.0'
或 420000000000000000
并將浮點值 -4.5678e-5
格式化為 '-0.000045678'
.
That is, a function or operation that for example converts the float value 0.00000005
to string '0.00000005'
; 0.1
to '0.1'
; 420000000000000000.0
to '420000000000000000.0'
or 420000000000000000
and formats the float value -4.5678e-5
as '-0.000045678'
.
在賞金期之后:似乎至少有兩種可行的方法,正如 Karin 證明的那樣,與我在 Python 2 上的初始算法相比,使用字符串操作可以顯著提高速度.
After the bounty period: It seems that there are at least 2 viable approaches, as Karin demonstrated that using string manipulation one can achieve significant speed boost compared to my initial algorithm on Python 2.
因此,
- 如果性能很重要并且需要兼容 Python 2;或者如果由于某種原因不能使用
decimal
模塊,那么 Karin 使用字符串操作的方法 是方法. - 在 Python 3 上,我稍微短一些的代碼也會更快.
- If performance is important and Python 2 compatibility is required; or if the
decimal
module cannot be used for some reason, then Karin's approach using string manipulation is the way to do it. - On Python 3, my somewhat shorter code will also be faster.
由于我主要在 Python 3 上進行開發(fā),我將接受我自己的答案,并將獎勵 Karin.
Since I am primarily developing on Python 3, I will accept my own answer, and shall award Karin the bounty.
推薦答案
不幸的是,似乎連帶有 float.__format__
的新格式格式都不支持這一點.float
s 的默認(rèn)格式與 repr
相同;并且?guī)в?f
標(biāo)志,默認(rèn)情況下有 6 個小數(shù)位:
Unfortunately it seems that not even the new-style formatting with float.__format__
supports this. The default formatting of float
s is the same as with repr
; and with f
flag there are 6 fractional digits by default:
>>> format(0.0000000005, 'f')
'0.000000'
<小時>
然而,有一個技巧可以得到想要的結(jié)果——不是最快的,但相對簡單:
However there is a hack to get the desired result - not the fastest one, but relatively simple:
- 首先使用
str()
或repr()
將浮點數(shù)轉(zhuǎn)換為字符串 - 然后是一個新的
十進制
實例是從該字符串創(chuàng)建的. Decimal.__format__
支持提供所需結(jié)果的f
標(biāo)志,并且與float
不同,它打印實際精度而不是默認(rèn)精度.
- first the float is converted to a string using
str()
orrepr()
- then a new
Decimal
instance is created from that string. Decimal.__format__
supportsf
flag which gives the desired result, and, unlikefloat
s it prints the actual precision instead of default precision.
因此我們可以制作一個簡單的效用函數(shù)float_to_str
:
Thus we can make a simple utility function 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')
必須注意不要使用全局十進制上下文,因此為此函數(shù)構(gòu)造了一個新上下文.這是最快的方法;另一種方法是使用 decimal.local_context
但它會更慢,為每次轉(zhuǎn)換創(chuàng)建一個新的線程本地上下文和一個上下文管理器.
Care must be taken to not use the global decimal context, so a new context is constructed for this function. This is the fastest way; another way would be to use decimal.local_context
but it would be slower, creating a new thread-local context and a context manager for each conversion.
此函數(shù)現(xiàn)在返回包含尾數(shù)中所有可能數(shù)字的字符串,四舍五入為最短等效表示:
This function now returns the string with all possible digits from mantissa, rounded to the shortest equivalent representation:
>>> 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'
最后一位結(jié)果四舍五入
正如@Karin 所說,float_to_str(420000000000000000.0)
與預(yù)期的格式不完全匹配;它返回 420000000000000000
而沒有尾隨 .0
.
As @Karin noted, float_to_str(420000000000000000.0)
does not strictly match the format expected; it returns 420000000000000000
without trailing .0
.
這篇關(guān)于將浮點數(shù)轉(zhuǎn)換為位置格式的字符串(沒有科學(xué)記數(shù)法和錯誤精度)的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網(wǎng)!