/*
     Description: 高精度相关的函数，如比较两个高精度浮点是否相等

     Copyright : All right reserved by ICT

     Author : Zhang Zhonghai
     Date : 2025/12/26
*/
#pragma once

#include <cmath>
#include <cstdint>


#include "math_func.h"

struct Precision {
    /**
     * <p>
     * Largest double-precision floating-point number such that
     * {@code 1 + EPSILON} is numerically equal to 1. This value is an upper
     * bound on the relative error due to rounding real numbers to double
     * precision floating-point numbers.
     * </p>
     * <p>
     * In IEEE 754 arithmetic, this is 2<sup>-53</sup>.
     * </p>
     *
     * @see <a href="http://en.wikipedia.org/wiki/Machine_epsilon">Machine epsilon</a>
     */
    static const double EPSILON;

    /**
     * Safe minimum, such that {@code 1 / SAFE_MIN} does not overflow.
     * <br/>
     * In IEEE 754 arithmetic, this is also the smallest normalized
     * number 2<sup>-1022</sup>.
     */
    static const double SAFE_MIN;

    /** Exponent offset in IEEE754 representation. */
    static constexpr int64_t EXPONENT_OFFSET = 1023l;

    /** Offset to order signed double numbers lexicographically. */
    static constexpr int64_t SGN_MASK = 0x8000000000000000L;
    /** Offset to order signed double numbers lexicographically. */
    static constexpr int SGN_MASK_FLOAT = 0x80000000;
    /** Positive zero. */
    static constexpr double POSITIVE_ZERO = 0.0;
    /** Positive zero bits. */
    static const int64_t POSITIVE_ZERO_DOUBLE_BITS;
    /** Negative zero bits. */
    static const int64_t NEGATIVE_ZERO_DOUBLE_BITS;
    /** Positive zero bits. */
    static const int POSITIVE_ZERO_FLOAT_BITS;
    /** Negative zero bits. */
    static const int NEGATIVE_ZERO_FLOAT_BITS;

    /**
     * Returns {@code true} if there is no double value strictly between the
     * arguments or the difference between them is within the range of allowed
     * error (inclusive).
     *
     * @param x First value.
     * @param y Second value.
     * @param eps Amount of allowed absolute error.
     * @return {@code true} if the values are two adjacent floating point
     * numbers or they are within range of each other.
     */
    static bool equals(double x, double y, double eps) { return equals(x, y, 1) || std::abs(y - x) <= eps; }
    /**
     * Returns true if both arguments are equal or within the range of allowed
     * error (inclusive).
     * <p>
     * Two float numbers are considered equal if there are {@code (maxUlps - 1)}
     * (or fewer) floating point numbers between them, i.e. two adjacent
     * floating point numbers are considered equal.
     * </p>
     * <p>
     * Adapted from <a
     * href="http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/">
     * Bruce Dawson</a>
     * </p>
     *
     * @param x first value
     * @param y second value
     * @param maxUlps {@code (maxUlps - 1)} is the number of floating point
     * values between {@code x} and {@code y}.
     * @return {@code true} if there are fewer than {@code maxUlps} floating
     * point values between {@code x} and {@code y}.
     */
    static bool equals(const double x, const double y, const int maxUlps) {
        const int64_t xInt = MathFunc::doubleToRawLongBits(x);
        const int64_t yInt = MathFunc::doubleToRawLongBits(y);

        bool isEqual;

        if (((xInt ^ yInt) & SGN_MASK) == 0l) {
            // number have same sign, there is no risk of overflow
            isEqual = std::abs(xInt - yInt) <= maxUlps;
        } else {
            // number have opposite signs, take care of overflow
            int64_t deltaPlus;
            int64_t deltaMinus;
            if (xInt < yInt) {
                deltaPlus = yInt - POSITIVE_ZERO_DOUBLE_BITS;
                deltaMinus = xInt - NEGATIVE_ZERO_DOUBLE_BITS;
            } else {
                deltaPlus = xInt - POSITIVE_ZERO_DOUBLE_BITS;
                deltaMinus = yInt - NEGATIVE_ZERO_DOUBLE_BITS;
            }

            if (deltaPlus > maxUlps) {
                isEqual = false;
            } else {
                isEqual = deltaMinus <= (maxUlps - deltaPlus);
            }
        }

        return isEqual && !std::isnan(x) && !std::isnan(y);
    }
};