Увеличьте значение python с плавающей запятой на наименьшую возможную величину



Я использую значения с плавающей запятой в качестве ключей словаря.



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



В C я бы скрутил биты мантиссы, чтобы достичь этого, но я предполагаю, что это невозможно в python.

331   14  

14 ответов:

увеличить python с плавающей точкой значение на наименьшую возможную величину

вы не сумасшедший, и вы должны быть в состоянии сделать это. Это текущий недостаток математической библиотеки Python, к сожалению, как в Python 2.X и Python3000. Там должно быть math.nextafter(x,y) в Python, но нет. Было бы тривиально, чтобы добавить, поскольку большинство компиляторов имеют функции.

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

The nextafter() функции являются частью POSIX и ISO C99 стандарты и _nextafter () в Visual C. C99 совместимые стандартные математические библиотеки, Visual C, C++, Boost и Java все реализуют рекомендованный IEEE nextafter() функции или методы. (Я честно не знаю, есть ли у .NET nextafter (). Microsoft не очень заботится о C99 или POSIX.)

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

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

есть два способы получить nextafter() или другие исключенные математические функции POSIX в Python:

Используйте Numpy:

>>> import numpy
>>> numpy.nextafter(0,1)
4.9406564584124654e-324
>>> numpy.nextafter(.1, 1)
0.10000000000000002
>>> numpy.nextafter(1e6, -1)
999999.99999999988
>>> numpy.nextafter(-.1, 1)
-0.099999999999999992

ссылка непосредственно на системную математику DLL:

import ctypes
import sys
from sys import platform as _platform

if _platform == "linux" or _platform == "linux2":
    _libm = ctypes.cdll.LoadLibrary('libm.so.6')
    _funcname = 'nextafter'
elif _platform == "darwin":
    _libm = ctypes.cdll.LoadLibrary('libSystem.dylib')
    _funcname = 'nextafter'
elif _platform == "win32":
    _libm = ctypes.cdll.LoadLibrary('msvcrt.dll')
    _funcname = '_nextafter'
else:
    # these are the ones I have access to...
    # fill in library and function name for your system math dll
    print "Platform", repr(_platform), "is not supported"
    sys.exit(0)

_nextafter = getattr(_libm, _funcname)
_nextafter.restype = ctypes.c_double
_nextafter.argtypes = [ctypes.c_double, ctypes.c_double]

def nextafter(x, y):
    "Returns the next floating-point number after x in the direction of y."
    return _nextafter(x, y)

assert nextafter(0, 1) - nextafter(0, 1) == 0
assert 0.0 + nextafter(0, 1) > 0.0

и если вы действительно хотите чистое решение Python:

# handles edge cases correctly on MY computer 
# not extensively QA'd...
import math
# 'double' means IEEE 754 double precision -- c 'double'
epsilon  = math.ldexp(1.0, -53) # smallest double that 0.5+epsilon != 0.5
maxDouble = float(2**1024 - 2**971)  # From the IEEE 754 standard
minDouble  = math.ldexp(1.0, -1022) # min positive normalized double
smallEpsilon  = math.ldexp(1.0, -1074) # smallest increment for doubles < minFloat
infinity = math.ldexp(1.0, 1023) * 2

def nextafter(x,y):    
    """returns the next IEEE double after x in the direction of y if possible"""
    if y==x:
       return y         #if x==y, no increment

    # handle NaN
    if x!=x or y!=y:
        return x + y       

    if x >= infinity:
        return infinity

    if x <= -infinity:
        return -infinity

    if -minDouble < x < minDouble:
        if y > x:
            return x + smallEpsilon
        else:
            return x - smallEpsilon  

    m, e = math.frexp(x)        
    if y > x:
        m += epsilon
    else:
        m -= epsilon

    return math.ldexp(m,e)

или Марка Дикинсона отлично решение

очевидно включает в себя решение является самым простым.

во-первых, это "реагировать на столкновение" является довольно плохой идеей.

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

ваш алгоритм "хэш-зондирования "должен будет пройти через несколько" крошечных приращений " для разрешения конфликтов.

и последовательные хэш-зонды, как известно, неэффективны.

читать это: http://en.wikipedia.org/wiki/Quadratic_probing

во-вторых, используйте math.frexp и sys.float_info.epsilon возиться с мантиссой и экспонентой отдельно.

>>> m, e = math.frexp(4.0)
>>> (m+sys.float_info.epsilon)*2**e
4.0000000000000018
import sys
>>> sys.float_info.epsilon
2.220446049250313e-16

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

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

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

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

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

import struct

def floatToieee754Bits(f):
    return struct.unpack('<Q', struct.pack('<d', f))[0]

def ieee754BitsToFloat(i):
    return struct.unpack('<d', struct.pack('<Q', i))[0]

def incrementFloat(f):
    i = floatToieee754Bits(f)
    if f >= 0:
        return ieee754BitsToFloat(i+1)
    else:
        raise Exception('f not >= 0: unsolved problem!')

вместо того, чтобы изменить ваши метки поплавок, использовать Кортеж для каждого ключа, как Марк Рэнсом предлагает где кортежа!--2--> состоит из x=your_unmodified_time_stamp и y=(extremely unlikely to be a same value twice).

так:

  1. x просто неизмененного timestamp и может быть одно и то же значение много раз;
  2. y вы можете использовать:
    1. случайное целое число из большого диапазона
    2. последовательное целое число (0,1,2 и т. д),
    3. UUID.

в то время как 2.1 (случайный int из большого диапазона) отлично работает для ethernet, я бы использовал 2.2 (сериализатор) или 2.3 (UUID). Легкий, быстрый, пуленепробиваемый. Для 2.2 и 2.3 вам даже не нужно обнаружение столкновений (вы можете все еще иметь его для 2.1, Как это делает ethernet.)

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

тогда просто извлеките x из кортежа для любых операций типа сортировки, а сам кортеж является ключом без конфликтов для хэша / словаря.

Edit

Я думаю, пример кода поможет:

#!/usr/bin/env python

import time
import sys
import random

#generator for ints from 0 to maxinteger on system:
serializer=(sn for sn in xrange(0,sys.maxint))

#a list with guranteed collisions:
times=[]
for c in range(0,35):
   t=time.clock()
   for i in range(0,random.choice(range(0,4))):
      times.append(t)

print len(set(times)), "unique items in a list of",len(times)      

#dictionary of tuples; no possibilities of collisions:
di={}   
for time in times:
    sn=serializer.next()
    di[(time,sn)]='Element {}'.format(sn)

#for tuples of multiple numbers, Python sorts
# as you expect: first by t[0] then t[1], until t[n]
for key in sorted(di.keys()):
    print "{:>15}:{}".format(key, di[key]) 

выход:

26 unique items in a list of 55
  (0.042289, 0):Element 0
  (0.042289, 1):Element 1
  (0.042289, 2):Element 2
  (0.042305, 3):Element 3
  (0.042305, 4):Element 4
  (0.042317, 5):Element 5
  # and so on until Element n...

для встречных ключ k добавить: k / 250


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

нет необходимости определять наименьшее значение, которое может быть добавлено. Все, что вам нужно сделать, это приблизить его. Формат FPU обеспечивает 52 бита мантиссы плюс скрытый бит для 53 бит точность. ни одна физическая константа не известна где-либо рядом с этим уровнем точности. Ни один датчик не может измерить ничего подобного. Так у вас нет проблемы.

в большинстве случаев, для ключ k, вы могли бы добавить k / 253, из-за этой 52-битной фракции плюс скрытый бит.

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

так я бы сказал, для сталкивающегося ключа k, просто добавьте k / 250 и назовем это днем.1


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

Я думаю, что вы имеете в виду" как можно меньше, чтобы избежать столкновения хэшей", так как, например, следующий по высоте поплавок уже может быть ключом! =)

while toInsert.key in myDict: # assumed to be positive
    toInsert.key *= 1.000000000001
myDict[toInsert.key] = toInsert

это говорит, что вы, вероятно, не хотите использовать временные метки в качестве ключей.

вместо разрешения коллизий путем изменения ключа, как насчет сбор наездом? Т. е.:

bag = {}
bag[1234.] = 'something'

становится

bag = collections.defaultdict(list)
bag[1234.].append('something')

будет ли это работать?

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

идея в том, чтобы получить шестнадцатеричную строку в число с плавающей точкой. Это дает вам строку с битами мантиссы и экспоненты для скручивания. Скручивание-это боль, так как вы должны делать все это вручную и продолжать конвертировать в/из строк. В любом случае, вы добавляете (вычитаете) 1 К (от) последней цифре для положительного (отрицательного) числа. Убедитесь, что вы переносите до экспоненты, если вы переполняетесь. Отрицательные числа немного сложнее, чтобы вы не теряли ни одного бита.

def increment(f):
    h = f.hex()
    # decide if we need to increment up or down
    if f > 0:
        sign = '+'
        inc = 1
    else:
        sign = '-'
        inc = -1
    # pull the string apart
    h = h.split('0x')[-1]
    h,e = h.split('p')
    h = ''.join(h.split('.'))
    h2 = shift(h, inc)
    # increase the exponent if we added a digit
    h2 = '%s0x%s.%sp%s' % (sign, h2[0], h2[1:], e)
    return float.fromhex(h2)

def shift(s, num):
    if not s:
        return ''
    right = s[-1]
    right = int(right, 16) + num
    if right > 15:
        num = right // 16
        right = right%16
    elif right < 0:
        right = 0
        num = -1
    else:
        num = 0
    # drop the leading 0x
    right = hex(right)[2:]
    return shift(s[:-1], num) + right

a = 1.4e4
print increment(a) - a
a = -1.4e4
print increment(a) - a

a = 1.4
print increment(a) - a

посмотрев на ответ автозаполнения, я придумал немного другой ответ:

import math, sys

def incrementFloatValue(value):
    if value == 0:
        return sys.float_info.min                                
    mant, exponent = math.frexp(value)                                                   
    epsilonAtValue = math.ldexp(1, exponent - sys.float_info.mant_dig)                
    return math.fsum([value, epsilonAtValue])

отказ от ответственности: я действительно не так хорош в математике, как я думаю ;) пожалуйста, убедитесь, что это правильно, прежде чем использовать его. Также я не уверен в производительности

некоторые замечания:

  • epsilonAtValue вычисляет, сколько битов используется для мантиссы (максимум минус то, что используется для экспоненты).
  • я не конечно, если math.fsum() необходимо, но эй, это, кажется, не больно.

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

Я думаю, что это правильное решение, оно, безусловно, правильно обрабатывает 0 и положительные значения:

import math
import sys

def incrementFloat(f):
    if f == 0.0:
        return sys.float_info.min
    m, e = math.frexp(f)
    return math.ldexp(m + sys.float_info.epsilon / 2, e)

Comments

    Ничего не найдено.