

Task.
Convert rational a/b to its approximate p-adic square root. To check the result,
square the root and construct rational m/n to compare with radicand a/b.

For rational reconstruction Lagrange's lattice basis reduction algorithm is used.

Recipe: find root x1 modulo p and build a sequence of solutions
f(xk) ≡ 0 (mod pk),

using the lifting equation
xk+1 = xk + dk * pk 
with dk = –(f(xk) / pk) /
f ′(x1) (mod p).

The multipliers dk are the successive p-adic digits to find.

If evaluation of f(x) = bx2 – a overflows,
the expansion is cut off and might be too short to retrieve the radicand.
Setting a higher precision won't help, using a programming language with built-in
large integer support will.



Related task.
p-Adic numbers, basic



Reference.
[1]
Solving x2 ≡ a (mod n)



constant EMX  = 48       // exponent maximum (if indexing starts at -EMX)
constant DMX  = 1e5     // approximation loop maximum
constant AMX  = 700000 // argument maximum
constant PMAX = 32749   // prime maximum
 
// global variables
integer p1 = 0
integer p  = 7    // default prime
integer k  = 11   // precision
 
type Ratio(sequence r)
    return length(r)=2 and integer(r[1]) and integer(r[2])
end type
 
class Padic
    integer v = 0   
    sequence d = repeat(0,EMX*2)
 
    function square_root(Ratio g, integer sw)
        -- p-adic square root of g = a/b
        integer {a,b} = g
        atom f, q, pk, x
        integer f1, r, s, t, i, res = 0
 
        if b = 0 then return 1 end if
        if b < 0 then
            b = -b
            a = -a
        end if
        if p < 2 or k < 1 then return 1 end if
 
        -- max. short prime
        p = min(p, PMAX)
        if sw then
            -- numerator, denominator, prime, precision
            printf(1,"%d/%d + O(%d^%d)\n",{a,b,p,k})
        end if
 
        -- initialize
        v = 0
        p1 = p - 1
        sequence ntd = repeat(0,2*EMX) -- (new this.d)
        if a = 0 then return 0 end if
 
        -- valuation
        while remainder(b,p)=0 do
            b /= p
            v -= 1
        end while
        while remainder(a,p)=0 do
            a /= p
            v += 1
        end while
 
        if remainder(v,2) then
            -- odd valuation
            printf(1,"(1)non-residue mod %d\n",p)
            return -1
        end if
 
        -- max. array length
        k = min(k + v, EMX - 1) - v
        v = floor(v/2)
 
        if abs(a) > AMX or b > AMX then return -1 end if
 
        if p = 2 then
            --1 / b = b (mod 8)
            --a / b = 1 (mod 8)
            t = a * b
            if mod(t,8)-1 then
                printf(1,"(2)non-residue mod 8\n")
                return -1
             end if
 
        else
            -- find root for small p
            for r = 1 to p1 do
                f = b * r * r - a
                if mod(f,p) = 0 then exit end if
            end for
 
            if r = p then
                printf(1,"(3)non-residue mod %d\n", p)
                return -1
            end if
 
            -- f'(r) = 2br
            t = b * r * 2
 
            s = 0
            t = mod(t,p)
            -- modular inverse for small p
            for f1 = 1 to p1 do
                s += t
                if s > p1 then s -= p end if
                if s = 1 then exit end if
            end for
 
            if f1 = p then
                printf(1,"impossible inverse mod\n")
                return -1
            end if
        end if
 
        if p = 2 then
            -- initialize
            x = 1
            ntd[v+EMX+1] = 1
            ntd[v+EMX+2] = 0
 
            pk = 4
            for i = v+2 to k-1+v do
                pk *= 2
                f = b * x * x - a
                q = floor(f/pk)
                -- overflow
                if f != q * pk then exit end if
                -- next digit
                ntd[i+EMX+1] = and_bits(q,1)
                -- lift x
                x += ntd[i+EMX+1] * floor(pk/2)
            end for
 
        else
            -- -1 / f'(x) mod p
            f1 = p - f1
            x = r
            ntd[v+EMX+1] = x
 
            pk = 1
            for i = v+1 to k-1 do
                pk *= p
                f = b * x * x - a
                q = floor(f/pk)
                -- overflow
                if f - q * pk then exit end if
                r = mod(q*f1,p)
                if r < 0 then r += p end if
                ntd[i+EMX+1] = r
                x += r * pk
            end for
        end if
        this.d = ntd
        k = i-v
 
        if sw then
            printf(1,"lift: %d mod %d^%d\n",{x,p,k})
        end if  
        return 0
    end function
 
    function square()
        integer c = 0
        Padic r = new()
        r.v = this.v * 2
        sequence td = this.d,
                 rd = r.d
        for i=0 to k do
            for j=0 to i do
                c += td[v+j+EMX+1] * td[v+i-j+EMX+1]
            end for
            // Euclidean step
            integer q = floor(c/p)
            rd[r.v+i+EMX+1] = c - q*p
            c = q
        end for
        r.d = rd
        return r
    end function
 
    function complement()
        integer c = 1
        Padic r = new({v})
        sequence rd = r.d
        for i=v to k+v do
            integer dx = i+EMX+1
            c += p1 - this.d[dx]
            if c>p1 then
                rd[dx] = c - p
                c = 1
            else
                rd[dx] = c
                c = 0
            end if
        end for
        r.d = rd
        return r
    end function
 
    function crat(integer sw)
        -- rational reconstruction
        integer i, j, t = min(v, 0)
        Ratio r
        atom f
        integer x, y
        atom p1,pk, q, s
 
        -- weighted digit sum
        s = 0
        pk = 1
        for i = t to k-1+v do
            p1 = pk
            pk *= p
            if floor(pk/p1) - p then
                -- overflow
                pk = p1
                exit
            end if
            s += d[i+EMX+1] * p1 --(mod pk)
        end for
 
        -- lattice basis reduction
        sequence m = {pk, s},
                 n = {0, 1}
        i = 1
        j = 2
        s = s * s + 1 -- norm(v)^2
 
        -- Lagrange's algorithm
        while true do
            f = (m[i] * m[j] + n[i] * n[j]) / s
 
            -- Euclidean step
            q = floor(f +.5)
            m[i] -= q * m[j]
            n[i] -= q * n[j]
 
            q = s
            s = m[i] * m[i] + n[i] * n[i]
            -- compare norms
            if s < q then
                -- interchange vectors
                {i,j} = {j,i}
            else
                exit
            end if
        end while
 
        x = m[j]
        y = n[j]
        if y < 0 then
            y = -y
            x = -x
        end if
 
        -- check determinant
        t = abs(m[i] * y - x * n[i]) == pk
 
        if not t then
            printf(1,"crat: fail\n")
            x = 0
            y = 1
        else
            -- negative powers
            for i = v to -1 do
                y *= p
            end for
 
            if sw then
--              printf(1,iff(y=1?"%d":"%d/%d"),{x*sgn,y})
                printf(1,iff(y=1?"%d\n":"%d/%d\n"),{x,y})
            end if
        end if
 
        r = {x,y}
        return r
    end function
 
    procedure prntf(bool sw)
        -- print expansion
        integer t = min(v, 0)
        for i=k-1+t to t by -1 do
            printf(1,"%d",d[i+EMX+1])
            printf(1,iff(i=0 and v<0?". ":" "))
        end for
        printf(1,"\n")
        // rational approximation
        if sw then crat(sw) end if
    end procedure
end class
 
constant tests = {
    {{-7,1},2,7},
--/*
    {{9,1},2,8},
    {{17,1},2,9},
    {{497,10496},2,18},
    {{10496,497},2,19}, 
 
    {{3141,5926},3,17},
    {{2718,281},3,15},
 
    {{-1,1},5,8},
    {{86,25},5,8},
    {{2150,1},5,10},
 
    {{2,1},7,8},
    {{-2645,28518},7,9},
    {{3029,4821},7,9},
    {{379,449},7,8},
 
    {{717,8},11,7},
    {{1414,213},41,5},
--*/
    {{-255,256},257,3}
}
 
Padic a = new(), c
Ratio q, r
 
for i=1 to length(tests) do
   {q,p,k} = tests[i]
 
   integer sw = a.square_root(q, 1)
   if sw=1 then exit end if
   if sw=0 then
       printf(1,"square_root +/-\n")
       printf(1,"... ")
       a.prntf(0)
       a = a.complement()
       printf(1,"... ")
       a.prntf(0)
 
       c = a.square()
       printf(1,"square_root^2\n")
       printf(1,"    ")
       c.prntf(0)
       r = c.crat(1)
 
       if q[1] * r[2] - r[1] * q[2] then
          printf(1,"fail: square_root^2\n")
       end if
   end if
   printf(1,"\n")
end for