/*      $NetBSD: vint64ops.c,v 1.6 2024/08/18 20:47:13 christos Exp $   */

/*
* vint64ops.c - operations on 'vint64' values
*
* Written by Juergen Perlinger ([email protected]) for the NTP project.
* The contents of 'html/copyright.html' apply.
* ----------------------------------------------------------------------
* This is an attempt to get the vint64 calculations stuff centralised.
*/

#include <config.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>

#include "ntp_types.h"
#include "ntp_fp.h"
#include "ntp_malloc.h"
#include "vint64ops.h"

/* -------------------------------------------------------------------------*/

vint64
strtouv64(
       char *  begp,
       char ** endp,
       int     base
       )
{
       vint64  res;
       u_char  digit;
       int     sig, num;
       u_char *src;

       num = sig = 0;
       src = (u_char *)begp;
       while (isspace(*src))
               src++;

       if (*src == '-') {
               src++;
               sig = 1;
       } else  if (*src == '+') {
               src++;
       }

       if (base == 0) {
               base = 10;
               if (*src == '0') {
                       base = 8;
                       if (toupper(*++src) == 'X') {
                               src++;
                               base = 16;
                       }
               }
       } else if (base == 16) { /* remove optional leading '0x' or '0X' */
               if (src[0] == '0' && toupper(src[1]) == 'X')
                       src += 2;
       } else if (base <= 2 || base > 36) {
               memset(&res, 0xFF, sizeof(res));
               errno = ERANGE;
               return res;
       }

       ZERO(res);
       while (*src) {
               if (isdigit(*src))
                       digit = *src - '0';
               else if (isupper(*src))
                       digit = *src - 'A' + 10;
               else if (islower(*src))
                       digit = *src - 'a' + 10;
               else
                       break;
               if (digit >= base)
                       break;
               num = 1;
#if defined(HAVE_INT64)
               res.Q_s = res.Q_s * base + digit;
#else
               /* res *= base, using 16x16->32 bit
                * multiplication. Slow but portable.
                */
               {
                       uint32_t accu;
                       accu       = (uint32_t)res.W_s.ll * base;
                       res.W_s.ll = (uint16_t)accu;
                       accu       = (accu >> 16)
                                  + (uint32_t)res.W_s.lh * base;
                       res.W_s.lh = (uint16_t)accu;
                       /* the upper bits can be done in one step: */
                       res.D_s.hi = res.D_s.hi * base + (accu >> 16);
               }
               M_ADD(res.D_s.hi, res.D_s.lo, 0, digit);
#endif
               src++;
       }
       if (!num)
               errno = EINVAL;
       if (endp)
               *endp = (char *)src;
       if (sig)
               M_NEG(res.D_s.hi, res.D_s.lo);
       return res;
}

/* -------------------------------------------------------------------------*/

int
icmpv64(
       const vint64 * lhs,
       const vint64 * rhs
       )
{
       int res;

#if defined(HAVE_INT64)
       res = (lhs->q_s > rhs->q_s)
           - (lhs->q_s < rhs->q_s);
#else
       res = (lhs->d_s.hi > rhs->d_s.hi)
           - (lhs->d_s.hi < rhs->d_s.hi);
       if ( ! res )
               res = (lhs->D_s.lo > rhs->D_s.lo)
                   - (lhs->D_s.lo < rhs->D_s.lo);
#endif

       return res;
}

/* -------------------------------------------------------------------------*/

int
ucmpv64(
       const vint64 * lhs,
       const vint64 * rhs
       )
{
       int res;

#if defined(HAVE_INT64)
       res = (lhs->Q_s > rhs->Q_s)
           - (lhs->Q_s < rhs->Q_s);
#else
       res = (lhs->D_s.hi > rhs->D_s.hi)
           - (lhs->D_s.hi < rhs->D_s.hi);
       if ( ! res )
               res = (lhs->D_s.lo > rhs->D_s.lo)
                   - (lhs->D_s.lo < rhs->D_s.lo);
#endif
       return res;
}

/* -------------------------------------------------------------------------*/

vint64
addv64(
       const vint64 *lhs,
       const vint64 *rhs
       )
{
       vint64 res;

#if defined(HAVE_INT64)
       res.Q_s = lhs->Q_s + rhs->Q_s;
#else
       res = *lhs;
       M_ADD(res.D_s.hi, res.D_s.lo, rhs->D_s.hi, rhs->D_s.lo);
#endif
       return res;
}

/* -------------------------------------------------------------------------*/

vint64
subv64(
       const vint64 *lhs,
       const vint64 *rhs
       )
{
       vint64 res;

#if defined(HAVE_INT64)
       res.Q_s = lhs->Q_s - rhs->Q_s;
#else
       res = *lhs;
       M_SUB(res.D_s.hi, res.D_s.lo, rhs->D_s.hi, rhs->D_s.lo);
#endif
       return res;
}

/* -------------------------------------------------------------------------*/

vint64
addv64i32(
       const vint64 * lhs,
       int32_t        rhs
       )
{
       vint64 res;

       res = *lhs;
#if defined(HAVE_INT64)
       res.q_s += rhs;
#else
       M_ADD(res.D_s.hi, res.D_s.lo,  -(rhs < 0), rhs);
#endif
       return res;
}

/* -------------------------------------------------------------------------*/

vint64
subv64i32(
       const vint64 * lhs,
       int32_t        rhs
       )
{
       vint64 res;

       res = *lhs;
#if defined(HAVE_INT64)
       res.q_s -= rhs;
#else
       M_SUB(res.D_s.hi, res.D_s.lo,  -(rhs < 0), rhs);
#endif
       return res;
}

/* -------------------------------------------------------------------------*/

vint64
addv64u32(
       const vint64 * lhs,
       uint32_t       rhs
       )
{
       vint64 res;

       res = *lhs;
#if defined(HAVE_INT64)
       res.Q_s += rhs;
#else
       M_ADD(res.D_s.hi, res.D_s.lo, 0, rhs);
#endif
       return res;
}

/* -------------------------------------------------------------------------*/

vint64
subv64u32(
       const vint64 * lhs,
       uint32_t       rhs
       )
{
       vint64 res;

       res = *lhs;
#if defined(HAVE_INT64)
       res.Q_s -= rhs;
#else
       M_SUB(res.D_s.hi, res.D_s.lo, 0, rhs);
#endif
       return res;
}