問題描述
我想打印一些浮點數,以便它們始終以十進制形式寫入(例如 12345000000000000000000.0
或 0.000000000000012345
,而不是 科學記數法,但我希望結果達到~15.7 重要數字 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 以便結果是位置十進制格式的 最短 字符串,當轉換為 浮動
.
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
.
眾所周知,如果指數大于 15 或小于 -4,則 float
的 repr
以科學計數法編寫:
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
,結果字符串又是科學計數法:
If str
is used, the resulting string again is in scientific notation:
>>> str(n)
'5.4321654321e-08'
<小時>
有人建議我可以使用帶有 f
標志的 format
和足夠的精度來擺脫科學記數法:
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'
它適用于該數字,盡管它有一些額外的尾隨零.但是對于 .1
,同樣的格式會失敗,它給出的十進制數字超出了 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'
因此這些方法不符合我的要求.
這導致了一個問題:以十進制格式打印任意浮點數的最簡單且性能良好的方法是什么,其數字與 repr(n)
(或 Python 3 上的 str(n)
),但始終使用十進制格式,而不是科學記數法.
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
轉換為字符串'0.00000005'
的函數或操作;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 上進行開發,我將接受我自己的答案,并將獎勵 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 的默認格式與 repr
相同;并且帶有 f
標志,默認情況下有 6 個小數位:
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'
<小時>
然而,有一個技巧可以得到想要的結果——不是最快的,但相對簡單:
However there is a hack to get the desired result - not the fastest one, but relatively simple:
- 首先使用
str()
或repr()
將浮點數轉換為字符串 - 然后是一個新的
十進制
實例是從該字符串創建的. Decimal.__format__
支持提供所需結果的f
標志,并且與float
不同,它打印實際精度而不是默認精度.
- 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.
因此我們可以制作一個簡單的效用函數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')
必須注意不要使用全局十進制上下文,因此為此函數構造了一個新上下文.這是最快的方法;另一種方法是使用 decimal.local_context
但它會更慢,為每次轉換創建一個新的線程本地上下文和一個上下文管理器.
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.
此函數現在返回包含尾數中所有可能數字的字符串,四舍五入為最短等效表示:
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'
最后一位結果四舍五入
正如@Karin 所說,float_to_str(420000000000000000.0)
與預期的格式不完全匹配;它返回 420000000000000000
而沒有尾隨 .0
.
As @Karin noted, float_to_str(420000000000000000.0)
does not strictly match the format expected; it returns 420000000000000000
without trailing .0
.
這篇關于將浮點數轉換為位置格式的字符串(沒有科學記數法和錯誤精度)的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!