Примерно обратимая функция от пары поплавков до одного поплавка? - PullRequest
1 голос
/ 22 октября 2019

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

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

К сожалению, яя больше не работаю над этим кодом, и я забыл название функции, поэтому больше не могу его искать в Википедии. Кто-нибудь знает именованные математические функции, которые соответствуют этому описанию?

Ответы [ 2 ]

2 голосов
/ 24 октября 2019

Я не знаю названия функции, но вы можете нормализовать 2 значения x и y в диапазоне [0, 1], используя некоторые методы, такие как

X = arctan(x)/π + 0.5
Y = arctan(y)/π + 0.5

В этот момент X =0.a 1 a 2 a 3 ... и Y = 0.b 1 b 2 b 3 ... Теперь просто перемежая цифры, мы можем получить один плавающий со значением 0.a 1 b 1 a 2 b 2 a 3 b 3 ...

На принимающем сайте просто нарежьте биты и верните x и y из X иY

x = tan((X - 0.5)π)
y = tan((Y - 0.5)π)

Это работает в десятичном формате, но также работает в двоичном, и, конечно, будет проще манипулировать двоичными цифрами напрямую. Но, вероятно, вам нужно нормализовать значения до [0, ½] или [½, 1], чтобы показатели были одинаковыми. Вы также можете избежать использования битовых манипуляций, используя тот факт, что значимая часть всегда имеет длину 24 бита, и мы можем просто хранить x и y в верхней и нижней частях значимого. Результирующее парное значение равно

r = ⌊X × 2 12 ⌋ / 2 12 + Y / 2 12

⌊x⌋ - символ пола. Теперь это чисто математическое решение!

Если вы знаете, что величины значений всегда близки друг к другу, вы можете улучшить процесс, выровняв точки осей значений для нормализации и получениястаршие 12 значащих битов значенийи для слияния вместе, нет необходимости использовать atan

В случае, если диапазон значений ограничен, вы можете нормализовать по этой формуле, чтобы избежать потери точности из-за atan

X = (x - min)/(max - min)
Y = (y - min)/(max - min)

Но в этом случае есть способ объединить значения только с чисто математическими функциями. Предположим, что значения находятся в диапазоне [0, max], значение равно r = x*max + y. Чтобы отменить операцию:

x = ⌊r/max⌋;
y = r mod max

Если min не равно нулю, просто сдвиньте диапазон соответствующим образом

Подробнее в Существует ли математическая функция, которая преобразует два числа в одно так, чтобыдва числа всегда можно извлечь снова?

1 голос
/ 02 ноября 2019

В комментарии OP пояснил, что требуемая функция должна отображать два операнда IEEE-754 binary64 в один такой операнд. Один из способов сделать это состоит в том, чтобы отобразить каждое число binary64 (двойной точности) в положительное целое число в [0, 2 26 -2], а затем использовать хорошо известную функцию сопряжения , чтобы отобразить два таких целых числа в одно положительное целое число в интервале [0,2 52 ), который точно представлен в binary64, который имеет 52 сохраненных значащих ("мантисса") бита.

Поскольку операнды binary64 не имеют ограничений по дальности на комментарий от OP, все бинады должны быть представимы с одинаковой относительной точностью, и нам также нужно обрабатывать нули, бесконечности и NaN. По этой причине я выбрал log2() для сжатия данных. Нули рассматриваются как наименьшее субнормальное binary64, 0x1.0p-1074, что приводит к тому, что как 0x1.0p-1074, так и ноль будут распадаться до нуля. Результат от log2 попадает в диапазон [-1074, 1024). Поскольку нам нужно сохранить знак операнда, мы смещаем значение логарифма на 1074, давая результат в [0, 2098), затем масштабируем его почти до [0, 2 25 ), округлите до ближайшего целого числа и, наконец, поставьте знак исходного операнда binary64. Мотивация для почти полного использования диапазона - оставить немного места в верхней части диапазона для специальных кодировок для бесконечности и NaN (поэтому используется одиночное каноническое кодирование NaN).

Поскольку известны функции сопряженияиз литературы оперируют только натуральными числами, нам нужно отображение целых чисел в натуральные числа. Это легко сделать путем сопоставления отрицательных целых чисел с нечетными натуральными числами, в то время как положительные целые числа отображаются с четными натуральными числами. Таким образом, наши операнды отображаются с (-2 25 , + 2 25 ) на [0, 2 26 -2]. Затем функция спаривания объединяет два целых числа в [0, 2 26 -2] в одно целое число в [0, 2 52 ).

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

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

Хотя логарифмы представлены с относительной точностью порядка 10 -8 , ожидаемая максимальная относительная погрешность в конечных результатах составляет порядка 10 -5 из-заизвестная погрешность увеличения свойства возведения в степень. Максимальная относительная ошибка, наблюдаемая для кругового обхода pack() / unpack() в расширенном тестировании, составляла 2,167e-5.

Ниже приведена моя реализация алгоритма ISO C99 вместе с частью моей тестовой среды. Это должно быть переносимо на другие языки программирования с минимальными усилиями.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <float.h>

#define SZUDZIK_ELEGANT_PAIRING  (1)
#define ROZSA_PETER_PAIRING      (2)
#define ROSENBERG_STRONG_PAIRING (3)
#define CHRISTOPH_MICHEL_PAIRING (4)

#define PAIRING_FUNCTION  (ROZSA_PETER_PAIRING)

/*
  https://groups.google.com/forum/#!original/comp.lang.c/qFv18ql_WlU/IK8KGZZFJx4J
  From: geo <gmars...@gmail.com>
  Newsgroups: sci.math,comp.lang.c,comp.lang.fortran
  Subject: 64-bit KISS RNGs
  Date: Sat, 28 Feb 2009 04:30:48 -0800 (PST)

  This 64-bit KISS RNG has three components, each nearly
  good enough to serve alone.    The components are:
  Multiply-With-Carry (MWC), period (2^121+2^63-1)
  Xorshift (XSH), period 2^64-1
  Congruential (CNG), period 2^64
*/
static uint64_t kiss64_x = 1234567890987654321ULL;
static uint64_t kiss64_c = 123456123456123456ULL;
static uint64_t kiss64_y = 362436362436362436ULL;
static uint64_t kiss64_z = 1066149217761810ULL;
static uint64_t kiss64_t;
#define MWC64  (kiss64_t = (kiss64_x << 58) + kiss64_c, \
                kiss64_c = (kiss64_x >> 6), kiss64_x += kiss64_t, \
                kiss64_c += (kiss64_x < kiss64_t), kiss64_x)
#define XSH64  (kiss64_y ^= (kiss64_y << 13), kiss64_y ^= (kiss64_y >> 17), \
                kiss64_y ^= (kiss64_y << 43))
#define CNG64  (kiss64_z = 6906969069ULL * kiss64_z + 1234567ULL)
#define KISS64 (MWC64 + XSH64 + CNG64)

double uint64_as_double (uint64_t a)
{
    double r;
    memcpy (&r, &a, sizeof r);
    return r;
}

#define LOG2_BIAS    (1074.0)
#define CLAMP_LOW    (exp2(-LOG2_BIAS))
#define SCALE        (15993.5193125)
#define NAN_ENCODING (33554430)
#define INF_ENCODING (33554420)

/* check whether argument is an odd integer */
int is_odd_int (double a)
{
    return (-2.0 * floor (0.5 * a) + a) == 1.0;
}

/* compress double-precision number into an integer in (-2**25, +2**25) */
double compress (double x)
{
    double t;

    t = fabs (x);
    t = (t < CLAMP_LOW) ? CLAMP_LOW : t;
    t = rint ((log2 (t) + LOG2_BIAS) * SCALE);
    if (isnan (x)) t = NAN_ENCODING;
    if (isinf (x)) t = INF_ENCODING;
    return copysign (t, x);
}

/* expand integer in (-2**25, +2**25) into double-precision number */
double decompress (double x)
{
    double s, t;

    s = fabs (x);
    t = s / SCALE;
    if (s == (NAN_ENCODING)) t = NAN;
    if (s == (INF_ENCODING)) t = INFINITY;
    return copysign ((t == 0) ? 0 : exp2 (t - LOG2_BIAS), x);
}

/* map whole numbers to natural numbers. Here: (-2^25, +2^25) to [0, 2^26-2] */
double map_Z_to_N (double x)
{
    return (x < 0) ? (-2 * x - 1) : (2 * x);
}

/* Map natural numbers to whole numbers. Here: [0, 2^26-2] to (-2^25, +2^25) */
double map_N_to_Z (double x)
{
    return is_odd_int (x) ? (-0.5 * (x + 1)) : (0.5 * x);
}

#if PAIRING_FUNCTION == SZUDZIK_ELEGANT_PAIRING
/* Matthew Szudzik, "An elegant pairing function." In Wolfram Research (ed.) 
   Special NKS 2006 Wolfram Science Conference, pp. 1-12. 

   Here: map two natural numbers in [0, 2^26-2] to natural number in [0, 2^52),
   and vice versa
*/
double pair (double x, double y)
{
    return (x != fmax (x, y)) ? (y * y + x) : (x * x + x + y);
}

void unpair (double z, double *x, double *y)
{
    double sqrt_z = trunc (sqrt (z));
    double sqrt_z_diff = z - sqrt_z * sqrt_z;
    *x = (sqrt_z_diff < sqrt_z) ? sqrt_z_diff : sqrt_z;
    *y = (sqrt_z_diff < sqrt_z) ? sqrt_z : (sqrt_z_diff - sqrt_z);
}

#elif PAIRING_FUNCTION == ROZSA_PETER_PAIRING
/*
  Rozsa Peter, "Rekursive Funktionen" (1951), p. 44. Via David R. Hagen's blog,
  https://drhagen.com/blog/superior-pairing-function/

  Here: map two natural numbers in [0, 2^26-2] to natural number in [0, 2^52),
  and vice versa
*/
double pair (double x, double y)
{
    double mx = fmax (x, y);
    double mn = fmin (x, y);
    double sel = (mx == x) ? 0 : 1;
    return mx * mx + mn * 2 + sel;
}

void unpair (double z, double *x, double *y)
{
    double sqrt_z = trunc (sqrt (z));
    double sqrt_z_diff = z - sqrt_z * sqrt_z;
    double half_diff = trunc (sqrt_z_diff * 0.5);
    *x = is_odd_int (sqrt_z_diff) ? half_diff : sqrt_z;
    *y = is_odd_int (sqrt_z_diff) ? sqrt_z : half_diff;
}

#elif PAIRING_FUNCTION == ROSENBERG_STRONG_PAIRING
/*
  A. L. Rosenberg and H. R. Strong, "Addressing arrays by shells", 
  IBM Technical Disclosure Bulletin, Vol. 14, No. 10, March 1972,
  pp. 3026-3028.

  Arnold L. Rosenberg, "Allocating storage for extendible arrays," 
  Journal of the ACM, Vol. 21, No. 4, October 1974, pp. 652-670.
  Corrigendum, Journal of the ACM, Vol. 22, No. 2, April 1975, p. 308.

  Matthew P. Szudzik, "The Rosenberg-Strong Pairing Function", 2019
  https://arxiv.org/abs/1706.04129

  Here: map two natural numbers in [0, 2^26-2] to natural number in [0, 2^52),
  and vice versa
 */
double pair (double x, double y)
{
    double mx = fmax (x, y);
    return mx * mx + mx + x - y;
}

void unpair (double z, double *x, double *y)
{
    double sqrt_z = trunc (sqrt (z));
    double sqrt_z_diff = z - sqrt_z * sqrt_z;
    *x = (sqrt_z_diff < sqrt_z) ? sqrt_z_diff : sqrt_z;
    *y = (sqrt_z_diff < sqrt_z) ? sqrt_z : (2 * sqrt_z - sqrt_z_diff);
}
#elif PAIRING_FUNCTION == CHRISTOPH_MICHEL_PAIRING
/*
  Christoph Michel, "Enumerating a Grid in Spiral Order", September 7, 2016,
  https://cmichel.io/enumerating-grid-in-spiral-order. Via German Wikipedia,
  https://de.wikipedia.org/wiki/Cantorsche_Paarungsfunktion

  Here: map two natural numbers in [0, 2^26-2] to natural number in [0, 2^52),
  and vice versa
*/
double pair (double x, double y)
{
    double mx = fmax (x, y);
    return mx * mx + mx + (is_odd_int (mx) ? (x - y) : (y - x));
}

void unpair (double z, double *x, double *y)
{
    double sqrt_z = trunc (sqrt (z));
    double sqrt_z_diff = z - sqrt_z * (sqrt_z + 1);
    double min_clamp = fmin (sqrt_z_diff, 0);
    double max_clamp = fmax (sqrt_z_diff, 0);
    *x = is_odd_int (sqrt_z) ? (sqrt_z + min_clamp) : (sqrt_z - max_clamp);
    *y = is_odd_int (sqrt_z) ? (sqrt_z - max_clamp) : (sqrt_z + min_clamp);
}
#else
#error unknown PAIRING_FUNCTION
#endif

/* Lossy pairing function for double precision numbers. The maximum round-trip
   relative error is about 2.167e-5
*/
double pack (double a, double b)
{
    double c, p, q, s, t;

    p = compress (a);
    q = compress (b);
    s = map_Z_to_N (p);
    t = map_Z_to_N (q);
    c = pair (s, t);
    return c;
}

/* Unpairing function for double precision numbers. The maximum round-trip
   relative error is about 2.167e-5 */
void unpack (double c, double *a, double *b)
{
    double s, t, u, v;

    unpair (c, &s, &t);
    u = map_N_to_Z (s);
    v = map_N_to_Z (t);
    *a = decompress (u);
    *b = decompress (v);
}

int main (void)
{
    double a, b, c, ua, ub, relerr_a, relerr_b;
    double max_relerr_a = 0, max_relerr_b = 0; 

#if PAIRING_FUNCTION == SZUDZIK_ELEGANT_PAIRING
    printf ("Testing with Szudzik's elegant pairing function\n");
#elif PAIRING_FUNCTION == ROZSA_PETER_PAIRING
    printf ("Testing with Rozsa Peter's pairing function\n");
#elif PAIRING_FUNCTION == ROSENBERG_STRONG_PAIRING
    printf ("Testing with Rosenberg-Strong pairing function\n");
#elif PAIRING_FUNCTION == CHRISTOPH_MICHEL_PAIRING
    printf ("Testing with C. Michel's spiral pairing function\n");
#else
#error unkown PAIRING_FUNCTION
#endif

    do {
        a = uint64_as_double (KISS64);
        b = uint64_as_double (KISS64);

        c = pack (a, b);
        unpack (c, &ua, &ub);

        if (!isnan(ua) && !isinf(ua) && (ua != 0)) {
            relerr_a = fabs ((ua - a) / a);
            if (relerr_a > max_relerr_a) {
                printf ("relerr_a= %15.8e  a=% 23.16e  ua=% 23.16e\n", 
                        relerr_a, a, ua);
                max_relerr_a = relerr_a;
            }
        }
        if (!isnan(ub) && !isinf(ub) && (ub != 0)) {
            relerr_b = fabs ((ub - b) / b);
            if (relerr_b > max_relerr_b) {
                printf ("relerr_b= %15.8e  b=% 23.16e  ub=% 23.16e\n", 
                        relerr_b, b, ub);
                max_relerr_b = relerr_b;
            }
        }
    } while (1);
    return EXIT_SUCCESS;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...