mux2.4/game/data/
mux2.4/src/tools/
// funmath.cpp -- MUX math function handlers.
//
// $Id: funmath.cpp,v 1.3 2005/07/14 04:40:54 rmg Exp $
//
// MUX 2.4
// Copyright (C) 1998 through 2005 Solid Vertical Domains, Ltd. All
// rights not explicitly given are reserved.
//
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"

#include <float.h>
#include <limits.h>
#include <math.h>

#include "functions.h"
#include "funmath.h"
#include "sha1.h"

#ifdef HAVE_IEEE_FP_FORMAT

const char *mux_FPStrings[] = { "+Inf", "-Inf", "Ind", "NaN", "0", "0", "0", "0" };

#define MUX_FPGROUP_PASS  0x00 // Pass-through to printf
#define MUX_FPGROUP_ZERO  0x10 // Force to be zero.
#define MUX_FPGROUP_PINF  0x20 // "+Inf"
#define MUX_FPGROUP_NINF  0x30 // "-Inf"
#define MUX_FPGROUP_IND   0x40 // "Ind"
#define MUX_FPGROUP_NAN   0x50 // "NaN"
#define MUX_FPGROUP(x) ((x) & 0xF0)

// mux_fpclass returns an integer that is one of the following:
//
#define MUX_FPCLASS_PINF  (MUX_FPGROUP_PINF|0) // Positive infinity (+INF)
#define MUX_FPCLASS_NINF  (MUX_FPGROUP_NINF|1) // Negative infinity (-INF)
#define MUX_FPCLASS_QNAN  (MUX_FPGROUP_IND |2) // Quiet NAN (Indefinite)
#define MUX_FPCLASS_SNAN  (MUX_FPGROUP_NAN |3) // Signaling NAN
#define MUX_FPCLASS_ND    (MUX_FPGROUP_ZERO|4) // Negative denormalized
#define MUX_FPCLASS_NZ    (MUX_FPGROUP_ZERO|5) // Negative zero (-0)
#define MUX_FPCLASS_PZ    (MUX_FPGROUP_ZERO|6) // Positive zero (+0)
#define MUX_FPCLASS_PD    (MUX_FPGROUP_ZERO|7) // Positive denormalized
#define MUX_FPCLASS_PN    (MUX_FPGROUP_PASS|8) // Positive normalized non-zero
#define MUX_FPCLASS_NN    (MUX_FPGROUP_PASS|9) // Negative normalized non-zero
#define MUX_FPCLASS(x)    ((x) & 0x0F)

#ifdef WIN32
#define IEEE_MASK_SIGN     0x8000000000000000ui64
#define IEEE_MASK_EXPONENT 0x7FF0000000000000ui64
#define IEEE_MASK_MANTISSA 0x000FFFFFFFFFFFFFui64
#define IEEE_MASK_QNAN     0x0008000000000000ui64
#else
#define IEEE_MASK_SIGN     0x8000000000000000ull
#define IEEE_MASK_EXPONENT 0x7FF0000000000000ull
#define IEEE_MASK_MANTISSA 0x000FFFFFFFFFFFFFull
#define IEEE_MASK_QNAN     0x0008000000000000ull
#endif

#define ARBITRARY_NUMBER 1
#define IEEE_MAKE_TABLESIZE 5
typedef union
{
    INT64  i64;
    double d;
} SpecialFloatUnion;

// We return a Quiet NAN when a Signalling NAN is requested because
// any operation on a Signalling NAN will result in a Quiet NAN anyway.
// MUX doesn't catch SIGFPE, but if it did, a Signalling NAN would
// generate a SIGFPE.
//
SpecialFloatUnion SpecialFloatTable[IEEE_MAKE_TABLESIZE] =
{
    { 0 }, // Unused.
    { IEEE_MASK_EXPONENT | IEEE_MASK_QNAN | ARBITRARY_NUMBER },
    { IEEE_MASK_EXPONENT | IEEE_MASK_QNAN | ARBITRARY_NUMBER },
    { IEEE_MASK_EXPONENT },
    { IEEE_MASK_EXPONENT | IEEE_MASK_SIGN }
};

double MakeSpecialFloat(int iWhich)
{
    return SpecialFloatTable[iWhich].d;
}

static int mux_fpclass(double result)
{
    UINT64 i64;

    *((double *)&i64) = result;

    if ((i64 & IEEE_MASK_EXPONENT) == 0)
    {
        if (i64 & IEEE_MASK_MANTISSA)
        {
            if (i64 & IEEE_MASK_SIGN) return MUX_FPCLASS_ND;
            else                      return MUX_FPCLASS_PD;
        }
        else
        {
            if (i64 & IEEE_MASK_SIGN) return MUX_FPCLASS_NZ;
            else                      return MUX_FPCLASS_PZ;
        }
    }
    else if ((i64 & IEEE_MASK_EXPONENT) == IEEE_MASK_EXPONENT)
    {
        if (i64 & IEEE_MASK_MANTISSA)
        {
            if (i64 & IEEE_MASK_QNAN) return MUX_FPCLASS_QNAN;
            else                      return MUX_FPCLASS_SNAN;
        }
        else
        {
            if (i64 & IEEE_MASK_SIGN) return MUX_FPCLASS_NINF;
            else                      return MUX_FPCLASS_PINF;
        }
    }
    else
    {
        if (i64 & IEEE_MASK_SIGN)     return MUX_FPCLASS_NN;
        else                          return MUX_FPCLASS_PN;
    }
}
#endif // HAVE_IEEE_FP_FORMAT

static double AddWithError(double& err, double a, double b)
{
    double sum = a+b;
    err = b-(sum-a);
    return sum;
}

// Typically, we are within 1ulp of an exact answer, find the shortest answer
// within that 1 ulp (that is, within 0, +ulp, and -ulp).
//
static double NearestPretty(double R)
{
    char *rve = NULL;
    int decpt;
    int bNegative;
    const int mode = 0;

    double ulpR = ulp(R);
    double R0 = R-ulpR;
    double R1 = R+ulpR;

    // R.
    //
    char *p = mux_dtoa(R, mode, 50, &decpt, &bNegative, &rve);
    int nDigits = rve - p;

    // R-ulp(R)
    //
    p = mux_dtoa(R0, mode, 50, &decpt, &bNegative, &rve);
    if (rve - p < nDigits)
    {
        nDigits = rve - p;
        R  = R0;
    }

    // R+ulp(R)
    //
    p = mux_dtoa(R1, mode, 50, &decpt, &bNegative, &rve);
    if (rve - p < nDigits)
    {
        nDigits = rve - p;
        R = R1;
    }
    return R;
}

// Compare for decreasing order by absolute value.
//
static int DCL_CDECL f_comp_abs(const void *s1, const void *s2)
{
    double a = fabs(*(double *)s1);
    double b = fabs(*(double *)s2);

    if (a > b)
    {
        return -1;
    }
    else if (a < b)
    {
        return 1;
    }
    return 0;
}

// Double compensation method. Extended by Priest from Knuth and Kahan.
//
// Error of sum is less than 2*epsilon or 1 ulp except for very large n.
// Return the result that yields the shortest number of base-10 digits.
//
static double AddDoubles(int n, double pd[])
{
    qsort(pd, n, sizeof(double), f_comp_abs);
    double sum = 0.0;
    if (0 < n)
    {
        sum = pd[0];
        double sum_err = 0.0;
        int i;
        for (i = 1; i < n; i++)
        {
            double addend_err;
            double addend = AddWithError(addend_err, sum_err, pd[i]);
            double sum1_err;
            double sum1 = AddWithError(sum1_err, sum, addend);
            sum = AddWithError(sum_err, sum1, addend_err + sum1_err);
        }
    }
    return NearestPretty(sum);
}

/* ---------------------------------------------------------------------------
 * fval: copy the floating point value into a buffer and make it presentable
 */
static void fval(char *buff, char **bufc, double result)
{
    // Get double val into buffer.
    //
#ifdef HAVE_IEEE_FP_FORMAT
    int fpc = mux_fpclass(result);
    if (MUX_FPGROUP(fpc) == MUX_FPGROUP_PASS)
    {
#endif // HAVE_IEEE_FP_FORMAT
        double rIntegerPart;
        double rFractionalPart = modf(result, &rIntegerPart);
        if (  0.0 == rFractionalPart
           && LONG_MIN <= rIntegerPart
           && rIntegerPart <= LONG_MAX)
        {
            long i = (long)rIntegerPart;
            safe_ltoa(i, buff, bufc);
        }
        else
        {
            safe_str(mux_ftoa(result, false, 0), buff, bufc);
        }
#ifdef HAVE_IEEE_FP_FORMAT
    }
    else
    {
        safe_str(mux_FPStrings[MUX_FPCLASS(fpc)], buff, bufc);
    }
#endif // HAVE_IEEE_FP_FORMAT
}

static const long nMaximums[10] =
{
    0, 9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999
};

static double g_aDoubles[(LBUF_SIZE+1)/2];

FUNCTION(fun_add)
{
    int i;
    for (i = 0; i < nfargs; i++)
    {
        int nDigits;
        long nMaxValue = 0;
        if (  !is_integer(fargs[i], &nDigits)
           || nDigits > 9
           || (nMaxValue += nMaximums[nDigits]) > 999999999L)
        {
            // Do it the slow way.
            //
            for (int j = 0; j < nfargs; j++)
            {
                g_aDoubles[j] = mux_atof(fargs[j]);
            }
            fval(buff, bufc, AddDoubles(nfargs, g_aDoubles));
            return;
        }
    }

    // We can do it the fast way.
    //
    long sum = 0;
    for (i = 0; i < nfargs; i++)
    {
        sum += mux_atol(fargs[i]);
    }
    safe_ltoa(sum, buff, bufc);
}

FUNCTION(fun_ladd)
{
    int n = 0;
    if (0 < nfargs)
    {
        SEP sep;
        if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
        {
            return;
        }

        char *cp = trim_space_sep(fargs[0], &sep);
        while (cp)
        {
            char *curr = split_token(&cp, &sep);
            g_aDoubles[n++] = mux_atof(curr);
        }
    }
    fval(buff, bufc, AddDoubles(n, g_aDoubles));
}

/////////////////////////////////////////////////////////////////
// Function : iadd(Arg[0], Arg[1],..,Arg[n])
//
// Written by : Chris Rouse (Seraphim) 04/04/2000
/////////////////////////////////////////////////////////////////

FUNCTION(fun_iadd)
{
    INT64 sum = 0;
    for (int i = 0; i < nfargs; i++)
    {
        sum += mux_atoi64(fargs[i]);
    }
    safe_i64toa(sum, buff, bufc);
}

FUNCTION(fun_sub)
{
    int nDigits;
    if (  is_integer(fargs[0], &nDigits)
       && nDigits <= 9
       && is_integer(fargs[1], &nDigits)
       && nDigits <= 9)
    {
        int iResult;
        iResult = mux_atol(fargs[0]) - mux_atol(fargs[1]);
        safe_ltoa(iResult, buff, bufc);
    }
    else
    {
        g_aDoubles[0] = mux_atof(fargs[0]);
        g_aDoubles[1] = -mux_atof(fargs[1]);
        fval(buff, bufc, AddDoubles(2, g_aDoubles));
    }
}

/////////////////////////////////////////////////////////////////
// Function : isub(Arg[0], Arg[1])
//
// Written by : Chris Rouse (Seraphim) 04/04/2000
/////////////////////////////////////////////////////////////////

FUNCTION(fun_isub)
{
    INT64 diff = mux_atoi64(fargs[0]) - mux_atoi64(fargs[1]);
    safe_i64toa(diff, buff, bufc);
}

FUNCTION(fun_mul)
{
    double prod = 1.0;
    for (int i = 0; i < nfargs; i++)
    {
        prod *= mux_atof(fargs[i]);
    }
    fval(buff, bufc, NearestPretty(prod));
}

/////////////////////////////////////////////////////////////////
// Function : imul(Arg[0], Arg[1], ... , Arg[n])
//
// Written by : Chris Rouse (Seraphim) 04/04/2000
/////////////////////////////////////////////////////////////////

FUNCTION(fun_imul)
{
    INT64 prod = 1;
    for (int i = 0; i < nfargs; i++)
    {
        prod *= mux_atoi64(fargs[i]);
    }
    safe_i64toa(prod, buff, bufc);
}

FUNCTION(fun_gt)
{
    bool bResult = false;
    int nDigits;
    if (  is_integer(fargs[0], &nDigits)
       && nDigits <= 9
       && is_integer(fargs[1], &nDigits)
       && nDigits <= 9)
    {
        bResult = (mux_atol(fargs[0]) > mux_atol(fargs[1]));
    }
    else
    {
        bResult = (mux_atof(fargs[0]) > mux_atof(fargs[1]));
    }
    safe_bool(bResult, buff, bufc);
}

FUNCTION(fun_gte)
{
    bool bResult = false;
    int nDigits;
    if (  is_integer(fargs[0], &nDigits)
       && nDigits <= 9
       && is_integer(fargs[1], &nDigits)
       && nDigits <= 9)
    {
        bResult = (mux_atol(fargs[0]) >= mux_atol(fargs[1]));
    }
    else
    {
        bResult = (mux_atof(fargs[0]) >= mux_atof(fargs[1]));
    }
    safe_bool(bResult, buff, bufc);
}

FUNCTION(fun_lt)
{
    bool bResult = false;
    int nDigits;
    if (  is_integer(fargs[0], &nDigits)
       && nDigits <= 9
       && is_integer(fargs[1], &nDigits)
       && nDigits <= 9)
    {
        bResult = (mux_atol(fargs[0]) < mux_atol(fargs[1]));
    }
    else
    {
        bResult = (mux_atof(fargs[0]) < mux_atof(fargs[1]));
    }
    safe_bool(bResult, buff, bufc);
}

FUNCTION(fun_lte)
{
    bool bResult = false;
    int nDigits;
    if (  is_integer(fargs[0], &nDigits)
       && nDigits <= 9
       && is_integer(fargs[1], &nDigits)
       && nDigits <= 9)
    {
        bResult = (mux_atol(fargs[0]) <= mux_atol(fargs[1]));
    }
    else
    {
        bResult = (mux_atof(fargs[0]) <= mux_atof(fargs[1]));
    }
    safe_bool(bResult, buff, bufc);
}

FUNCTION(fun_eq)
{
    bool bResult = false;
    int nDigits;
    if (  is_integer(fargs[0], &nDigits)
       && nDigits <= 9
       && is_integer(fargs[1], &nDigits)
       && nDigits <= 9)
    {
        bResult = (mux_atol(fargs[0]) == mux_atol(fargs[1]));
    }
    else
    {
        bResult = (  strcmp(fargs[0], fargs[1]) == 0
                  || mux_atof(fargs[0]) == mux_atof(fargs[1]));
    }
    safe_bool(bResult, buff, bufc);
}

FUNCTION(fun_neq)
{
    bool bResult = false;
    int nDigits;
    if (  is_integer(fargs[0], &nDigits)
       && nDigits <= 9
       && is_integer(fargs[1], &nDigits)
       && nDigits <= 9)
    {
        bResult = (mux_atol(fargs[0]) != mux_atol(fargs[1]));
    }
    else
    {
        bResult = (  strcmp(fargs[0], fargs[1]) != 0
                  && mux_atof(fargs[0]) != mux_atof(fargs[1]));
    }
    safe_bool(bResult, buff, bufc);
}

/*
 * ---------------------------------------------------------------------------
 * * fun_max, fun_min: Return maximum (minimum) value.
 */

FUNCTION(fun_max)
{
    double maximum = 0.0;
    for (int i = 0; i < nfargs; i++)
    {
        double tval = mux_atof(fargs[i]);
        if (  i == 0
           || tval > maximum)
        {
            maximum = tval;
        }
    }
    fval(buff, bufc, maximum);
}

FUNCTION(fun_min)
{
    double minimum = 0.0;
    for (int i = 0; i < nfargs; i++)
    {
        double tval = mux_atof(fargs[i]);
        if (  i == 0
           || tval < minimum)
        {
            minimum = tval;
        }
    }
    fval(buff, bufc, minimum);
}

/* ---------------------------------------------------------------------------
 * fun_sign: Returns -1, 0, or 1 based on the the sign of its argument.
 */

FUNCTION(fun_sign)
{
    double num = mux_atof(fargs[0]);
    if (num < 0)
    {
        safe_str("-1", buff, bufc);
    }
    else
    {
        safe_bool(num > 0, buff, bufc);
    }
}

// fun_isign: Returns -1, 0, or 1 based on the the sign of its argument.
//
FUNCTION(fun_isign)
{
    INT64 num = mux_atoi64(fargs[0]);

    if (num < 0)
    {
        safe_str("-1", buff, bufc);
    }
    else
    {
        safe_bool(num > 0, buff, bufc);
    }
}

// shl() and shr() borrowed from PennMUSH 1.50
//
FUNCTION(fun_shl)
{
    if (  is_integer(fargs[0], NULL)
       && is_integer(fargs[1], NULL))
    {
        safe_i64toa(mux_atoi64(fargs[0]) << mux_atol(fargs[1]), buff, bufc);
    }
    else
    {
        safe_str("#-1 ARGUMENTS MUST BE INTEGERS", buff, bufc);
    }
}

FUNCTION(fun_shr)
{
    if (  is_integer(fargs[0], NULL)
       && is_integer(fargs[1], NULL))
    {
        safe_i64toa(mux_atoi64(fargs[0]) >> mux_atol(fargs[1]), buff, bufc);
    }
    else
    {
        safe_str("#-1 ARGUMENTS MUST BE INTEGERS", buff, bufc);
    }
}

FUNCTION(fun_inc)
{
    if (nfargs == 1)
    {
        safe_i64toa(mux_atoi64(fargs[0]) + 1, buff, bufc);
    }
    else
    {
        safe_chr('1', buff, bufc);
    }
}

FUNCTION(fun_dec)
{
    if (nfargs == 1)
    {
        safe_i64toa(mux_atoi64(fargs[0]) - 1, buff, bufc);
    }
    else
    {
        safe_str("-1", buff, bufc);
    }
}

FUNCTION(fun_trunc)
{
    double rArg = mux_atof(fargs[0]);
    double rIntegerPart;
    double rFractionalPart;

    mux_FPRestore();
    rFractionalPart = modf(rArg, &rIntegerPart);
    mux_FPSet();

#ifdef HAVE_IEEE_FP_FORMAT
    int fpc = mux_fpclass(rIntegerPart);
    if (MUX_FPGROUP(fpc) == MUX_FPGROUP_PASS)
    {
#endif // HAVE_IEEE_FP_FORMAT
        safe_tprintf_str(buff, bufc, "%.0f", rIntegerPart);
#ifdef HAVE_IEEE_FP_FORMAT
    }
    else
    {
        safe_str(mux_FPStrings[MUX_FPCLASS(fpc)], buff, bufc);
    }
#endif // HAVE_IEEE_FP_FORMAT
}

FUNCTION(fun_fdiv)
{
    double bot = mux_atof(fargs[1]);
    double top = mux_atof(fargs[0]);
#ifndef HAVE_IEEE_FP_SNAN
    if (bot == 0.0)
    {
        if (top > 0.0)
        {
            safe_str("+Inf", buff, bufc);
        }
        else if (top < 0.0)
        {
            safe_str("-Inf", buff, bufc);
        }
        else
        {
            safe_str("Ind", buff, bufc);
        }
    }
    else
    {
        fval(buff, bufc, top/bot);
    }
#else
    fval(buff, bufc, top/bot);
#endif
}

FUNCTION(fun_idiv)
{
    INT64 bot, top;

    bot = mux_atoi64(fargs[1]);
    if (bot == 0)
    {
        safe_str("#-1 DIVIDE BY ZERO", buff, bufc);
    }
    else
    {
        top = mux_atoi64(fargs[0]);
        top = i64Division(top, bot);
        safe_i64toa(top, buff, bufc);
    }
}

FUNCTION(fun_floordiv)
{
    INT64 bot, top;

    bot = mux_atoi64(fargs[1]);
    if (bot == 0)
    {
        safe_str("#-1 DIVIDE BY ZERO", buff, bufc);
    }
    else
    {
        top = mux_atoi64(fargs[0]);
        top = i64FloorDivision(top, bot);
        safe_i64toa(top, buff, bufc);
    }
}

FUNCTION(fun_mod)
{
    INT64 bot, top;

    bot = mux_atoi64(fargs[1]);
    if (bot == 0)
    {
        bot = 1;
    }
    top = mux_atoi64(fargs[0]);
    top = i64Mod(top, bot);
    safe_i64toa(top, buff, bufc);
}

FUNCTION(fun_remainder)
{
    INT64 bot, top;

    bot = mux_atoi64(fargs[1]);
    if (bot == 0)
    {
        bot = 1;
    }
    top = mux_atoi64(fargs[0]);
    top = i64Remainder(top, bot);
    safe_i64toa(top, buff, bufc);
}

/* ---------------------------------------------------------------------------
 * fun_abs: Returns the absolute value of its argument.
 */

FUNCTION(fun_abs)
{
    double num = mux_atof(fargs[0]);
    if (num == 0.0)
    {
        safe_chr('0', buff, bufc);
    }
    else if (num < 0.0)
    {
        fval(buff, bufc, -num);
    }
    else
    {
        fval(buff, bufc, num);
    }
}

// fun_iabs: Returns the absolute value of its argument.
//
FUNCTION(fun_iabs)
{
    INT64 num = mux_atoi64(fargs[0]);

    if (num == 0)
    {
        safe_chr('0', buff, bufc);
    }
    else if (num < 0)
    {
        safe_i64toa(-num, buff, bufc);
    }
    else
    {
        safe_i64toa(num, buff, bufc);
    }
}

FUNCTION(fun_dist2d)
{
    double d;
    double sum;

    d = mux_atof(fargs[0]) - mux_atof(fargs[2]);
    sum  = d * d;
    d = mux_atof(fargs[1]) - mux_atof(fargs[3]);
    sum += d * d;

    mux_FPRestore();
    double result = sqrt(sum);
    mux_FPSet();

    fval(buff, bufc, result);
}

FUNCTION(fun_dist3d)
{
    double d;
    double sum;

    d = mux_atof(fargs[0]) - mux_atof(fargs[3]);
    sum  = d * d;
    d = mux_atof(fargs[1]) - mux_atof(fargs[4]);
    sum += d * d;
    d = mux_atof(fargs[2]) - mux_atof(fargs[5]);
    sum += d * d;

    mux_FPRestore();
    double result = sqrt(sum);
    mux_FPSet();

    fval(buff, bufc, result);
}

//------------------------------------------------------------------------
// Vector functions: VADD, VSUB, VMUL, VCROSS, VMAG, VUNIT, VDIM
// Vectors are space-separated numbers.
//
#define VADD_F   0
#define VSUB_F   1
#define VMUL_F   2
#define VDOT_F   3
#define VCROSS_F 4

static void handle_vectors
(
    char *vecarg1, char *vecarg2, char *buff, char **bufc, SEP *psep,
    SEP *posep, int flag
)
{
    char *v1[(LBUF_SIZE+1)/2], *v2[(LBUF_SIZE+1)/2];
    double scalar;
    int n, m, i;

    // Split the list up, or return if the list is empty.
    //
    if (!vecarg1 || !*vecarg1 || !vecarg2 || !*vecarg2)
    {
        return;
    }
    n = list2arr(v1, (LBUF_SIZE+1)/2, vecarg1, psep);
    m = list2arr(v2, (LBUF_SIZE+1)/2, vecarg2, psep);

    // vmul() and vadd() accepts a scalar in the first or second arg,
    // but everything else has to be same-dimensional.
    //
    if (  n != m
       && !(  (  flag == VMUL_F
              || flag == VADD_F
              || flag == VSUB_F)
           && (  n == 1
              || m == 1)))
    {
        safe_str("#-1 VECTORS MUST BE SAME DIMENSIONS", buff, bufc);
        return;
    }

    switch (flag)
    {
    case VADD_F:

        // If n or m is 1, this is scalar addition.
        // otherwise, add element-wise.
        //
        if (n == 1)
        {
            scalar = mux_atof(v1[0]);
            for (i = 0; i < m; i++)
            {
                if (i != 0)
                {
                    print_sep(posep, buff, bufc);
                }
                fval(buff, bufc, mux_atof(v2[i]) + scalar);
            }
            n = m;
        }
        else if (m == 1)
        {
            scalar = mux_atof(v2[0]);
            for (i = 0; i < n; i++)
            {
                if (i != 0)
                {
                    print_sep(posep, buff, bufc);
                }
                fval(buff, bufc, mux_atof(v1[i]) + scalar);
            }
        }
        else
        {
            for (i = 0; i < n; i++)
            {
                if (i != 0)
                {
                    print_sep(posep, buff, bufc);
                }
                fval(buff, bufc, mux_atof(v1[i]) + mux_atof(v2[i]));
            }
        }
        break;

    case VSUB_F:

        if (n == 1)
        {
            // This is a scalar minus a vector.
            //
            scalar = mux_atof(v1[0]);
            for (i = 0; i < m; i++)
            {
                if (i != 0)
                {
                    print_sep(posep, buff, bufc);
                }
                fval(buff, bufc, scalar - mux_atof(v2[i]));
            }
        }
        else if (m == 1)
        {
            // This is a vector minus a scalar.
            //
            scalar = mux_atof(v2[0]);
            for (i = 0; i < n; i++)
            {
                if (i != 0)
                {
                    print_sep(posep, buff, bufc);
                }
                fval(buff, bufc, mux_atof(v1[i]) - scalar);
            }
        }
        else
        {
            // This is a vector minus a vector.
            //
            for (i = 0; i < n; i++)
            {
                if (i != 0)
                {
                    print_sep(posep, buff, bufc);
                }
                fval(buff, bufc, mux_atof(v1[i]) - mux_atof(v2[i]));
            }
        }
        break;

    case VMUL_F:

        // If n or m is 1, this is scalar multiplication.
        // otherwise, multiply elementwise.
        //
        if (n == 1)
        {
            scalar = mux_atof(v1[0]);
            for (i = 0; i < m; i++)
            {
                if (i != 0)
                {
                    print_sep(posep, buff, bufc);
                }
                fval(buff, bufc, mux_atof(v2[i]) * scalar);
            }
        }
        else if (m == 1)
        {
            scalar = mux_atof(v2[0]);
            for (i = 0; i < n; i++)
            {
                if (i != 0)
                {
                    print_sep(posep, buff, bufc);
                }
                fval(buff, bufc, mux_atof(v1[i]) * scalar);
            }
        }
        else
        {
            // Vector element-wise product.
            //
            for (i = 0; i < n; i++)
            {
                if (i != 0)
                {
                    print_sep(posep, buff, bufc);
                }
                fval(buff, bufc, mux_atof(v1[i]) * mux_atof(v2[i]));
            }
        }
        break;

    case VDOT_F:

        scalar = 0.0;
        for (i = 0; i < n; i++)
        {
            scalar += mux_atof(v1[i]) * mux_atof(v2[i]);
        }
        fval(buff, bufc, scalar);
        break;

    case VCROSS_F:

        // cross product: (a,b,c) x (d,e,f) = (bf - ce, cd - af, ae - bd)
        //
        // Or in other words:
        //
        //      | a  b  c |
        //  det | d  e  f | = i(bf-ce) + j(cd-af) + k(ae-bd)
        //      | i  j  k |
        //
        // where i, j, and k are unit vectors in the x, y, and z
        // cartisian coordinate space and are understood when expressed
        // in vector form.
        //
        if (n != 3)
        {
            safe_str("#-1 VECTORS MUST BE DIMENSION OF 3", buff, bufc);
        }
        else
        {
            double a[2][3];
            for (i = 0; i < 3; i++)
            {
                a[0][i] = mux_atof(v1[i]);
                a[1][i] = mux_atof(v2[i]);
            }
            fval(buff, bufc, (a[0][1] * a[1][2]) - (a[0][2] * a[1][1]));
            print_sep(posep, buff, bufc);
            fval(buff, bufc, (a[0][2] * a[1][0]) - (a[0][0] * a[1][2]));
            print_sep(posep, buff, bufc);
            fval(buff, bufc, (a[0][0] * a[1][1]) - (a[0][1] * a[1][0]));
        }
        break;

    default:

        // If we reached this, we're in trouble.
        //
        safe_str("#-1 UNIMPLEMENTED", buff, bufc);
    }
}

FUNCTION(fun_vadd)
{
    SEP sep;
    if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING))
    {
        return;
    }

    SEP osep = sep;
    if (!OPTIONAL_DELIM(4, osep, DELIM_NULL|DELIM_CRLF|DELIM_STRING|DELIM_INIT))
    {
        return;
    }
    handle_vectors(fargs[0], fargs[1], buff, bufc, &sep, &osep, VADD_F);
}

FUNCTION(fun_vsub)
{
    SEP sep;
    if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING))
    {
        return;
    }

    SEP osep = sep;
    if (!OPTIONAL_DELIM(4, osep, DELIM_NULL|DELIM_CRLF|DELIM_STRING|DELIM_INIT))
    {
        return;
    }
    handle_vectors(fargs[0], fargs[1], buff, bufc, &sep, &osep, VSUB_F);
}

FUNCTION(fun_vmul)
{
    SEP sep;
    if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING))
    {
        return;
    }

    SEP osep = sep;
    if (!OPTIONAL_DELIM(4, osep, DELIM_NULL|DELIM_CRLF|DELIM_STRING|DELIM_INIT))
    {
        return;
    }
    handle_vectors(fargs[0], fargs[1], buff, bufc, &sep, &osep, VMUL_F);
}

FUNCTION(fun_vdot)
{
    // dot product: (a,b,c) . (d,e,f) = ad + be + cf
    //
    SEP sep;
    if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING))
    {
        return;
    }

    SEP osep = sep;
    if (!OPTIONAL_DELIM(4, osep, DELIM_NULL|DELIM_CRLF|DELIM_STRING|DELIM_INIT))
    {
        return;
    }
    handle_vectors(fargs[0], fargs[1], buff, bufc, &sep, &osep, VDOT_F);
}

FUNCTION(fun_vcross)
{
    // cross product: (a,b,c) x (d,e,f) = (bf - ce, cd - af, ae - bd)
    //
    SEP sep;
    if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING))
    {
        return;
    }

    SEP osep = sep;
    if (!OPTIONAL_DELIM(4, osep, DELIM_NULL|DELIM_CRLF|DELIM_STRING|DELIM_INIT))
    {
        return;
    }
    handle_vectors(fargs[0], fargs[1], buff, bufc, &sep, &osep, VCROSS_F);
}

FUNCTION(fun_vmag)
{
    SEP sep;
    if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
    {
        return;
    }

    char *v1[LBUF_SIZE];
    int n, i;
    double tmp, res = 0.0;

    // Split the list up, or return if the list is empty.
    //
    if (!fargs[0] || !*fargs[0])
    {
        return;
    }
    n = list2arr(v1, LBUF_SIZE, fargs[0], &sep);

    // Calculate the magnitude.
    //
    for (i = 0; i < n; i++)
    {
        tmp = mux_atof(v1[i]);
        res += tmp * tmp;
    }

    if (res > 0)
    {
        mux_FPRestore();
        double result = sqrt(res);
        mux_FPSet();

        fval(buff, bufc, result);
    }
    else
    {
        safe_chr('0', buff, bufc);
    }
}

FUNCTION(fun_vunit)
{
    SEP sep;
    if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
    {
        return;
    }

    char *v1[LBUF_SIZE];
    int n, i;
    double tmp, res = 0.0;

    // Split the list up, or return if the list is empty.
    //
    if (!fargs[0] || !*fargs[0])
    {
        return;
    }
    n = list2arr(v1, LBUF_SIZE, fargs[0], &sep);

    // Calculate the magnitude.
    //
    for (i = 0; i < n; i++)
    {
        tmp = mux_atof(v1[i]);
        res += tmp * tmp;
    }

    if (res <= 0)
    {
        safe_str("#-1 CAN'T MAKE UNIT VECTOR FROM ZERO-LENGTH VECTOR",
            buff, bufc);
        return;
    }
    for (i = 0; i < n; i++)
    {
        if (i != 0)
        {
            print_sep(&sep, buff, bufc);
        }

        mux_FPRestore();
        double result = sqrt(res);
        mux_FPSet();

        fval(buff, bufc, mux_atof(v1[i]) / result);
    }
}

FUNCTION(fun_floor)
{
    mux_FPRestore();
    double r = floor(mux_atof(fargs[0]));
    mux_FPSet();

#ifdef HAVE_IEEE_FP_FORMAT
    int fpc = mux_fpclass(r);
    if (MUX_FPGROUP(fpc) == MUX_FPGROUP_PASS)
    {
#endif // HAVE_IEEE_FP_FORMAT
        safe_tprintf_str(buff, bufc, "%.0f", r);
#ifdef HAVE_IEEE_FP_FORMAT
    }
    else
    {
        safe_str(mux_FPStrings[MUX_FPCLASS(fpc)], buff, bufc);
    }
#endif // HAVE_IEEE_FP_FORMAT
}

FUNCTION(fun_ceil)
{
    mux_FPRestore();
    double r = ceil(mux_atof(fargs[0]));
    mux_FPSet();

#ifdef HAVE_IEEE_FP_FORMAT
    int fpc = mux_fpclass(r);
    if (MUX_FPGROUP(fpc) == MUX_FPGROUP_PASS)
    {
#endif // HAVE_IEEE_FP_FORMAT
        safe_tprintf_str(buff, bufc, "%.0f", r);
#ifdef HAVE_IEEE_FP_FORMAT
    }
    else
    {
        safe_str(mux_FPStrings[MUX_FPCLASS(fpc)], buff, bufc);
    }
#endif // HAVE_IEEE_FP_FORMAT
}

FUNCTION(fun_round)
{
    double r = mux_atof(fargs[0]);
#ifdef HAVE_IEEE_FP_FORMAT
    int fpc = mux_fpclass(r);
    if (  MUX_FPGROUP(fpc) == MUX_FPGROUP_PASS
       || MUX_FPGROUP(fpc) == MUX_FPGROUP_ZERO)
    {
        if (MUX_FPGROUP(fpc) == MUX_FPGROUP_ZERO)
        {
            r = 0.0;
        }
#endif // HAVE_IEEE_FP_FORMAT
        int frac = mux_atol(fargs[1]);
        safe_str(mux_ftoa(r, true, frac), buff, bufc);
#ifdef HAVE_IEEE_FP_FORMAT
    }
    else
    {
        safe_str(mux_FPStrings[MUX_FPCLASS(fpc)], buff, bufc);
    }
#endif // HAVE_IEEE_FP_FORMAT
}

FUNCTION(fun_pi)
{
    safe_str("3.141592653589793", buff, bufc);
}
FUNCTION(fun_e)
{
    safe_str("2.718281828459045", buff, bufc);
}

static double ConvertRDG2R(double d, const char *szUnits)
{
    switch (mux_tolower(szUnits[0]))
    {
    case 'd':
        // Degrees to Radians.
        //
        d *= 0.017453292519943295;
        break;

    case 'g':
        // Gradians to Radians.
        //
        d *= 0.015707963267948967;
        break;
    }
    return d;
}

static double ConvertR2RDG(double d, const char *szUnits)
{
    switch (mux_tolower(szUnits[0]))
    {
    case 'd':
        // Radians to Degrees.
        //
        d *= 57.29577951308232;
        break;

    case 'g':
        // Radians to Gradians.
        //
        d *= 63.66197723675813;
        break;
    }
    return d;
}

FUNCTION(fun_ctu)
{
    double val = mux_atof(fargs[0]);
    val = ConvertRDG2R(val, fargs[1]);
    val = ConvertR2RDG(val, fargs[2]);
    fval(buff, bufc, val);
}

FUNCTION(fun_sin)
{
    double d = mux_atof(fargs[0]);
    if (nfargs == 2)
    {
        d = ConvertRDG2R(d, fargs[1]);
    }

    mux_FPRestore();
    d = sin(d);
    mux_FPSet();

    fval(buff, bufc, d);
}

FUNCTION(fun_cos)
{
    double d = mux_atof(fargs[0]);
    if (nfargs == 2)
    {
        d = ConvertRDG2R(d, fargs[1]);
    }

    mux_FPRestore();
    d = cos(d);
    mux_FPSet();

    fval(buff, bufc, d);
}

FUNCTION(fun_tan)
{
    double d = mux_atof(fargs[0]);
    if (nfargs == 2)
    {
        d = ConvertRDG2R(d, fargs[1]);
    }

    mux_FPRestore();
    d = tan(d);
    mux_FPSet();

    fval(buff, bufc, d);
}

FUNCTION(fun_asin)
{
    double val = mux_atof(fargs[0]);
#ifndef HAVE_IEEE_FP_SNAN
    if ((val < -1.0) || (val > 1.0))
    {
        safe_str("Ind", buff, bufc);
        return;
    }
#endif
    mux_FPRestore();
    val = asin(val);
    mux_FPSet();

    if (nfargs == 2)
    {
        val = ConvertR2RDG(val, fargs[1]);
    }
    fval(buff, bufc, val);
}

FUNCTION(fun_acos)
{
    double val = mux_atof(fargs[0]);
#ifndef HAVE_IEEE_FP_SNAN
    if ((val < -1.0) || (val > 1.0))
    {
        safe_str("Ind", buff, bufc);
        return;
    }
#endif
    mux_FPRestore();
    val = acos(val);
    mux_FPSet();

    if (nfargs == 2)
    {
        val = ConvertR2RDG(val, fargs[1]);
    }
    fval(buff, bufc, val);
}

FUNCTION(fun_atan)
{
    double val = mux_atof(fargs[0]);

    mux_FPRestore();
    val = atan(val);
    mux_FPSet();

    if (nfargs == 2)
    {
        val = ConvertR2RDG(val, fargs[1]);
    }
    fval(buff, bufc, val);
}

FUNCTION(fun_exp)
{
    double val = mux_atof(fargs[0]);

    mux_FPRestore();
    val = exp(val);
    mux_FPSet();

    fval(buff, bufc, val);
}

FUNCTION(fun_power)
{
    double val, val1, val2;

    val1 = mux_atof(fargs[0]);
    val2 = mux_atof(fargs[1]);
#ifndef HAVE_IEEE_FP_SNAN
    if (val1 < 0.0)
    {
        safe_str("Ind", buff, bufc);
    }
    else
    {
        mux_FPRestore();
        val = pow(val1, val2);
        mux_FPSet();
    }
#else
    mux_FPRestore();
    val = pow(val1, val2);
    mux_FPSet();
#endif
    fval(buff, bufc, val);
}

FUNCTION(fun_ln)
{
    double val;

    val = mux_atof(fargs[0]);
#ifndef HAVE_IEEE_FP_SNAN
    if (val < 0.0)
    {
        safe_str("Ind", buff, bufc);
    }
    else if (val == 0.0)
    {
        safe_str("-Inf", buff, bufc);
    }
    else
    {
        mux_FPRestore();
        val = log(val);
        mux_FPSet();
    }
#else
    mux_FPRestore();
    val = log(val);
    mux_FPSet();
#endif
    fval(buff, bufc, val);
}

FUNCTION(fun_log)
{
    double val;

    val = mux_atof(fargs[0]);
#ifndef HAVE_IEEE_FP_SNAN
    if (val < 0.0)
    {
        safe_str("Ind", buff, bufc);
    }
    else if (val == 0.0)
    {
        safe_str("-Inf", buff, bufc);
    }
    else
    {
        mux_FPRestore();
        val = log10(val);
        mux_FPSet();
    }
#else
    mux_FPRestore();
    val = log10(val);
    mux_FPSet();
#endif
    fval(buff, bufc, val);
}

FUNCTION(fun_sqrt)
{
    double val;

    val = mux_atof(fargs[0]);
#ifndef HAVE_IEEE_FP_SNAN
    if (val < 0.0)
    {
        safe_str("Ind", buff, bufc);
    }
    else if (val == 0.0)
    {
        safe_chr('0', buff, bufc);
    }
    else
    {
        mux_FPRestore();
        val = sqrt(val);
        mux_FPSet();
    }
#else
    mux_FPRestore();
    val = sqrt(val);
    mux_FPSet();
#endif
    fval(buff, bufc, val);
}

/* ---------------------------------------------------------------------------
 * isnum: is the argument a number?
 */

FUNCTION(fun_isnum)
{
    safe_bool(is_real(fargs[0]), buff, bufc);
}

/* ---------------------------------------------------------------------------
 * israt: is the argument an rational?
 */

FUNCTION(fun_israt)
{
    safe_bool(is_rational(fargs[0]), buff, bufc);
}

/* ---------------------------------------------------------------------------
 * isint: is the argument an integer?
 */

FUNCTION(fun_isint)
{
    safe_bool(is_integer(fargs[0], NULL), buff, bufc);
}

FUNCTION(fun_and)
{
    bool val = true;
    for (int i = 0; i < nfargs && val; i++)
    {
        val = isTRUE(mux_atol(fargs[i]));
    }
    safe_bool(val, buff, bufc);
}

FUNCTION(fun_or)
{
    bool val = false;
    for (int i = 0; i < nfargs && !val; i++)
    {
        val = isTRUE(mux_atol(fargs[i]));
    }
    safe_bool(val, buff, bufc);
}

FUNCTION(fun_andbool)
{
    bool val = true;
    for (int i = 0; i < nfargs && val; i++)
    {
        val = xlate(fargs[i]);
    }
    safe_bool(val, buff, bufc);
}

FUNCTION(fun_orbool)
{
    bool val = false;
    for (int i = 0; i < nfargs && !val; i++)
    {
        val = xlate(fargs[i]);
    }
    safe_bool(val, buff, bufc);
}

FUNCTION(fun_cand)
{
    bool val = true;
    char *temp = alloc_lbuf("fun_cand");
    for (int i = 0; i < nfargs && val && !MuxAlarm.bAlarmed; i++)
    {
        char *bp = temp;
        char *str = fargs[i];
        mux_exec(temp, &bp, executor, caller, enactor,
            EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
        *bp = '\0';
        val = isTRUE(mux_atol(temp));
    }
    free_lbuf(temp);
    safe_bool(val, buff, bufc);
}

FUNCTION(fun_cor)
{
    bool val = false;
    char *temp = alloc_lbuf("fun_cor");
    for (int i = 0; i < nfargs && !val && !MuxAlarm.bAlarmed; i++)
    {
        char *bp = temp;
        char *str = fargs[i];
        mux_exec(temp, &bp, executor, caller, enactor,
            EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
        *bp = '\0';
        val = isTRUE(mux_atol(temp));
    }
    free_lbuf(temp);
    safe_bool(val, buff, bufc);
}

FUNCTION(fun_candbool)
{
    bool val = true;
    char *temp = alloc_lbuf("fun_candbool");
    for (int i = 0; i < nfargs && val && !MuxAlarm.bAlarmed; i++)
    {
        char *bp = temp;
        char *str = fargs[i];
        mux_exec(temp, &bp, executor, caller, enactor,
            EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
        *bp = '\0';
        val = xlate(temp);
    }
    free_lbuf(temp);
    safe_bool(val, buff, bufc);
}

FUNCTION(fun_corbool)
{
    bool val = false;
    char *temp = alloc_lbuf("fun_corbool");
    for (int i = 0; i < nfargs && !val && !MuxAlarm.bAlarmed; i++)
    {
        char *bp = temp;
        char *str = fargs[i];
        mux_exec(temp, &bp, executor, caller, enactor,
            EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, cargs, ncargs);
        *bp = '\0';
        val = xlate(temp);
    }
    free_lbuf(temp);
    safe_bool(val, buff, bufc);
}

FUNCTION(fun_xor)
{
    bool val = false;
    for (int i = 0; i < nfargs; i++)
    {
        int tval = mux_atol(fargs[i]);
        val = (val && !tval) || (!val && tval);
    }
    safe_bool(val, buff, bufc);
}

FUNCTION(fun_not)
{
    safe_bool(!xlate(fargs[0]), buff, bufc);
}

FUNCTION(fun_t)
{
    if (  nfargs <= 0
       || fargs[0][0] == '\0')
    {
        safe_chr('0', buff, bufc);
    }
    else
    {
        safe_bool(xlate(fargs[0]), buff, bufc);
    }
}
static const char *bigones[] =
{
    "",
    "thousand",
    "million",
    "billion",
    "trillion"
};

static const char *singles[] =
{
    "",
    "one",
    "two",
    "three",
    "four",
    "five",
    "six",
    "seven",
    "eight",
    "nine"
};

static const char *teens[] =
{
    "ten",
    "eleven",
    "twelve",
    "thirteen",
    "fourteen",
    "fifteen",
    "sixteen",
    "seventeen",
    "eighteen",
    "nineteen"
};

static const char *tens[] =
{
    "",
    "",
    "twenty",
    "thirty",
    "forty",
    "fifty",
    "sixty",
    "seventy",
    "eighty",
    "ninety"
};

static const char *th_prefix[] =
{
    "",
    "ten",
    "hundred"
};

class CSpellNum
{
public:
    void SpellNum(const char *p, char *buff_arg, char **bufc_arg);

private:
    void TwoDigits(const char *p);
    void ThreeDigits(const char *p, int iBigOne);
    void ManyDigits(int n, const char *p, bool bHundreds);
    void FractionalDigits(int n, const char *p);

    void StartWord(void);
    void AddWord(const char *p);

    char *buff;
    char **bufc;
    bool bNeedSpace;
};

void CSpellNum::StartWord(void)
{
    if (bNeedSpace)
    {
        safe_chr(' ', buff, bufc);
    }
    bNeedSpace = true;
}

void CSpellNum::AddWord(const char *p)
{
    safe_str(p, buff, bufc);
}

// Handle two-character sequences.
//
void CSpellNum::TwoDigits(const char *p)
{
    int n0 = p[0] - '0';
    int n1 = p[1] - '0';

    if (n0 == 0)
    {
        if (n1 != 0)
        {
            StartWord();
            AddWord(singles[n1]);
        }
        return;
    }
    else if (n0 == 1)
    {
        StartWord();
        AddWord(teens[n1]);
        return;
    }
    if (n1 == 0)
    {
        StartWord();
        AddWord(tens[n0]);
    }
    else
    {
        StartWord();
        AddWord(tens[n0]);
        AddWord("-");
        AddWord(singles[n1]);
    }
}

// Handle three-character sequences.
//
void CSpellNum::ThreeDigits(const char *p, int iBigOne)
{
    if (  p[0] == '0'
       && p[1] == '0'
       && p[2] == '0')
    {
        return;
    }

    // Handle hundreds.
    //
    if (p[0] != '0')
    {
        StartWord();
        AddWord(singles[p[0]-'0']);
        StartWord();
        AddWord("hundred");
    }
    TwoDigits(p+1);
    if (iBigOne > 0)
    {
        StartWord();
        AddWord(bigones[iBigOne]);
    }
}

// Handle a series of patterns of three.
//
void CSpellNum::ManyDigits(int n, const char *p, bool bHundreds)
{
    // Handle special Hundreds cases.
    //
    if (  bHundreds
       && n == 4
       && p[1] != '0')
    {
        TwoDigits(p);
        StartWord();
        AddWord("hundred");
        TwoDigits(p+2);
        return;
    }

    // Handle normal cases.
    //
    int ndiv = ((n + 2) / 3) - 1;
    int nrem = n % 3;
    char buf[3];
    if (nrem == 0)
    {
        nrem = 3;
    }

    int j = nrem;
    for (int i = 2; 0 <= i; i--)
    {
        if (j)
        {
            j--;
            buf[i] = p[j];
        }
        else
        {
            buf[i] = '0';
        }
    }
    ThreeDigits(buf, ndiv);
    p += nrem;
    while (ndiv-- > 0)
    {
        ThreeDigits(p, ndiv);
        p += 3;
    }
}

// Handle precision ending for part to the right of the decimal place.
//
void CSpellNum::FractionalDigits(int n, const char *p)
{
    ManyDigits(n, p, false);
    if (  0 < n
       && n < 15)
    {
        int d = n / 3;
        int r = n % 3;
        StartWord();
        if (r != 0)
        {
            AddWord(th_prefix[r]);
            if (d != 0)
            {
                AddWord("-");
            }
        }
        AddWord(bigones[d]);
        AddWord("th");
        INT64 i64 = mux_atoi64(p);
        if (i64 != 1)
        {
            AddWord("s");
        }
    }
}

void CSpellNum::SpellNum(const char *number, char *buff_arg, char **bufc_arg)
{
    buff = buff_arg;
    bufc = bufc_arg;
    bNeedSpace = false;

    // Trim Spaces from beginning.
    //
    while (mux_isspace(*number))
    {
        number++;
    }

    if (*number == '-')
    {
        StartWord();
        AddWord("negative");
        number++;
    }

    // Trim Zeroes from Beginning.
    //
    while (*number == '0')
    {
        number++;
    }

    const char *pA = number;
    while (mux_isdigit(*number))
    {
        number++;
    }
    size_t nA = number - pA;

    const char *pB  = NULL;
    size_t nB = 0;
    if (*number == '.')
    {
        number++;
        pB = number;
        while (mux_isdigit(*number))
        {
            number++;
        }
        nB = number - pB;
    }

    // Skip trailing spaces.
    //
    while (mux_isspace(*number))
    {
        number++;
    }

    if (  *number
       || nA >= 16
       || nB >= 15)
    {
        safe_str("#-1 ARGUMENT MUST BE A NUMBER", buff, bufc);
        return;
    }

    if (nA == 0)
    {
        if (nB == 0)
        {
            StartWord();
            AddWord("zero");
        }
    }
    else
    {
        ManyDigits(nA, pA, true);
        if (nB)
        {
            StartWord();
            AddWord("and");
        }
    }
    if (nB)
    {
        FractionalDigits(nB, pB);
    }
}

FUNCTION(fun_spellnum)
{
    CSpellNum sn;
    sn.SpellNum(fargs[0], buff, bufc);
}

FUNCTION(fun_roman)
{
    const char *number = fargs[0];

    // Trim Spaces from beginning.
    //
    while (mux_isspace(*number))
    {
        number++;
    }

    // Trim Zeroes from Beginning.
    //
    while (*number == '0')
    {
        number++;
    }

    const char *pA = number;
    while (mux_isdigit(*number))
    {
        number++;
    }
    size_t nA = number - pA;

    // Skip trailing spaces.
    //
    while (mux_isspace(*number))
    {
        number++;
    }

    // Validate that argument is numeric with a value between 1 and 3999.
    //
    if (*number || nA < 1)
    {
        safe_str("#-1 ARGUMENT MUST BE A POSITIVE NUMBER", buff, bufc);
        return;
    }
    else if (  nA > 4
            || (  nA == 1
               && pA[0] == '0')
            || (  nA == 4
               && '3' < pA[0]))
    {
        safe_range(buff, bufc);
        return;
    }

    // I:1, V:5, X:10, L:50, C:100, D:500, M:1000
    //
    // Ones:      _ I II III IV V VI VII VIII IX
    // Tens:      _ X XX XXX XL L LX LXX LXXX XC
    // Hundreds:  _ C CC CCC CD D DC DCC DCCC CM
    // Thousands: _ M MM MMM
    //
    static const char aLetters[4][3] =
    {
        { 'I', 'V', 'X' },
        { 'X', 'L', 'C' },
        { 'C', 'D', 'M' },
        { 'M', ' ', ' ' }
    };

    static const char *aCode[10] =
    {
        "",
        "1",
        "11",
        "111",
        "12",
        "2",
        "21",
        "211",
        "2111",
        "13"
    };

    while (nA--)
    {
        const char *pCode = aCode[*pA - '0'];
        const char *pLetters = aLetters[nA];

        while (*pCode)
        {
            safe_chr(pLetters[*pCode - '1'], buff, bufc);
            pCode++;
        }
        pA++;
    }
}

/*-------------------------------------------------------------------------
 * List-based numeric functions.
 */

FUNCTION(fun_land)
{
    bool bValue = true;
    if (0 < nfargs)
    {
        SEP sep;
        if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
        {
            return;
        }

        char *cp = trim_space_sep(fargs[0], &sep);
        while (cp && bValue)
        {
            char *curr = split_token(&cp, &sep);
            bValue = isTRUE(mux_atol(curr));
        }
    }
    safe_bool(bValue, buff, bufc);
}

FUNCTION(fun_lor)
{
    bool bValue = false;
    if (0 < nfargs)
    {
        SEP sep;
        if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
        {
            return;
        }

        char *cp = trim_space_sep(fargs[0], &sep);
        while (cp && !bValue)
        {
            char *curr = split_token(&cp, &sep);
            bValue = isTRUE(mux_atol(curr));
        }
    }
    safe_bool(bValue, buff, bufc);
}

FUNCTION(fun_band)
{
    UINT64 val = UINT64_MAX_VALUE;
    for (int i = 0; i < nfargs; i++)
    {
        if (is_integer(fargs[i], NULL))
        {
            val &= mux_atoi64(fargs[i]);
        }
        else
        {
            safe_str("#-1 ARGUMENTS MUST BE INTEGERS", buff, bufc);
            return;
        }
    }
    safe_i64toa(val, buff, bufc);
}

FUNCTION(fun_bor)
{
    UINT64 val = 0;
    for (int i = 0; i < nfargs; i++)
    {
        if (is_integer(fargs[i], NULL))
        {
            val |= mux_atoi64(fargs[i]);
        }
        else
        {
            safe_str("#-1 ARGUMENTS MUST BE INTEGERS", buff, bufc);
            return;
        }
    }
    safe_i64toa(val, buff, bufc);
}

FUNCTION(fun_bnand)
{
    if (  is_integer(fargs[0], NULL)
       && is_integer(fargs[1], NULL))
    {
        safe_i64toa(mux_atoi64(fargs[0]) & ~(mux_atoi64(fargs[1])), buff, bufc);
    }
    else
    {
        safe_str("#-1 ARGUMENTS MUST BE INTEGERS", buff, bufc);
    }
}

FUNCTION(fun_bxor)
{
    UINT64 val = 0;
    for (int i = 0; i < nfargs; i++)
    {
        if (is_integer(fargs[i], NULL))
        {
            val ^= mux_atoi64(fargs[i]);
        }
        else
        {
            safe_str("#-1 ARGUMENTS MUST BE INTEGERS", buff, bufc);
            return;
        }
    }
    safe_i64toa(val, buff, bufc);
}

FUNCTION(fun_crc32)
{
    UINT32 ulCRC32 = 0;
    for (int i = 0; i < nfargs; i++)
    {
        int n = strlen(fargs[i]);
        ulCRC32 = CRC32_ProcessBuffer(ulCRC32, fargs[i], n);
    }
    safe_i64toa(ulCRC32, buff, bufc);
}

FUNCTION(fun_sha1)
{
    int i;
    SHA1_CONTEXT shac;
    SHA1_Init(&shac);
    for (i = 0; i < nfargs; i++)
    {
        SHA1_Compute(&shac, strlen(fargs[i]), fargs[i]);
    }
    SHA1_Final(&shac);
    for (i = 0; i <= 4; i++)
    {
        char buf[9];
        sprintf(buf, "%08X", shac.H[i]);
        safe_str(buf, buff, bufc);
    }
}