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

это интересный вопрос, который я пытался проработать на днях. Можно ли заставить мантиссы и экспоненты одного float таким же, как другой float в Python?

вопрос возникает потому, что я пытался масштабировать некоторые данные, чтобы min и max соответствовали другому набору данных. Однако мои масштабированные данные были немного выключены (после примерно 6 десятичных знаков), и этого было достаточно, чтобы вызвать проблемы вниз по линии.

чтобы дать идею, У меня f1 и f2 (type(f1) == type(f2) == numpy.ndarray). Я хочу!--9-->. Чтобы добиться этого, я делаю:

import numpy as np

f2 = (f2-np.min(f2))/(np.max(f2)-np.min(f2)) # f2 is now between 0.0 and 1.0
f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1)  # f2 is now between min(f1) and max(f1)

результат (как раз как пример) был бы:

np.max(f1) # 5.0230593
np.max(f2) # 5.0230602 but I need 5.0230593 

моя первоначальная мысль заключается в том, что заставляя представитель float было бы правильным решением. Я не мог найти много на нем, поэтому я сделал решение для моей потребности:

exp = 0
mm = np.max(f1)

# find where the decimal is
while int(10**exp*mm) == 0
  exp += 1

# add 4 digits of precision
exp += 4

scale = 10**exp

f2 = np.round(f2*scale)/scale
f1 = np.round(f1*scale)/scale

теперь np.max(f2) == np.max(f1)

однако, есть ли лучший способ? Я сделала что-то не так? Возможно ли изменить форму float быть похожим на другого float (экспонента или другим способом)?

EDIT: как было предложено, теперь я использую:

scale = 10**(-np.floor(np.log10(np.max(f1))) + 4)

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

4 ответов


TL; DR

использовать

f2 = f2*np.max(f1)-np.min(f1)*(f2-1)  # f2 is now between min(f1) and max(f1)

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

подробности

это не очень простая ошибка для воспроизведения, как вы обнаружили. Однако, работа с плавающими числами подвержена ошибкам. Е. Г., сложения 1 000 000 000 + 0 . 000 000 000 1 дает 1 000 000 000 . 000 000 000 1, но это слишком много значащих цифр даже для двойной точности (которая поддерживает вокруг 15 значащих цифр), поэтому конечная десятичная дробь отбрасывается. Более того, некоторые "короткие" числа не могут быть представлены точно, как отмечено в @Kevin's ответ. См., например, здесь, для более. (Поиск чего-то вроде "ошибки округления с плавающей запятой" для еще больше.)

вот пример, который демонстрирует проблему:

import numpy as np

numpy.set_printoptions(precision=16)

dtype=np.float32                     
f1 = np.linspace(-1000, 0.001, 3, dtype=dtype)
f2 = np.linspace(0, 1, 3, dtype=dtype)

f2 = (f2-np.min(f2))/(np.max(f2)-np.min(f2)) # f2 is now between 0.0 and 1.0
f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1)  # f2 is now between min(f1) and max(f1)

print (f1)
print (f2)

выход

[ -1.0000000000000000e+03  -4.9999951171875000e+02   1.0000000474974513e-03]
[ -1.0000000000000000e+03  -4.9999951171875000e+02   9.7656250000000000e-04]

после @Mark Dickinson's комментарий, я использовал 32-битную плавающую точку. Это согласуется с сообщенной вами ошибкой, относительной ошибкой около 10^-7, около 7-й значимой цифры

In: (5.0230602 - 5.0230593) / 5.0230593
Out: 1.791736760621852e-07

собирается dtype=np.float64 делает вещи лучше, но это все еще не идеально. Программа выше, то дает

[ -1.0000000000000000e+03  -4.9999950000000001e+02   1.0000000000000000e-03]
[ -1.0000000000000000e+03  -4.9999950000000001e+02   9.9999999997635314e-04]

это не идеально, но, как правило, достаточно близко. При сравнении чисел с плавающей запятой вы почти никогда не захотите использовать строгое равенство из-за возможности небольших ошибок, как указано выше. Вместо этого вычитайте одно число из другого и проверьте, что абсолютная разница меньше некоторого допуска, и/или посмотрите на относительную ошибку. См., например, numpy.isclose.

возвращаясь к вашей проблеме, кажется, что это должно быть возможно сделать лучше. Ведь f2 имеет диапазон от 0 до 1, поэтому вы должны иметь возможность реплицировать максимум в f1. Проблема возникает в строке

f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1)  # f2 is now between min(f1) and max(f1)

потому что, когда элемент f2 это 1 Вы делаете намного больше, чем просто умножение 1 на максимум f1, что приводит к возможности возникновения арифметических ошибок с плавающей запятой. Обратите внимание, что вы можете умножить скобки f2*(np.max(f1)-np.min(f1)) to f2*np.max(f1) - f2*np.min(f1), а затем разложим полученный - f2*np.min(f1) + np.min(f1) до np.min(f1)*(f2-1) давать

f2 = f2*np.max(f1)-np.min(f1)*(f2-1)  # f2 is now between min(f1) and max(f1)

Итак, когда элемент f2 это 1, у нас есть 1*np.max(f1) - np.min(f1)*0. И наоборот, когда элемент f2 равно 0, у нас есть 0*np.max(f1) - np.min(f1)*1. Числа 1 и 0 can быть точно представлены, так что не должно быть никаких ошибок.

измененная программа выводит

[ -1.0000000000000000e+03  -4.9999950000000001e+02   1.0000000000000000e-03]
[ -1.0000000000000000e+03  -4.9999950000000001e+02   1.0000000000000000e-03]

т. е. по желанию.

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

альтернативным подходом к приведенному выше, который может быть предпочтительнее, было бы масштабирование и массивы от 0 до 1. Это может быть наиболее подходящая форма для использования в программе. (И оба массива могут быть умножены на коэффициент масштабирования такой оригинальный выбор f1, если это необходимо.)

Re используя округление для решения вашей проблемы, я бы не рекомендую это. Проблема с округлением-помимо того, что это излишне уменьшает точность ваших данных - заключается в том, что числа, которые очень близки, могут округляться в разных направлениях. Е. Г.

f1 = np.array([1.000049])
f2 = np.array([1.000051])
print (f1)
print (f2)
scale = 10**(-np.floor(np.log10(np.max(f1))) + 4)
f2 = np.round(f2*scale)/scale
f1 = np.round(f1*scale)/scale
print (f1)
print (f2)

выход

[ 1.000049]
[ 1.000051]
[ 1.]
[ 1.0001]

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

Re мантиссы и экспоненты, см. math.frexp и math.ldexp, документирован здесь. Однако я бы не рекомендовал устанавливать их самостоятельно (рассмотрим два числа, которые очень близки, но имеют разные показатели, например-вы действительно хотите установить мантиссу). Гораздо лучше просто установить максимум f2 явно до максимума f1, если вы хотите, чтобы убедиться, цифры точно такие же (и аналогично для минимума).


это зависит от того, что вы подразумеваете под "мантиссой"."

внутренне, поплавки хранятся используя научную нотацию в основании 2. Так что если вы имеете в виду база 2 мантисса, на самом деле это очень просто: просто умножьте или разделите на степени двух (а не на степени 10), и мантисса останется прежней (при условии, что показатель не выйдет из диапазона; если это произойдет, вы будете зажаты до бесконечности или нуля, или, возможно, войдете в цифры Донормила в зависимости от архитектуры подробная информация.) Важно понимать, что десятичные расширения не будут совпадать при масштабировании по степеням двух. Это двоичное расширение, которое сохраняется с помощью этого метода.

но если вы имеете в виду базовую 10 мантиссу, нет, это невозможно с поплавками, потому что масштабируемое значение может быть не совсем представимым. Например, 1.1 не может быть представлен точно в базе 2 (с конечным числом цифр) так же, как 1/3 не может быть представлена в базе 10 (С a конечное число цифр). Таким образом, масштабирование 11 вниз на 1/10 не может быть сделано совершенно точно:

>>> print("%1.29f" % (11 * 0.1))
1.10000000000000008881784197001

Вы можете, однако, последний с decimals. Десятичные дроби работают в базе 10 и будут вести себя так, как ожидалось с точки зрения масштабирования базы 10. Они также предоставляют достаточно большое количество специализированных функций для обнаружения и обработки различных видов потери точности. Но десятичные не извлекайте пользу из numpy speedups, так что если у вас очень большой объем данные для работы с ними могут быть недостаточно эффективными для вашего варианта использования. Поскольку NumPy зависит от аппаратной поддержки с плавающей запятой, и большинство (все?) современные архитектуры не обеспечивают аппаратной поддержки базы 10, это не легко исправить.


попробуйте заменить вторую строку с

f2 = f2*np.max(f1) + (1.0-f2)*np.min(f1)

объяснение: есть 2 места, где разница может ползти в:

Шаг 1) f2 = (f2-np.min(f2))/(np.max(f2)-np.min(f2))

при проверке np.min(f2) и np.max(f2), вы получаете точно 0 и 1 или что-то вроде 1.0000003?

Шаг 2) f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1)

выражение типа (a-b)+b не всегда производит точно a, из-за ошибки округления. Предложенное выражение немного больше стабильный.

для более подробного объяснения см. Что Каждый Компьютерщик Должен Знать Об Арифметике С Плавающей Запятой Дэвид Голдберг.


вот один с десятичными

from decimal import Decimal, ROUND_05UP
num1 = Decimal('{:.5f}'.format(5.0230593))  ## Decimal('5.02306')
num2 = Decimal('{}'.format(5.0230602))  ## Decimal('5.0230602')
print num2.quantize(num1, rounding=ROUND_05UP) ## 5.02306

EDIT* * я немного смущен, почему я получаю так много отрицательных отзывов, поэтому вот еще одно решение, не использующее десятичные дроби:

a = 5.0230593
b = 5.0230602
if abs(a - b) < 1e-6:
    b = a