iw4-sp/deps/libtommath/mp_log.c

169 lines
5.0 KiB
C

#include "tommath_private.h"
#ifdef MP_LOG_C
/* LibTomMath, multiple-precision integer library -- Tom St Denis */
/* SPDX-License-Identifier: Unlicense */
#define MP_LOG2_EXPT(a,b) ((mp_count_bits((a)) - 1) / mp_cnt_lsb((b)))
static mp_err s_approx_log_d(const mp_int *a, const mp_int *b, int *lb)
{
mp_word La, Lb;
mp_err err;
/* Approximation of the individual logarithms with low precision */
if ((err = s_mp_fp_log_d(a, &La)) != MP_OKAY) goto LTM_ERR;
if ((err = s_mp_fp_log_d(b, &Lb)) != MP_OKAY) goto LTM_ERR;
/* Approximation of log_b(a) with low precision. */
*lb = (int)(((La - (Lb + 1)/2) / Lb) + 1);
/* TODO: just floor it instead? Multiplication is cheaper than division. */
/* *lb = (int)(la_word / lb_word); */
err = MP_OKAY;
LTM_ERR:
return err;
}
static mp_err s_approx_log(const mp_int *a, const mp_int *b, int *lb)
{
mp_int La, Lb, t;
mp_err err;
if ((err = mp_init_multi(&La, &Lb, &t, NULL)) != MP_OKAY) {
return err;
}
if ((err = s_mp_fp_log(a, &La)) != MP_OKAY) goto LTM_ERR;
if ((err = s_mp_fp_log(b, &Lb)) != MP_OKAY) goto LTM_ERR;
if ((err = mp_add_d(&Lb, 1u, &t)) != MP_OKAY) goto LTM_ERR;
if ((err = mp_div_2(&t, &t)) != MP_OKAY) goto LTM_ERR;
if ((err = mp_sub(&La, &t, &t)) != MP_OKAY) goto LTM_ERR;
if ((err = mp_div(&t, &Lb, &t, NULL)) != MP_OKAY) goto LTM_ERR;
if ((err = mp_add_d(&t, 1u, &t)) != MP_OKAY) goto LTM_ERR;
*lb = mp_get_i32(&t);
err = MP_OKAY;
LTM_ERR:
mp_clear_multi(&t, &Lb, &La, NULL);
return err;
}
mp_err mp_log(const mp_int *a, const mp_int *b, int *lb)
{
mp_int bn;
int n, fla, flb;
mp_err err;
mp_ord cmp;
if (mp_isneg(a) || mp_iszero(a) || (mp_cmp_d(b, 2u) == MP_LT)) {
return MP_VAL;
}
if (MP_IS_POWER_OF_TWO(b)) {
*lb = MP_LOG2_EXPT(a, b);
return MP_OKAY;
}
/* floor(log_2(x)) for cut-off */
fla = mp_count_bits(a) - 1;
flb = mp_count_bits(b) - 1;
cmp = mp_cmp(a, b);
/* "a < b -> 0" and "(b == a) -> 1" */
if ((cmp == MP_LT) || (cmp == MP_EQ)) {
*lb = (cmp == MP_EQ);
return MP_OKAY;
}
/* "a < b^2 -> 1" (bit-count is sufficient, doesn't need to be exact) */
if (((2 * flb)-1) > fla) {
*lb = 1;
return MP_OKAY;
}
if (MP_HAS(S_MP_WORD_TOO_SMALL)) {
err = s_approx_log(a, b, &n);
} else {
err = s_approx_log_d(a, b, &n);
}
if (err != MP_OKAY) {
return err;
}
if ((err = mp_init(&bn)) != MP_OKAY) {
return err;
}
/* Check result. Result is wrong by 2(two) at most. */
if ((err = mp_expt_n(b, n, &bn)) != MP_OKAY) {
/* If the approximation overshot we can give it another try */
if (err == MP_OVF) {
n--;
/* But only one */
if ((err = mp_expt_n(b, n, &bn)) != MP_OKAY) goto LTM_ERR;
} else {
goto LTM_ERR;
}
}
cmp = mp_cmp(&bn, a);
/* The rare case of a perfect power makes a perfect shortcut, too. */
if (cmp == MP_EQ) {
*lb = n;
goto LTM_OUT;
}
/* We have to make at least one multiplication because it could still be a perfect power. */
if (cmp == MP_LT) {
do {
/* Full big-integer operations are to be avoided if possible */
if (b->used == 1) {
if ((err = mp_mul_d(&bn, b->dp[0], &bn)) != MP_OKAY) {
if (err == MP_OVF) {
goto LTM_OUT;
}
goto LTM_ERR;
}
} else {
if ((err = mp_mul(&bn, b, &bn)) != MP_OKAY) {
if (err == MP_OVF) {
goto LTM_OUT;
}
goto LTM_ERR;
}
}
n++;
} while ((cmp = mp_cmp(&bn, a)) == MP_LT);
/* Overshot, take it back. */
if (cmp == MP_GT) {
n--;
}
goto LTM_OUT;
}
/* But it can overestimate, too, for example if "a" is closely below some "b^k" */
if (cmp == MP_GT) {
do {
if (b->used == 1) {
/* These are cheaper exact divisions, but that function is not available in LTM */
if ((err = mp_div_d(&bn, b->dp[0], &bn, NULL)) != MP_OKAY) goto LTM_ERR;
} else {
if ((err = mp_div(&bn, b, &bn, NULL)) != MP_OKAY) goto LTM_ERR;
}
n--;
} while ((cmp = mp_cmp(&bn, a)) == MP_GT);
}
LTM_OUT:
*lb = n;
err = MP_OKAY;
LTM_ERR:
mp_clear(&bn);
return err;
}
#endif