Извлечение значимого и показателя степени для представления base-10 из десятичной строки

Я ищу эффективную реализацию Python функции, которая принимает строку в десятичном формате, например

2.05000
200
0.012

и возвращает кортеж из двух целых чисел, представляющих значение и экспоненту ввода в формате с плавающей запятой 10, например

(205,-2)
(2,2)
(12,-3)

Понимание списка было бы хорошим бонусом.

У меня есть ощущение, что существует эффективный (и, возможно, Pythonic) способ сделать это, но это ускользает от меня...


Раствор наносится на панд

import pandas as pd
import numpy as np
ser1 = pd.Series(['2.05000', '- 2.05000', '00 205', '-205', '-0', '-0.0', '0.00205', '0', np.nan])

ser1 = ser1.str.replace(' ', '')
parts = ser1.str.split('.').apply(pd.Series)

# remove all white spaces
# strip leading zeros (even those after a minus sign)
parts.ix[:,0] = '-'*parts.ix[:,0].str.startswith('-') + parts.ix[:,0].str.lstrip('-').str.lstrip('0')

parts.ix[:,1] = parts.ix[:,1].fillna('')        # fill non-existamt decimal places
exponents = -parts.ix[:,1].str.len()
parts.ix[:,0] += parts.ix[:,1]                  # append decimal places to digit before decimal point

parts.ix[:,1] = parts.ix[:,0].str.rstrip('0')   # strip following zeros

exponents += parts.ix[:,0].str.len() - parts.ix[:,1].str.len()

parts.ix[:,1][(parts.ix[:,1] == '') | (parts.ix[:,1] == '-')] = '0'
significands = parts.ix[:,1].astype(float)

df2 = pd.DataFrame({'exponent': exponents, 'significand': significands})
df2

Входные данные:

0      2.05000
1    - 2.05000
2       00 205
3         -205
4           -0
5         -0.0
6      0.00205
7            0
8          NaN
dtype: object

Выход:

   exponent  significand
0        -2          205
1        -2         -205
2         0          205
3         0         -205
4         0            0
5         0            0
6        -5          205
7         0            0
8       NaN          NaN

[9 rows x 2 columns]

4 ответа

Решение

Вот простое решение для обработки строк.

def sig_exp(num_str):
    parts = num_str.split('.', 2)
    decimal = parts[1] if len(parts) > 1 else ''
    exp = -len(decimal)
    digits = parts[0].lstrip('0') + decimal
    trimmed = digits.rstrip('0')
    exp += len(digits) - len(trimmed)
    sig = int(trimmed) if trimmed else 0
    return sig, exp

>>> for x in ['2.05000', '200', '0.012', '0.0']:
    print sig_exp(x)

(205, -2)
(2, 2)
(12, -3)
(0, 0)

Я оставлю обработку отрицательных чисел в качестве упражнения для читателя.

Взгляни на decimal.Decimal:

>>> from decimal import Decimal
>>> s = '2.05000'
>>> x = Decimal(s)
>>> x
Decimal('2.05000')
>>> x.as_tuple()
DecimalTuple(sign=0, digits=(2, 0, 5, 0, 0, 0), exponent=-5)

Делает почти то, что вам нужно, просто конвертировать DecimalTuple в желаемый формат, например:

>>> t = Decimal('2.05000').as_tuple()
>>> (''.join(str(x) for i,x in enumerate(t.digits) if any(t.digits[i:])),
... t.exponent + sum(1 for i,x in enumerate(t.digits) if not 
... any (t.digits[i:])))
('205', -2)

Просто набросок, но удовлетворяет вашим трем тестам.

Вы можете захотеть .normalize() ваш Decimal прежде чем обрабатывать .as_tuple() (спасибо @georg), это заботится о конечных нулях. Таким образом, вам не нужно много форматировать:

>>> Decimal('2.05000').normalize().as_tuple()
DecimalTuple(sign=0, digits=(2, 0, 5), exponent=-2)

Таким образом, ваша функция может быть записана как:

>>> def decimal_str_to_sci_tuple(s):
...  t = Decimal(s).normalize().as_tuple()
...  return (int(''.join(map(str,t.digits))), t.exponent)
... 
>>> decimal_str_to_sci_tuple('2.05000')
(205, -2)
>>> decimal_str_to_sci_tuple('200')
(2, 2)
>>> decimal_str_to_sci_tuple('0.012')
(12, -3)

(не забудьте добавить t.sign при поддержке отрицательных чисел, хотя).

Если вы ищете научную нотацию, вы можете использовать десятичный формат и формат:

numbers = ['2.05000','200','0.01','111']
print ["{:.2E}".format(Decimal(n)) for n in numbers]

выход:

['2.05E+0', '2.00E+2', '1.00E-2']

Если вы ищете,

  1. Получить цифру, отличную от 0, в правой части
  2. Получить научную запись до правой цифры

    from decimal import  *
    numbers = ['2.05000','200','0.01','111']
    numbers = [ n.rstrip('0') if '.' in n else n  for n in numbers ] #strip right         zeros if found after .
    for n in numbers:
        if '.' in n:
            num = n.split('.')[0]
            dec = n.split('.')[1]
            tenthNumber = len(dec)
            print (Decimal(num+dec), -1 * tenthNumber)
        elif n.endswith('0'): 
            tenthNumber = 0
            revN = n[::-1]
            for i in range(len(revN)):
                if revN[i]=='0':
                    tenthNumber = tenthNumber + 1
                else:
                    break
            print (n[:(len(n)-tenthNumber)], str(tenthNumber))
    
        else:
            print (n,0)
    

Выход:

(Decimal('205'), -2)
('2', '2')
(Decimal('1'), -2)
('111', 0)

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

      def scd_exp(scnum):
    scnum = "{:.2e}".format(scnum)
    return (float(scnum[:4]),int(scnum[-3:]))


numbers = [2.05, 205, 0.0001576, 111]
for number in numbers:
    print(scd_exp(number))

результат

      (2.05, 0)
(2.05, 2)
(1.58, -4)
(1.11, 2)

Если вы хотите самостоятельно устанавливать значение округления при каждом вызове функции (скажем, до 6 цифр для примера), вы можете написать

      def scd_exp(scnum, roundafter):
    formstr = "".join(("{:.",str(roundafter),"e}"))
    scnum = formstr.format(scnum)     
    return (float(scnum[:roundafter+2]),int(scnum[-3:]))


numbers = [2.05, 205, 0.000157595678, 111]
for number in numbers:
    print(scd_exp(number, 6))

который возвращает

      (2.05, 0)
(2.05, 2)
(1.575957, -4)
(1.11, 2)
Другие вопросы по тегам