Преобразование Python Float в строку без потери точности - PullRequest
26 голосов
/ 14 августа 2010

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

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

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

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

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

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

data = Decimal(value)

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

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

РЕДАКТИРОВАТЬ: Просто подумал, что я хотел бы добавить, что я использую Python 2.6.4. Я не думаю, что есть какие-то формальные требования, мешающие мне менять версии; это просто не должно испортить любой другой код.

Ответы [ 5 ]

49 голосов
/ 14 августа 2010

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

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

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

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

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

float(repr(a_float)) == a_float

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

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

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

2 голосов
/ 14 августа 2010

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

>>> from decimal import Decimal
>>> f = 0.38288746115497402
>>> d = Decimal(repr(f))
>>> print d
0.38288746115497402
1 голос
/ 14 августа 2010

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

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

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 - вы можете попробовать изменить его на decimal.Decimal и посмотреть, что произойдет.

0 голосов
/ 14 августа 2010

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

Вот способ получить каждый последний бит информации из объекта с плавающей точкой:*

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

Другой способ был бы таким:

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

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

0 голосов
/ 14 августа 2010

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

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

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

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

...