/*
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Copyright © 2023 Keith Packard
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials provided
 *    with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef DTOX_SIZE
#define DTOX_SIZE 8
#endif

#include "local-stdio.h"

#if DTOX_SIZE == 4 || defined(FLOAT64)

#if DTOX_SIZE == 8

#define DTOA_MAX_DIG      17
#define DTOA_MAX_10_EXP   308
#define DTOA_MIN_10_EXP   (-307)
#define DTOA_SCALE_UP_NUM 9
#define DTOA_ROUND_NUM    (DTOA_MAX_DIG + 1)

#define DTOX_UINT         uint64_t
#define DTOX_INT          int64_t
#define DTOX_ASUINT(x)    asuint64(x)

#define EXP_SHIFT         52
#define EXP_MASK          0x7ff
#define SIG_SHIFT         0
#define SIG_MASK          0xfffffffffffffLL
#define SIG_MSB           0x10000000000000LL
#define EXP_BIAS          1023
#define ASUINT(x)         asuint64(x)
#define DTOX_NDIGS        14

#define _NEED_IO_FLOAT64

#elif DTOX_SIZE == 4

#define _NEED_IO_FLOAT32
#define __dtox_engine  __ftox_engine

#define DTOX_UINT      uint32_t
#define DTOX_INT       int32_t
#define DTOX_ASUINT(x) asuint(x)

#define EXP_SHIFT      23
#define EXP_MASK       0xff
#define SIG_SHIFT      1
#define SIG_MASK       0x7fffff
#define SIG_MSB        0x800000
#define EXP_BIAS       127
#define ASUINT(x)      asuint(x)
#define DTOX_NDIGS     7
#endif

#include "dtoa.h"

/*
 * Convert a 32-bit or 64-bit float to a string in hex ('a') format
 *
 * This chunk of code gets inserted into the vfprintf function in the
 * double/float handling code as well as the long double case when
 * long double is 32- or 64- bits.
 *
 * This code assumes that there is an integer type suitable for holding
 * the entire floating point value.
 *
 * Define DTOX_UINT, DTOX_INT and DTOX_SIZE before including
 * this file.
 */

#define TOCASE(c) ((c) - case_convert)

int
__dtox_engine(DTOX_UINT ufi, struct dtoa *dtoa, int prec, unsigned char case_convert)
{
    DTOX_INT fi = (DTOX_INT)ufi;
    DTOX_INT s;
    int      exp, d;

    dtoa->flags = 0;
    if (fi < 0)
        dtoa->flags = DTOA_MINUS;

    exp = ((fi >> EXP_SHIFT) & EXP_MASK);
    s = (fi & SIG_MASK) << SIG_SHIFT;
    if (s || exp) {
        if (exp == EXP_BIAS * 2 + 1) {
            if (s)
                dtoa->flags |= DTOA_NAN;
            else
                dtoa->flags |= DTOA_INF;
            return 0;
        }

        if (!exp)
            exp = 1;
        else
            s |= (SIG_MSB << SIG_SHIFT);
        exp -= EXP_BIAS;
    }
    dtoa->exp = exp;

    if (prec < 0)
        prec = 0;
    else if (prec >= (DTOX_NDIGS - 1))
        prec = DTOX_NDIGS - 1;
    else {
        int      bits = ((DTOX_NDIGS - 1) - prec) << 2;
        DTOX_INT half = ((DTOX_INT)1) << (bits - 1);
        DTOX_INT mask = ~((half << 1) - 1);

        /* round even */
        if ((s & ~mask) > half || ((s >> bits) & 1) != 0)
            s += half;
        s &= mask;
    }

    for (d = DTOX_NDIGS - 1; d >= 0; d--) {
        int dig = s & 0xf;
        s >>= 4;
        if (dig == 0 && d > prec)
            continue;
        if (dig > 9)
            dig += TOCASE('a') - 10 - '0';
        dig += '0';
        dtoa->digits[d] = dig;
        if (prec < d)
            prec = d;
    }
    return prec;
}

#endif
