4

I am interested in approximating positive integers with numbers in the form $N=2^a3^b$ where $a,b\in\mathbb{Z}$. For example, let's say that we want to approximate $143=11\times13$. We have,

$$2^a3^b\approx11\times13\Rightarrow a\log(2)+b\log(3)=\log(11)+\log(13)$$

$$a\log(2)+b\log(3)-\log(11)-\log(13)=0$$

The $\log p_1,\dots,\log p_t$ is linearly independent over $\mathbb{Q}$ (see), so obviously we cannot determine $a,b$ to have the above equation. I'm wondering if it is possible to optimize $a$ and $b$ to minimize the absolute error between $2^a3^b$ and $143$. Furthermore, I would be glad to solve this optimization problem in general case. In general, the problem can be stated in the form related to linear combination of the logarithm functions:

$$a \log (2) + b \log (3) + n_1\log(p_1)+n_2\log(p_2)+\cdots + n_k \log (p_k) = 0 $$

where the integers $n_1,\ldots,n_k$ and the prime numbers $p_1,\ldots,p_k$ are given and we want to optimize the integers $a$ and $b$.


I used a python script for $2^a\times 3^b\approx 143$ and the result was not so good. I looped over $a,b\in\{-10^6,\ldots,10^6\}$ and after about $35$ minutes of running, I got only two results for $|2^a3^b-143|<0.1$:


\begin{array}{|c|c|c|} \hline a & b & \text{Error} \\ \hline 825 & -516 & 0.05170246118419186 \\ -229 & 149 & 0.04546204418605271 \\ \hline \end{array}

The python script:

from numba import njit, prange

@njit(parallel=True) def fn(): for a in prange(-1000000, 1000001): i_a = 2.0a for b in prange(-1000000, 1000001): i_b = i_a * 3.0b v = i_b - 143.0 if abs(v) <= 1e-01: print( "a=", a, ", b=", b, ", error=", abs(v), )

fn()

User
  • 8,033
  • Your script uses the line approximation technique, right? So you start with some $a,b$ which are "close" to the desired result, and if it's greater you decrement one or if it's less you increment the other? I think your script should be able to get through at least a couple orders of magnitude more with some optimization... – abiessu Oct 13 '24 at 12:53
  • @abiessu I haven't much experience in optimizing the calculations, so I naively tried all the cases but optimized the code itself and ran it on Google Colab for its GPU. I added the script in my post. – User Oct 13 '24 at 12:57
  • 2
    Don't iterate over both a and b. Iterate over b and calculate the closest a. – DanielV Oct 13 '24 at 12:58
  • You can use logarithm's to find the nearest $b$ for a given $a$. (Or vice versa as DanielV suggests) – Daniel Mathias Oct 13 '24 at 13:00
  • 1
    The double-precision floating point format is limited to exponents in the range (-1022, 1023) for powers of two. Values outside this range result in either 0 or inf being assigned to i_a in your code. – Daniel Mathias Oct 13 '24 at 13:59
  • @DanielMathias Oh, now I understand why I got only 3-digit outputs for $a$ and $b$. I will try alternative ways. – User Oct 13 '24 at 14:23

3 Answers3

3

Convert the equation to $a+br = t$, $a,b \in \mathbb{Z}$ and $r,t \in \mathbb{R}$ and $|r|<1$.

Make good enough rational approximation to $r,t \implies r \approx \frac{p_k}{q_k}, t \approx \frac{r_k}{s_k}$.

$a+b\frac{p_k}{q_k} = \frac{r_k}{s_k} \implies q_ks_k a +s_kp_k b = q_k r_k \implies s_k | q_k, \ell_k s_k = q_k \implies q_k a +p_k b = \ell_k r_k$

$\gcd(p_k,q_k) = 1 \implies$ there is a solution $a,b$ to above equation by Euclidean algorithm to find gcd.

So find rational approximations $r \approx \frac{p_k}{\ell_k s_k}$ and $t \approx \frac{r_k}{s_k}$ such that $\gcd(p_k,\ell_k s_k) = 1$ and find solution $a,b$ by Euclidean algorithm.

Rational approximations can be found by decimal expansion with +1 added to numerator if needed to make gcd = 1 condition work.

2

I used @DanielV's and @DanielMathias's suggestions in the comments:

$$2^a3^b=143\implies a+b\log_23=\log_2143$$ $$b\approx\dfrac{(\log_2143)-a}{\log_23}$$ And iterated over $a$ to minimize the absolute value of the difference between $b$ and $\dfrac{(\log_2143)-a}{\log_23}$.

Here is the optimized code:

from numba import njit
from numpy import log2
from mpmath import mp

mp.dps = 50

@njit(parallel=False) def fn(): results = [] for a in range(-1011, 1011+1): v = (log2(143) - a) / log2(3) s = abs(v - round(v)) if s < 10**(-5): results.append((a, v)) return results

results = fn()

for a, v in results: b = round(v) error = abs(143 - (mp.exp(mp.log(2) * a + mp.log(3) * b))) if error < mp.mpf('1e-8'): print("a: ", a, " b: ", b, " error: ", error)

It took $11$ minute and $51$ seconds to run this code using Google Colab's processor. Here is the output with the error term, $|2^a3^b-143|$, less than $10^{-8}$:

\begin{array}{|c|c|c|} \hline a & b & \text{Error} \\ \hline -97766741041 & 61683945837 & 0.0000000031507807699242165 \\ -87326880450 & 55097127167 & 0.0000000045983876908203010 \\ -77517158756 & 48907881876 & 0.0000000090896242338383130 \\ -76887019859 & 48510308497 & 0.0000000060459946117310390 \\ -67077298165 & 42321063206 & 0.0000000076420173130661400 \\ -66447159268 & 41923489827 & 0.0000000074936015326564330 \\ -56637437574 & 35734244536 & 0.0000000061944103922793130 \\ -56007298677 & 35336671157 & 0.0000000089412084535964800 \\ -46197576983 & 29147425866 & 0.0000000047468034714778310 \\ -35757716392 & 22560607196 & 0.0000000032991965506616947 \\ -25317855801 & 15973788526 & 0.0000000018515896298309042 \\ -14877995210 & 9386969856 & \color{blue}{0.0000000004039827089854595} \\ -4438134619 & 2800151186 & 0.0000000010436242118746395 \\ 6001725972 & -3786667484 & 0.0000000024912311327493929 \\ 16441586563 & -10373486154 & 0.0000000039388380536388005 \\ 26251308257 & -16562731445 & 0.0000000097491738709566810 \\ 26881447154 & -16960304824 & 0.0000000053864449745428625 \\ 36691168848 & -23149550115 & 0.0000000083015669501911850 \\ 37321307745 & -23547123494 & 0.0000000068340518954615790 \\ 47131029439 & -29736368785 & 0.0000000068539600294110340 \\ 47761168336 & -30133942164 & 0.0000000082816588163949490 \\ 57570890030 & -36323187455 & 0.0000000054063531086162290 \\ 58201028927 & -36720760834 & 0.0000000097292657373429740 \\ 68010750621 & -42910006125 & 0.0000000039587461878067696 \\ 78450611212 & -49496824795 & 0.0000000025111392669826560 \\ 88890471803 & -56083643465 & 0.0000000010635323461438878 \\ 99330332394 & -62670462135 & \color{blue}{0.0000000003840745747095345 }\\ \hline \end{array}

The colored numbers show the error lower than $10^{-9}$.

User
  • 8,033
2

There is a generalisation of Euclid's algorithm to find (near) solutions to an affine linear equation $f(x, y) = a \cdot x + b \cdot y + c$ for some real coefficients. It goes as follows:

  1. For any two vectors $v, w$ such that $f(w) \neq f(0)$ let $k(v, w) = \lfloor f(v)/(f(0) - f(w)) \rfloor$.
  2. Initialize three vectors: $p \leftarrow (0,0)$, $v_1 \leftarrow (1,0)$, $v_2 \leftarrow (0, 1)$.
  3. Let $k_0 = k(p, v_1)$ and $k_1 = k(p + v_2, v_1)$.
  4. Substitute $(p, v_1, v_2) \leftarrow (p + k_0 v_1, v_2 + (k_1 - k_0) v1, v1).$
  5. Now the next near solution is the best one among the four vectors $p$, $p+v_1$, $p+v_2$, $p + v_1 + v_2$.
  6. Go to step 3. and repeat the procedure.

Note that the near solution may be stable for a few iterations, but will steadily improve overall. Here are the first subsequent different near solutions that are found for $f(x, y) = \log(2) \cdot x + \log(3) \cdot y - \log(143)$:

iteration near solution $\lvert f(x, y) \rvert$ $2^x 3^y$
1 $(7, 0)$ $1.1081$e-$1$ $128$
2 to 3 $(4, 2)$ $6.9687$e-$3$ $144$
4 to 6 $(23, -10)$ $6.5582$e-$3$ $142.0618$
7 $(-61, 43)$ $4.4942$e-$3$ $142.3588$
8 to 9 $(-229, 149)$ $3.1797$e-$4$ $142.9545$
10 $(17120, -10797)$ $5.8064$e-$6$ $143.0008$
WimC
  • 33,414
  • 2
  • 52
  • 98