Подтвердить что ты не робот

Преобразование Python Float в String без потери точности

Я поддерживаю Python script, который использует xlrd для извлечения значений из электронных таблиц Excel, а затем выполняет различные действия с ними. Некоторые ячейки в электронной таблице являются высокоточными числами, и они должны оставаться таковыми. При извлечении значений одной из этих ячеек xlrd дает мне float, например 0,38288746115497402.

Однако мне нужно получить это значение в строке позже в коде. Выполнение str(value) или unicode(value) приведет к возврату примерно как "0.382887461155". Требования говорят, что это неприемлемо; точность должна быть сохранена.

Я пробовал пару вещей до сих пор без успеха. Первый использовал форматирование строк:

data = "%.40s" % (value) 
data2 = "%.40r" % (value) 

Но оба производят одинаковое округленное число, "0.382887461155".

При поиске людей с аналогичными проблемами на SO и в других местах в Интернете было предложено использовать класс Decimal. Но я не могу изменить способ передачи данных мне (если кто-то не знает секретного способа сделать xlrd return Decimals). И когда я пытаюсь это сделать:

data = Decimal(value)

Я получаю a TypeError: Cannot convert float to Decimal. First convert the float to a string. Но, очевидно, я не могу преобразовать его в строку, иначе я потеряю точность.

Так что да, я открыт для любых предложений - даже очень грубых/взломанных, если это необходимо. Я не очень опытен с Python (больше сам парень из Java/С#), поэтому не стесняйтесь исправлять меня, если у меня есть какое-то фундаментальное недоразумение здесь.

EDIT: Просто подумал, что добавлю, что я использую Python 2.6.4. Я не думаю, что есть какие-то формальные требования, мешающие мне менять версии; он просто не должен испортить любой другой код.

4b9b3361

Ответ 1

Я автор xlrd. Существует так много путаницы в других ответах и ​​комментариях, чтобы опровергнуть комментарии, поэтому я делаю это в ответ.

@katriealex: "точность теряется в кишках xlrd "" "--- совершенно необоснованна и неверна. xlrd воспроизводит точно 64-битный float, который хранится в файле XLS.

@katriealex: "Возможно, вы сможете изменить локальную установку xlrd, чтобы изменить приведение float" "" --- Я не знаю, почему вы хотели бы это сделать; вы не теряете точности, плавая 16-битное целое!!! В любом случае этот код используется только при чтении файлов Excel 2.X(у которых была запись ячейки типа INTEGER). ОП не указывает, что он читает такие древние файлы.

@jloubert: Вы должны ошибаться. "%.40r" % a_float - это просто барочный способ получить тот же ответ, что и repr(a_float).

@EVERYBODY: вам не нужно преобразовывать float в десятичный знак, чтобы сохранить точность. Вся точка функции repr() заключается в том, что гарантируется следующее:

float(repr(a_float)) == a_float

Python 2.X(X <= 6) repr дает постоянную 17 десятичных цифр точности, так как гарантируется воспроизведение исходного значения. Позже Pythons (2.7, 3.1) дают минимальное число десятичных цифр, которые будут воспроизводить исходное значение.

Python 2.6.4 (r264:75708, Oct 26 2009, 08:23:19) [MSC v.1500 32 bit (Intel)] on win32
>>> f = 0.38288746115497402
>>> repr(f)
'0.38288746115497402'
>>> float(repr(f)) == f
True

Python 2.7 (r27:82525, Jul  4 2010, 09:01:59) [MSC v.1500 32 bit (Intel)] on win32
>>> f = 0.38288746115497402
>>> repr(f)
'0.382887461154974'
>>> float(repr(f)) == f
True

Итак, нижняя строка , если вы хотите, чтобы строка сохраняла всю точность объекта float, используйте preserved = repr(the_float_object)... восстановить значение позже float(preserved).. Это простое, Нет необходимости в модуле decimal.

Ответ 2

Вы можете использовать repr() для преобразования в строку без потери точности, затем конвертировать в десятичное число:

>>> from decimal import Decimal
>>> f = 0.38288746115497402
>>> d = Decimal(repr(f))
>>> print d
0.38288746115497402

Ответ 3

EDIT: Я ошибаюсь. Я оставлю этот ответ здесь, так что остальная часть потока имеет смысл, но это не так. Пожалуйста, см. Ответ Джона Мачина выше. Спасибо, ребята =).

Если вышеупомянутые ответы работают так здорово - это сэкономит вам много неприятного взлома. Однако, по крайней мере, в моей системе они не будут. Вы можете проверить это, например,

import sys
print( "%.30f" % sys.float_info.epsilon )

Этот номер является наименьшим поплавком, который ваша система может отличить от нуля. Все, что меньше, может быть случайно добавлено или вычтено из любого поплавка при выполнении операции. Это означает, что, по крайней мере, на моей настройке Python, точность теряется внутри кишок xlrd, и, похоже, не делайте ничего, что вы можете сделать, не изменяя его. Что странно; Я бы ожидал, что это дело произошло раньше, но, по-видимому, не!

Возможно изменение локальной установки xlrd для изменения приведения float. Откройте site-packages\xlrd\sheet.py и перейдите к строке 1099:

...
elif rc == XL_INTEGER:
                    rowx, colx, cell_attr, d = local_unpack('<HH3sH', data)
                    self_put_number_cell(rowx, colx, float(d), self.fixed_BIFF2_xfindex(cell_attr, rowx, colx))
...

Обратите внимание на float cast - вы можете попробовать изменить это на decimal.Decimal и посмотреть, что произойдет.

Ответ 4

РЕДАКТИРОВАТЬ: Сбросил мой предыдущий ответ b/c, он не работал должным образом.

Я на Python 2.6.5, и это работает для меня:

a = 0.38288746115497402
print repr(a)
type(repr(a))    #Says it a string

Примечание. Это просто преобразуется в строку. Вам потребуется преобразовать в Decimal самостоятельно позже, если это необходимо.

Ответ 5

Как уже было сказано, поплавок совсем не точен, поэтому сохранение точности может несколько заблуждаться.

Здесь можно получить каждый последний бит информации из объекта float:

>>> from decimal import Decimal
>>> str(Decimal.from_float(0.1))
'0.1000000000000000055511151231257827021181583404541015625'

Другой способ будет таким.

>>> 0.1.hex()
'0x1.999999999999ap-4'

Обе строки представляют собой точное содержимое поплавка. Все остальное интерпретирует float, поскольку python считает, что он, вероятно, был предназначен (что в большинстве случаев верно).