Определить точность и масштаб определенного числа в Python
У меня есть переменная в Python, содержащая число с плавающей запятой (например, num = 24654.123
), и я хотел бы определить значения точности и масштаба числа (в смысле Oracle), поэтому 123,45678 должно дать мне (8,5), 12,76 должно дать мне (4,2) и т. д.
Я впервые подумал об использовании строкового представления (через str
или же repr
), но они не подходят для больших чисел (хотя теперь я понимаю, что проблема заключается в ограничениях представления с плавающей запятой):
>>> num = 1234567890.0987654321
>>> str(num) = 1234567890.1
>>> repr(num) = 1234567890.0987654
Редактировать:
Хорошие моменты ниже. Я должен уточнить. Номер уже является числом с плавающей точкой и передается в базу данных через cx_Oracle. Я пытаюсь сделать все возможное в Python, чтобы обрабатывать числа с плавающей запятой, которые слишком велики для соответствующего типа базы данных, за исключением выполнения INSERT и обработки ошибок Oracle (потому что я хочу иметь дело с числами поле, а не запись, в время). Похоже map(len, repr(num).split('.'))
ближе всего я доберусь до точности и масштаба поплавка?
12 ответов
Получить количество цифр слева от десятичной точки очень просто:
int(log10(x))+1
Количество цифр справа от десятичной запятой сложнее из-за присущей ей погрешности значений с плавающей запятой. Мне понадобится еще несколько минут, чтобы понять это.
Изменить: на основе этого принципа, вот полный код.
import math
def precision_and_scale(x):
max_digits = 14
int_part = int(abs(x))
magnitude = 1 if int_part == 0 else int(math.log10(int_part)) + 1
if magnitude >= max_digits:
return (magnitude, 0)
frac_part = abs(x) - int_part
multiplier = 10 ** (max_digits - magnitude)
frac_digits = multiplier + int(multiplier * frac_part + 0.5)
while frac_digits % 10 == 0:
frac_digits /= 10
scale = int(math.log10(frac_digits))
return (magnitude + scale, scale)
Не возможно с переменными с плавающей точкой. Например, набрав
>>> 10.2345
дает:
10.234500000000001
Итак, чтобы получить 6,4 из этого, вам нужно будет найти способ отличить пользователя, входящего 10.2345
а также 10.234500000000001
, что невозможно с помощью поплавков. Это связано со способом хранения чисел с плавающей запятой. использование decimal
,
import decimal
a = decimal.Decimal('10.234539048538495')
>>> str(a)
'10.234539048538495'
>>> (len(str(a))-1, len(str(a).split('.')[1]))
(17,15)
Похоже на str
это лучший выбор, чем repr
:
>>> r=10.2345678
>>> r
10.234567800000001
>>> repr(r)
'10.234567800000001'
>>> str(r)
'10.2345678'
Я думаю, вы должны рассмотреть использование десятичного типа вместо float
, float
Тип выдаст ошибки округления, потому что числа представлены внутри в двоичном виде, но многие десятичные числа не имеют точного двоичного представления.
(0) Пожалуйста, подтвердите или опровергните: вам даны числа с плавающей запятой для использования, это неизбежно, вы не можете получить данные как десятичные, типы данных Oracle включают типы на основе десятичных чисел, и это фундаментальное несоответствие неизбежно. Пожалуйста, объясните любой полный или частичный отказ.
(1) Ваше замечание "потерпеть неудачу для больших чисел" вводит в заблуждение / не имеет значения / неверно - вы говорите, что отправной точкой является число с плавающей точкой, но 1234567890.0987654321 не может быть представлено как число с плавающей точкой, как показано результатом repr ().
(2) Возможно, вы могли бы использовать NEW repr (Python 2.7 и 3.1), который обеспечивает минимально возможную точность repr(x), которая все еще удовлетворяет float(repr(x)) == x
Например, старый repr (1.1) выдает "1.1000000000000001", новый repr (1.1) выдает "1.1"
О том, что "Я думаю, карта (len, repr(num).split('.')) - самая близкая к точности и масштабу плавания?": Вам нужна стратегия для обработки (а) отрицательных и нулевых числа (б) числа как 1.1e20
Копирование в Objects/floatobject.c должно включить код C для нового repr () объекта с плавающей точкой, если вам нужно использовать Python 2.6 или более раннюю версию.
(3) Возможно, если бы вы сообщили нам спецификации для соответствующих типов данных Oracle, мы могли бы помочь вам разработать проверки для выбора того, какой тип может содержать данное значение с плавающей запятой.
def get_precision(f1):
str1=str(f1)
return len(str1.split(".")[1])
В принципе, вы не можете с числами с плавающей запятой. Использование десятичного типа поможет, и если вы хотите действительно большую точность, подумайте об использовании gmpy
, порт библиотеки GNU Multiple Precision для Python.
Вот еще одинDecimal
подход, который будет работать, по крайней мере, для некоторых случаев использования. Будет ли это работать всегда, зависит от того, что именно вы ищете.
123,45678 должно дать мне (8,5), 12,76 должно дать мне (4,2),
from decimal import Decimal
def get_precision_and_scale(num: float):
# Cast float to string to get shortest round-trippable representation
d_num = Decimal(str(num))
sign, digits, exp = d_num.as_tuple()
scale = len(digits)
precision = abs(exp)
return scale, precision
print(get_precision_and_scale(123.45678))
# (8, 5)
print(get_precision_and_scale(12.76))
# (4, 2)
Если вам нужно проверить количество соответствующих цифр (a и b)
def prec_check(a, b):
a = str(a)
b = str(b)
do = bool(True)
n = 0
while do == True:
if a and b and a[n] == a[b]:
n += 1
else:
do = false
return n
Обратите внимание, что это не работает с модулем "Десятичный".
Я нашел другое решение, которое кажется более простым, но я не уверен, будет ли оно работать во всех случаях.
import math
x = 1.2345678
def flip(string):
result = ""
for ch in string:
result = ch + result
return result
prec = int(math.log10(float(flip(str(x)))) + 1 # precision as int
Если вам нужно проверить точность, вы можете попробовать:
def prec_check(a,b)
a = str(a)
b = str(b)
do = bool(True)
n = 0
while do == True:
if a and b and a[n] == a[b]:
n += 1
else:
do = false
return n
Количество символов после запятой. Работает с
int
,
float
а также
Decimal
типы.
def get_num_precision(num):
count = 0
while num * 10**count % 1 != 0:
count += 1
return count