-- florilegium-pgfmath.lua -- the part of pgfmath's arithmetic the species need, and pgf's
-- random generator, replayed exactly.
-- Copyright (c) 2026 Kevin Raymond. LaTeX Project Public License 1.3c (below); the rest of
-- florilegium is under the MIT License (see LICENSE).
--
-- The TikZ library (tikzlibraryflorilegium.code.tex) computes every drawing with pgfmath,
-- in TeX's fixed point: a number is a whole count of scaled points (1/65536 pt), every
-- operation rounds or truncates the way TeX and pgf do. florilegium-species.lua samples
-- the same species in Lua, and for the same seed it must make the same choices: a
-- real-valued trait is pgfmath's own number, to the scaled point, since a threshold that
-- one side reads as 0.29999 and the other as 0.30001 sends the plant elsewhere. And
-- florilegium-native.lua, which draws in doubles, takes from here the few numbers that TeX
-- holds on a threshold (a leaf's size on the page, a pic's counts). So this file does what
-- pgfmath does for those, step for step, on integers: TeX's decimal reading
-- (round_decimals) and printing (print_scaled), a factor times a dimension (xn_over_d),
-- \divide, and pgf's own algorithms for divide, reciprocal, ln, exp and pow, with pgf's
-- constants.
--
-- Not part of pgf. pgfmath is Mark Wibrow's (pgfmathfunctions.*.code.tex, "Copyright 2019
-- by Mark Wibrow", building on code by Till Tantau); its ln follows an algorithm due to
-- Rouben Rostamian with coefficients contributed by Alain Matthes, as pgfmath credits them.
-- This file is a reimplementation of part of pgfmath 3.1.10's arithmetic, and of its random
-- generator (pgfmathfunctions.random.code.tex), in Lua, made to give the same results, and
-- differs from them in this: the TeX macros are rewritten as Lua functions on integers, and
-- an overflow raises a Lua error where TeX reports one and goes on (tools/pgfmathcheck.lua
-- tests that the results agree). It replaces and changes no file of pgf. pgf's authors do
-- not support it: report problems with it to florilegium. pgf itself, unmodified, is at
-- https://ctan.org/pkg/pgf and https://github.com/pgf-tikz/pgf; it is distributed under the
-- LaTeX Project Public License 1.3c or the GNU GPL 2. This file is a Derived Work of pgf
-- under the LPPL, and this paragraph gives the notices the LPPL's clause 6 asks for.
--
-- This work may be distributed and/or modified under the conditions of the LaTeX Project
-- Public License, version 1.3c (https://www.latex-project.org/lppl/lppl-1-3c/). This work
-- has the LPPL maintenance status `maintained'. The Current Maintainer of this work is
-- Kevin Raymond. This work consists of the file florilegium-pgfmath.lua.
--
-- Values in and out are integers in sp: M.sp(text) reads a number, M.str(v) prints one,
-- M.mulf(x, y) is y as a factor times x, M.add, M.div (pgfmath's a/b), M.int (the integer
-- part, in sp) and M.pow.

local M = {}
M.version = "2.0.1"                 -- the release; florilegium.lua checks that every file has the same

local UNITY = 65536
local MAXDIMEN = 1073741823       -- 16383.99998pt
local concat, tonumber, tostring = table.concat, tonumber, tostring

local function check(v)
  if v > MAXDIMEN or v < -MAXDIMEN then error("florilegium-pgfmath: dimension too large (" .. v .. "sp)", 2) end
  return v
end

-- ---------------------------------------------------------------- TeX's numbers
-- round_decimals (tex.web 102): up to 17 digits after the point, to the nearest 2^-16
local function round_decimals(d)
  local a = 0
  for k = math.min(#d, 17), 1, -1 do a = (a + (d:byte(k) - 48) * 131072) // 10 end
  return (a + 1) // 2
end

-- a number as TeX reads "<text>pt": signs, integer part, fraction; in sp
local SPCACHE = {}
function M.sp(text)
  local v = SPCACHE[text]
  if v then return v end
  local signs, int, frac = text:match("^%s*([-+%s]*)(%d*)[.,]?(%d*)%s*$")
  if not signs or int .. frac == "" then error("florilegium-pgfmath: not a number: '" .. text .. "'", 2) end
  local _, nminus = signs:gsub("-", "")
  local i = tonumber(int ~= "" and int or "0")
  if i >= 16384 then error("florilegium-pgfmath: dimension too large: " .. text, 2) end
  v = i * UNITY + (frac ~= "" and round_decimals(frac) or 0)
  if nminus % 2 == 1 then v = -v end
  SPCACHE[text] = check(v)
  return v
end

-- print_scaled (tex.web 103): the shortest decimal that reads back to the same sp,
-- as \the gives it without "pt". Remembered: a page asks for the same values again and
-- again (nine calls in ten), so the table is emptied when it grows large
local STRCACHE, nstr = {}, 0
local function print_scaled(s)
  local out = {}
  if s < 0 then out[1] = "-" s = -s end
  out[#out + 1] = tostring(s // UNITY)
  out[#out + 1] = "."
  s = 10 * (s % UNITY) + 5
  local delta = 10
  repeat
    if delta > UNITY then s = s + 32768 - 50000 end
    out[#out + 1] = string.char(48 + s // UNITY)
    s = 10 * (s % UNITY)
    delta = delta * 10
  until s <= delta
  return concat(out)
end
function M.str(s)
  local v = STRCACHE[s]
  if v then return v end
  if nstr >= 200000 then STRCACHE, nstr = {}, 0 end
  v = print_scaled(s)
  STRCACHE[s], nstr = v, nstr + 1
  return v
end
local str, sp = M.str, M.sp

-- the integer part of \the<dimen> as a count reads it (toward zero), and the rest
local function ipart(x) if x >= 0 then return x // UNITY else return -((-x) // UNITY) end end
local function fpart(x) if x < 0 then x = -x end return x % UNITY end

-- <factor><dimen> (tex.web 455): integer part times x, plus xn_over_d(x, f, 2^16);
-- the factor's sign last
local function mulf(x, y)
  local neg = y < 0
  if neg then y = -y end
  local f = y % UNITY
  local r = (y // UNITY) * x
  if x >= 0 then r = r + (x * f) // UNITY else r = r - ((-x) * f) // UNITY end
  if neg then r = -r end
  if r > MAXDIMEN or r < -MAXDIMEN then return check(r) end   -- (check's error, from here)
  return r
end
M.mulf = mulf

-- \divide<dimen or count> by n: toward zero
local function divn(x, n)
  if n == 0 then error("florilegium-pgfmath: arithmetic overflow (divide by 0)", 2) end
  if n < 0 then x, n = -x, -n end
  if x >= 0 then return x // n else return -((-x) // n) end
end

-- ---------------------------------------------------------------- add, divide
function M.add(a, b) local r = a + b if r > MAXDIMEN or r < -MAXDIMEN then return check(r) end return r end

-- \pgfmathreciprocal@: from the digits of the number as written
function M.recip(x)
  if x == 0 then error("florilegium-pgfmath: reciprocal of 0", 2) end
  local I, D = str(x):match("^(-?%d+)%.(%d*)$")
  D = (D .. "0000000"):sub(1, 5)
  if tonumber(D) == 0 then return divn(UNITY, tonumber(I)) end
  local ca, cb
  if tonumber(I) > 100 then
    ca = tonumber(I .. D:sub(1, 3))
    cb = divn(1000000000, ca)
    ca = divn(cb, 10000)
    local r = check(ca * UNITY)
    cb = cb + ca * -10000
    return check(r + divn(check(cb * UNITY), 1000000))
  end
  ca = tonumber(I .. D)
  cb = divn(1000000000, ca)
  ca = divn(cb, 10000)
  local r = check(ca * UNITY)
  cb = cb + ca * -10000
  local y = check(cb * UNITY)
  for _ = 1, 4 do y = mulf(y, 6554) end           -- \pgfmath@y.1\pgfmath@y (.1 reads as 6554sp)
  return check(r + y)
end
local recip = M.recip

-- \pgfmathdivide@dimenbyten: the decimal point moved one digit left, in the text
local function byten(y)
  local I, D = str(y):match("^(%d+)%.(%d+)$")
  local i = tonumber(I)
  local a = i // 10
  return sp(a .. "." .. (i - 10 * a) .. D)
end

-- \pgfmathdivide@advance@last@digit: one more in the last of up to five decimals
local BUMP = { [0] = UNITY, sp("0.1"), sp("0.01"), sp("0.001"), sp("0.0001"), sp("0.00001") }
local function bump(res)
  local dec = res:match("%.(%d*)$")
  local y = sp(res) + BUMP[math.min(#dec, 5)]
  local s = str(y)
  return (s:gsub("%.0$", "."))                  -- \pgfmath@tonumber@notrailingzero
end

local SMALLNUM = sp("0.00002")
function M.div(a, b)
  if b == 0 then error("florilegium-pgfmath: division by 0", 2) end
  if fpart(b) == 0 then return divn(a, ipart(b)) end
  local signs, x, y = 0, a, b
  if x < 0 then signs = signs + 1 x = -x end
  if y < 0 then signs = signs + 1 y = -y end
  if y < UNITY then
    local r = recip(y)
    if signs == 1 then r = -r end
    return mulf(x, r)
  end
  local res, period = "0", true
  while x > SMALLNUM and y > SMALLNUM do        -- \pgfmath@small@number
    if y > x then
      if period then res = res .. "." period = false end
      y = byten(y)
      if y > x then res = res .. "0" end
    else
      local q = x // y
      x = x - q * y
      if not period and q > 9 then
        res = bump(res)
        q = q - 10
        if q ~= 0 then res = res .. q end
      else
        res = res .. q
      end
    end
  end
  local v = sp(res)
  if signs == 1 then v = -v end
  return v
end

function M.int(a) return ipart(a) * UNITY end   -- the text before the point

-- ---------------------------------------------------------------- ln, exp, pow
-- ln: Rouben Rostamian's algorithm, Alain Matthes's coefficients (as pgfmath credits them)
local LN = { sp("0.04300521312"), sp("-.4025153233"), sp("1.589480044"), sp("-3.489886985"),
  sp("5.047861502"), sp("-2.787927935"), sp("0.6931471806") }
local P2 = {}
for k = 1, 13 do P2[k] = 1 << k end
local SMALL = { sp("0.0078125"), sp("0.125"), sp("0.5"), sp("0.03125"), sp("0.0625"), sp("0.015625"),
  sp("0.00048828125"), sp("0.001953125"), sp("0.00390625"), sp("0.0009765625"),
  sp("0.0001220703125"), sp("0.0002441406256") }
function M.ln(x)
  if x <= 0 then error("florilegium-pgfmath: ln of " .. str(x), 2) end
  local c = 0
  local pt = UNITY
  if x > 2 * pt then
    if x < 128 * pt then
      if x < 8 * pt then
        if x < 4 * pt then c = 1 else c = 2 end
      elseif x < 32 * pt then
        if x < 16 * pt then c = 3 else c = 4 end
      else
        if x < 64 * pt then c = 5 else c = 6 end
      end
    elseif x < 2048 * pt then
      if x < 512 * pt then
        if x < 256 * pt then c = 7 else c = 8 end
      else
        if x < 1024 * pt then c = 9 else c = 10 end
      end
    else
      if x < 8192 * pt then
        if x < 4096 * pt then c = 11 else c = 12 end
      else c = 13 end
    end
    x = divn(x, P2[c])
  elseif x < pt then
    if x > SMALL[1] then
      if x > SMALL[2] then
        if x > SMALL[3] then c = -1 else c = -2 end
      elseif x > SMALL[4] then
        if x > SMALL[5] then c = -3 else c = -4 end
      else
        if x > SMALL[6] then c = -5 else c = -6 end
      end
    elseif x > SMALL[7] then
      if x > SMALL[8] then
        if x > SMALL[9] then c = -7 else c = -8 end
      else
        if x > SMALL[10] then c = -9 else c = -10 end
      end
    else
      if x > SMALL[11] then
        if x > SMALL[12] then c = -11 else c = -12 end
      else c = -13 end
    end
    x = check(x * P2[-c])
  end
  local t = x
  x = mulf(x, LN[1])
  x = x + LN[2]
  x = mulf(x, t)
  x = x + LN[3]
  x = mulf(x, t)
  x = x + LN[4]
  x = mulf(x, t)
  x = x + LN[5]
  x = mulf(x, t)
  x = x + LN[6]
  x = x + c * UNITY
  return mulf(x, LN[7])
end

function M.exp(v)
  if v < -9 * UNITY then return 1 end
  local xc = v < 0 and -v or v
  local x, xa = UNITY, UNITY
  local xb, k = x, 1
  while true do
    xa = divn(xa, k)
    xa = mulf(xa, xc)
    x = check(x + xa)
    if x == xb then break end
    xb = x
    k = k + 1
  end
  if v < 0 then x = recip(x) end
  return x
end

function M.pow(a, b)
  if a == 0 then return b == 0 and UNITY or 0 end
  local c, f = ipart(b), fpart(b)
  local xa = a
  if c < 0 then c = -c xa = recip(a) end
  if f == 0 then
    local x = UNITY
    while c > 0 do
      if c % 2 == 1 then x = mulf(xa, x) end
      if c > 1 then xa = mulf(xa, xa) end
      c = c // 2
    end
    return x
  end
  return M.exp(mulf(M.ln(a), b))
end

-- ---------------------------------------------------------------- pgf's random generator
-- pgf's own generator, replayed step for step, so that a species drawn by TikZ
-- (tikzlibraryflorilegium.code.tex) and by Lua from the same seed makes the same
-- choices. pgfmathfunctions.random.code.tex (pgf, Mark Wibrow; LPPL 1.3c / GPL 2): Lehmer's
-- generator, a = 69621, m = 2^31-1, by Schrage's method (q = 30845, r = 23902). Not part of
-- pgf: a reimplementation in Lua, not supported by pgf's authors (pgf itself, unmodified,
-- is at https://ctan.org/pkg/pgf); under this file's license, as above.
-- rnd is (z mod 100001) written as five decimals and read as a TeX dimension,
-- rand the same over (z mod 200001) - 100000, random(a,b) is a + z mod (b-a+1).
-- Values are returned as TeX holds them: whole scaled points (1/65536 pt).
-- TeX's round_decimals of the five digits of n (0 <= n < 100000): it takes the digits from
-- the last, a = (a + digit * 2^17) div 10, then (a + 1) div 2; the digits are whole numbers,
-- so the five truncations are one, of n * 2^17 / 10^5 (checked for every n)
local function decimals_sp(n) return (n * 131072 // 100000 + 1) // 2 end
function M.pgfrng(seed)
  local z = math.floor(seed)
  assert(z >= 1 and z <= 2147483646, "florilegium: a pgf seed is an integer from 1 to 2147483646")
  local r = {}
  local function step()
    local t = 69621 * (z % 30845) - 23902 * (z // 30845)
    if t < 0 then t = t + 2147483647 end
    z = t
    return t
  end
  function r.rnd()                          -- [0, 1]
    local c = step() % 100001
    return c == 100000 and 1.0 or decimals_sp(c) / 65536
  end
  function r.rand()                         -- [-1, 1]
    local c = step() % 200001 - 100000
    if c == 100000 or c == -100000 then return c / 100000 end
    local v = decimals_sp(c < 0 and -c or c) / 65536
    return c < 0 and -v or v
  end
  function r.random(a, b)                   -- integer in [a, b]
    if a > b then a, b = b, a end
    return a + step() % (b - a + 1)
  end
  r.step = step                             -- \pgfmathgeneratepseudorandomnumber
  function r.save() return z end
  function r.load(v) z = v end
  return r
end

return M
