Find the first perfect square in a given base N that has at least N digits and
exactly N significant unique digits when expressed in base N.

E.G. In base 10, the first perfect square with at least 10 unique digits is 1026753849 (32043²).

You may use analytical methods to reduce the search space, but the code must do a search. Do not use magic numbers or just feed the code the answer to verify it is correct.

Task
 Find and display here, on this page, the first perfect square in base N, with N significant unique digits when expressed in base N, for each of base 2 through 12. Display each number in the base N for which it was calculated.
 (optional) Do the same for bases 13 through 16.
 (stretch goal) Continue on for bases 17 - ?? (Big Integer math)


See also

 OEIS A260182: smallest square that is pandigital in base n.
Related task
 Casting out nines


-- demo\rosetta\PandigitalSquares.exw
without javascript_semantics    -- (mpz_set_str() currently only supports bases 2, 8, 10, and 16 under pwa/p2js)
include mpfr.e
atom t0 = time()
constant chars = "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz|",
         use_hll_code = true
 
function str_conv(sequence s, integer mode=+1)
-- mode of +1: eg {1,2,3} -> "123", mode of -1 the reverse.
-- note this doesn't really care what base s/res are in.
    {sequence res,integer dcheck} = iff(mode=+1?{"",9}:{{},'9'})
    for i=1 to length(s) do
        integer d = s[i]
        d += mode*iff(d>dcheck?'a'-10:'0')
        res &= d
    end for
    return res
end function
 
procedure do_one(integer base)
-- tabulates one base
    integer bm1 = base-1,
            dr = iff(and_bits(base,1) ? floor(base/2) : 0),
            id = 0, 
            rc = 0,
            sdri
    atom st = time()
    sequence sdr = repeat(0,bm1)
    for i=0 to bm1-1 do
        sdri = mod(i*i,bm1)
        rc += (sdri==dr)
        sdr[i+1] = iff(sdri=0 ? bm1 : sdri)
    end for
    if dr>0 then
        id = base
        for i=1 to dr do
            sdri = sdr[i+1]
            if sdri>=dr
            and id>sdri then
                id = sdri
            end if
        end for
        id -= dr
    end if
    string sq = chars[1..base]
    if id>0 then sq = sq[1..id]&chars[id+1]&sq[id+1..$] end if
    sq[1..2] = "10"
    mpz sqz = mpz_init(),
        rtz = mpz_init(),
        dnz = mpz_init(),
        tmp = mpz_init()
    mpz_set_str(sqz,sq,base)
    mpz_sqrt(rtz,sqz)
    mpz_add_ui(rtz,rtz,1)       -- rtz = sqrt(sqz)+1
    mpz_mul_si(dnz,rtz,2)
    mpz_add_si(dnz,dnz,1)       -- dnz = rtz*2+1
    mpz_mul(sqz,rtz,rtz)        -- sqz = rtz * rtz
    integer d = 1,
            inc = 1
    if base>3 and rc>0 then
        while mpz_fdiv_ui(sqz,bm1)!=dr do 
            -- align sqz to dr
            mpz_add_ui(rtz,rtz,1)   -- rtz += 1
            mpz_add(sqz,sqz,dnz)    -- sqz += dnz
            mpz_add_ui(dnz,dnz,2)   -- dnz += 2
        end while
        inc = floor(bm1/rc)
        if inc>1 then
            mpz_mul_si(tmp,rtz,inc-2)
            mpz_sub_ui(tmp,tmp,1)
            mpz_add(dnz,dnz,tmp)    -- dnz += rtz*(inc-2)-1
        end if
        d = inc * inc
        mpz_add(dnz,dnz,dnz)
        mpz_add_ui(dnz,dnz,d)       -- dnz += dnz + d
    end if
    d *= 2
    atom mask, fullmask = power(2,base)-1 -- ie 0b111..
    integer icount = 0
    mpz_set_si(tmp,d)
    sequence sqi = str_conv(mpz_get_str(sqz,base), mode:=-1),
             dni = str_conv(mpz_get_str(dnz,base), mode:=-1),
             dti = str_conv(mpz_get_str(tmp,base), mode:=-1)
    while true do
        if use_hll_code then
            mask = 0
            for i=1 to length(sqi) do
                mask = or_bits(mask,power(2,sqi[i]))
            end for
        else
            ?9/0 -- see below, inline part 1
        end if
        if mask=fullmask then exit end if
        integer carry = 0, sidx, si
        if use_hll_code then
            for sidx=-1 to -length(dni) by -1 do
                si = sqi[sidx]+dni[sidx]+carry
                carry = si>=base
                sqi[sidx] = si-carry*base
            end for
            sidx += length(sqi)+1
            while carry and sidx do
                si = sqi[sidx]+carry
                carry = si>=base
                sqi[sidx] = si-carry*base
                sidx -= 1
            end while
        else
            ?9/0 --see below, inline part 2
        end if
        if carry then
            sqi = carry&sqi
        end if
        carry = 0
        for sidx=-1 to -length(dti) by -1 do
            si = dni[sidx]+dti[sidx]+carry
            carry = floor(si/base)
            dni[sidx] = remainder(si,base)
        end for
        sidx += length(dni)+1
        while carry and sidx do
            si = dni[sidx]+carry
            carry = si>=base
            dni[sidx] = si-carry*base
            sidx -= 1
        end while
        if carry then
            dni = carry&dni
        end if
        icount += 1
    end while
    mpz_set_si(tmp,icount)
    mpz_mul_si(tmp,tmp,inc)
    mpz_add(rtz,rtz,tmp)            -- rtz += icount * inc
    sq = str_conv(sqi, mode:=+1)
    string rt = mpz_get_str(rtz,base),
           idstr = iff(id?sprintf("%d",id):" "),
           ethis = elapsed_short(time()-st),
           etotal = elapsed_short(time()-t0)
    printf(1,"%3d %3d %s %18s -> %-28s %10d %8s     %8s\n",
             {base, inc, idstr, rt, sq, icount, ethis, etotal})
    {sqz,rtz,dnz,tmp} = mpz_free({sqz,rtz,dnz,tmp})
end procedure
 
puts(1,"base inc id             root -> square" &
        "                       test count    time        total\n")
for base=2 to 19 do
--for base=2 to 25 do
--for base=2 to 28 do
    do_one(base)
end for
printf(1,"completed in %s\n", {elapsed(time()-t0)})
