Python - среднее числового массива, десятичных разрядов, числа с плавающей запятой и десятичное число - PullRequest
0 голосов
/ 05 ноября 2019

Рассмотрим следующий код (Python 3.8.0 на MINGW64 в MSYS2 в Windows 10):

import numpy as np
from decimal import Decimal

aa = [25744, 25687, 25641, 25601, 25566, 25533, 25505, 25479, 25456, 25435]
npaa = np.array(aa)

print(np.mean(npaa))                            # 25564.7
print(0.001*np.mean(npaa))                      # 25.564700000000002

print( Decimal(np.mean(npaa)) )                 # 25564.70000000000072759576141834259033203125
print( Decimal(0.001)*Decimal(np.mean(npaa)) )  # 25.56470000000000125976798437

Итак, среднее из приведенного выше списка целых чисел сначала печатается как 25564.7, чтоЯ ожидаю и хочу получить.

Но затем, как только я умножу это число на 0,0001, я получу тонну десятичных разрядов, вероятно, из-за точности float (im).

Итак,Я подумал - какого черта, я просто собираюсь использовать десятичный класс, тогда я должен получить «правильное» количество десятичных дробей в этом случае.

Но потом, как только я попробую Decimal(np.mean(npaa)),Я получаю среднее значение в виде набора десятичных знаков: 25564.70000000000072759576141834259033203125

Так что, очевидно, np.mean(npaa) уже содержит эти десятичные дроби - но по какой-то причине они просто не были напечатаны.

Так что этоДело в том, что, поскольку все, что у меня есть в списке, это целые числа, а в списке их 10, математически (в данном случае) невозможно получить какой-либо другой результат, кроме числа с 1 десятичным знаком и 1 только десятичным.

ТеперьЯ мог бы обойти это, напечатав среднее число в виде строки и отформатировав его как 1 десятичный, как в "{:.1f}".format(np.mean(npaa)), а затем использовать эту строку в качестве источника десятичного числа - и это работает;но затем у меня есть другие массивы, длина которых не равна 10, и я хотел бы, чтобы минимальное количество десятичных знаков автоматически присутствовало в переменной - без необходимости вручную определять, какое количество десятичных знаков мне следует ожидать, а затем форматировать ихв виде строки.

Так что я могу просто попытаться использовать массив десятичных дробей (как пытался сделать связанный пост), что не совсем тривиально:

print( np.array(aa, dtype=Decimal) )          # [25744 25687 25641 25601 25566 25533 25505 25479 25456 25435]
print( np.array(aa).astype(Decimal) )         # [25744 25687 25641 25601 25566 25533 25505 25479 25456 25435]
print( np.array([Decimal(ax) for ax in aa]) ) # [Decimal('25744') Decimal('25687') Decimal('25641') Decimal('25601') Decimal('25566') Decimal('25533') Decimal('25505') Decimal('25479')  Decimal('25456') Decimal('25435')]

print( np.mean( np.array([Decimal(ax) for ax in aa]) ) )                # 25564.7
print( type(np.mean( np.array([Decimal(ax) for ax in aa]) )) )          # <class 'decimal.Decimal'>
print( Decimal(0.001)*np.mean( np.array([Decimal(ax) for ax in aa]) ) ) # 25.56470000000000053217222296

... и все же, даже , если у меня теперь есть Decimal 25564,7 и Decimal 0,001, когда я их умножаю - в домене Decimal! - Я все еще получаю 25.56470000000000053217222296!?

Как, черт возьми, я могу получить Python для вычисления 0,001 * 25564.7 как 25.5647, что и должно быть - без необходимости "кастовать", то естьвывести десятичное / плавающее значение в виде строки с ограниченным числом десятичных знаков? Разве десятичный класс не должен был это делать?


РЕДАКТИРОВАТЬ: Итак, я попробовал также подход sum()/len(), как в связанном посте - сначала я подумал, что он это делает, но№:

print( sum(aa)/len(aa) )                # 25564.7
print( 0.001*sum(aa)/len(aa) )          # 25.5647
print( Decimal(0.001*sum(aa)/len(aa)) ) # 25.564699999999998425437297555617988109588623046875

print( sum(npaa)/len(npaa) )                 # 25564.7
print( 0.001*sum(npaa)/len(npaa) )           # 25.5647
print( Decimal(0.001*sum(npaa)/len(npaa)) )  # 25.564699999999998425437297555617988109588623046875

https://docs.python.org/2/tutorial/floatingpoint.html

Между прочим, десятичный модуль также предоставляет хороший способ «увидеть» точное значение, которое хранится в любом конкретном Python-плавающем

https://docs.python.org/2/library/decimal.html

Десятичные числа могут быть представлены точно. Напротив, числа типа 1.1 и 2.2 не имеют точных представлений в двоичной форме с плавающей точкой. Конечные пользователи обычно не ожидают, что 1.1 + 2.2 будет отображаться как 3.3000000000000003, как это происходит с двоичной плавающей точкой.

Итак, если Decimal числа могут быть представлены точно, почему я получаюта же неточность с плавающей запятой, когда я использую класс Decimal в этом контексте?

1 Ответ

0 голосов
/ 05 ноября 2019

ОК, я собираюсь опубликовать это как ответ - благодаря комментариям @ user2357112, теперь я знаю, что независимо от того, как сначала можно напечатать плавающее число (возможно, округленное), если я брошу / приведус плавающей точкой в ​​Decimal, я все равно получу ту же ошибку округления. Итак, я должен либо работать на уровне целых чисел с самого начала, либо на уровнях строк.

Итак, в данном конкретном случае (целые числа в массиве, ища среднее) я могу сделать это:

tmean = Decimal(int(sum(npaa)))/Decimal(len(npaa))
print( type(tmean), tmean ) # <class 'decimal.Decimal'> 25564.7

Итак, я в основном сначала запускаю сумму массива, котораярезультаты с целым числом, которое я приведу к Decimal. Обратите внимание, что сначала мне нужно привести сумму во встроенный Python int, в противном случае:

Decimal(sum(npaa))/Decimal(len(npaa)) # TypeError: conversion from numpy.int32 to Decimal is not supported

И, оказывается, довольно сложно «привести» весь массив numpy во встроенный Pythonint: создание и использование массива NumPy с dtype встроенного типа int , поэтому остается только привести сумму как единое число к встроенному int.

Но как только это будет сделано, я получу среднее значение как деление двух целых чисел класса Decimal - в этом случае я могу разумно ожидать правильное количество десятичных знаков (в данном случае одно) в окончательном вычислении среднегочисло.

РЕДАКТИРОВАТЬ: и в конце, если я хочу вычислить это среднее значение 0,001, мне нужно использовать его как строку в Decimal('0.001')*tmean, чтобы получить правильное количество десятичных знаков - если я просто использую Decimal(0.001) с аргументом float, неточность float уже закралась, и просто использование класса Decimal больше не может помочь.

...