/***************************************************************************
* Copyright (c) 2016, Johan Mabille and Sylvain Corlay                     *
*                                                                          *
* Distributed under the terms of the BSD 3-Clause License.                 *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#ifndef XSIMD_LOGARITHM_HPP
#define XSIMD_LOGARITHM_HPP

#include "xsimd_numerical_constant.hpp"

namespace xsimd
{
    /**
     * Computes the natural logarithm of the batch \c x.
     * @param x batch of floating point values.
     * @return the natural logarithm of \c x.
     */
    template <class B>
    batch_type_t<B> log(const simd_base<B>& x);

    /**
     * Computes the base 2 logarithm of the batch \c x.
     * @param x batch of floating point values.
     * @return the base 2 logarithm of \c x.
     */
    template <class B>
    batch_type_t<B> log2(const simd_base<B>& x);

    /**
     * Computes the base 10 logarithm of the batch \c x.
     * @param x batch of floating point values.
     * @return the base 10 logarithm of \c x.
     */
    template <class B>
    batch_type_t<B> log10(const simd_base<B>& x);

    /**
     * Computes the natural logarithm of one plus the batch \c x.
     * @param x batch of floating point values.
     * @return the natural logarithm of one plus \c x.
     */
    template <class B>
    batch_type_t<B> log1p(const simd_base<B>& x);

    /**********************
     * log implementation *
     **********************/

    namespace detail
    {
        template <class B, class T = typename B::value_type>
        struct log_kernel;

        template <class B>
        struct log_kernel<B, float>
        {
            /* origin: boost/simd/arch/common/simd/function/log.hpp */
            /*
             * ====================================================
             * copyright 2016 NumScale SAS
             *
             * Distributed under the Boost Software License, Version 1.0.
             * (See copy at http://boost.org/LICENSE_1_0.txt)
             * ====================================================
             */
            static inline B compute(const B& a)
            {
                using i_type = as_integer_t<B>;
                B x = a;
                i_type k(0);
                auto isnez = (a != B(0.));
#ifndef XSIMD_NO_DENORMALS
                auto test = (a < smallestposval<B>()) && isnez;
                if (any(test))
                {
                    k = select(bool_cast(test), k - i_type(23), k);
                    x = select(test, x * B(8388608ul), x);
                }
#endif
                i_type ix = bitwise_cast<i_type>(x);
                ix += 0x3f800000 - 0x3f3504f3;
                k += (ix >> 23) - 0x7f;
                ix = (ix & i_type(0x007fffff)) + 0x3f3504f3;
                x = bitwise_cast<B>(ix);
                B f = --x;
                B s = f / (B(2.) + f);
                B z = s * s;
                B w = z * z;
                B t1 = w * horner<B, 0x3eccce13, 0x3e789e26>(w);
                B t2 = z * horner<B, 0x3f2aaaaa, 0x3e91e9ee>(w);
                B R = t2 + t1;
                B hfsq = B(0.5) * f * f;
                B dk = to_float(k);
                B r = fma(dk, log_2hi<B>(), fma(s, (hfsq + R), dk * log_2lo<B>()) - hfsq + f);
#ifndef XSIMD_NO_INFINITIES
                B zz = select(isnez, select(a == infinity<B>(), infinity<B>(), r), minusinfinity<B>());
#else
                B zz = select(isnez, r, minusinfinity<B>());
#endif
                return select(!(a >= B(0.)), nan<B>(), zz);
            }
        };

        template <class B>
        struct log_kernel<B, double>
        {
            /* origin: boost/simd/arch/common/simd/function/log.hpp */
            /*
             * ====================================================
             * copyright 2016 NumScale SAS
             *
             * Distributed under the Boost Software License, Version 1.0.
             * (See copy at http://boost.org/LICENSE_1_0.txt)
             * ====================================================
             */
            static inline B compute(const B& a)
            {
                using i_type = as_integer_t<B>;

                B x = a;
                i_type hx = bitwise_cast<i_type>(x) >> 32;
                i_type k = zero<i_type>();
                auto isnez = (a != B(0.));
#ifndef XSIMD_NO_DENORMALS
                auto test = (a < smallestposval<B>()) && isnez;
                if (any(test))
                {
                    k = select(bool_cast(test), k - i_type(54), k);
                    x = select(test, x * B(18014398509481984ull), x);
                }
#endif
                hx += 0x3ff00000 - 0x3fe6a09e;
                k += (hx >> 20) - 0x3ff;
                B dk = to_float(k);
                hx = (hx & i_type(0x000fffff)) + 0x3fe6a09e;
                x = bitwise_cast<B>(hx << 32 | (i_type(0xffffffff) & bitwise_cast<i_type>(x)));

                B f = --x;
                B hfsq = B(0.5) * f * f;
                B s = f / (B(2.) + f);
                B z = s * s;
                B w = z * z;

                B t1 = w * horner<B,
                                  0x3fd999999997fa04ll,
                                  0x3fcc71c51d8e78afll,
                                  0x3fc39a09d078c69fll>(w);
                B t2 = z * horner<B,
                                  0x3fe5555555555593ll,
                                  0x3fd2492494229359ll,
                                  0x3fc7466496cb03dell,
                                  0x3fc2f112df3e5244ll>(w);
                B R = t2 + t1;
                B r = fma(dk, log_2hi<B>(), fma(s, (hfsq + R), dk * log_2lo<B>()) - hfsq + f);
#ifndef XSIMD_NO_INFINITIES
                B zz = select(isnez, select(a == infinity<B>(), infinity<B>(), r), minusinfinity<B>());
#else
                B zz = select(isnez, r, minusinfinity<B>());
#endif
                return select(!(a >= B(0.)), nan<B>(), zz);
            }
        };
    }

    template <class B>
    inline batch_type_t<B> log(const simd_base<B>& x)
    {
        return detail::log_kernel<batch_type_t<B>>::compute(x());
    }

    /***********************
     * log2 implementation *
     ***********************/

    namespace detail
    {
        template <class B, class T = typename B::value_type>
        struct log2_kernel;

        template <class B>
        struct log2_kernel<B, float>
        {
            /* origin: FreeBSD /usr/src/lib/msun/src/e_log2f.c */
            /*
             * ====================================================
             * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
             *
             * Developed at SunPro, a Sun Microsystems, Inc. business.
             * Permission to use, copy, modify, and distribute this
             * software is freely granted, provided that this notice
             * is preserved.
             * ====================================================
             */
            static inline B compute(const B& a)
            {
                using i_type = as_integer_t<B>;
                B x = a;
                i_type k(0);
                auto isnez = (a != B(0.));
#ifndef XSIMD_NO_DENORMALS
                auto test = (a < smallestposval<B>()) && isnez;
                if (any(test))
                {
                    k = select(bool_cast(test), k - i_type(25), k);
                    x = select(test, x * B(33554432ul), x);
                }
#endif
                i_type ix = bitwise_cast<i_type>(x);
                ix += 0x3f800000 - 0x3f3504f3;
                k += (ix >> 23) - 0x7f;
                ix = (ix & i_type(0x007fffff)) + 0x3f3504f3;
                x = bitwise_cast<B>(ix);
                B f = --x;
                B s = f / (B(2.) + f);
                B z = s * s;
                B w = z * z;
                B t1 = w * horner<B, 0x3eccce13, 0x3e789e26>(w);
                B t2 = z * horner<B, 0x3f2aaaaa, 0x3e91e9ee>(w);
                B R = t1 + t2;
                B hfsq = B(0.5) * f * f;
                B dk = to_float(k);
                B r = fma(fms(s, hfsq + R, hfsq) + f, invlog_2<B>(), dk);
#ifndef XSIMD_NO_INFINITIES
                B zz = select(isnez, select(a == infinity<B>(), infinity<B>(), r), minusinfinity<B>());
#else
                B zz = select(isnez, r, minusinfinity<B>());
#endif
                return select(!(a >= B(0.)), nan<B>(), zz);
            }
        };

        template <class B>
        struct log2_kernel<B, double>
        {
            /* origin: FreeBSD /usr/src/lib/msun/src/e_log2f.c */
            /*
             * ====================================================
             * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
             *
             * Developed at SunPro, a Sun Microsystems, Inc. business.
             * Permission to use, copy, modify, and distribute this
             * software is freely granted, provided that this notice
             * is preserved.
             * ====================================================
             */
            static inline B compute(const B& a)
            {
                using i_type = as_integer_t<B>;
                B x = a;
                i_type hx = bitwise_cast<i_type>(x) >> 32;
                i_type k = zero<i_type>();
                auto isnez = (a != B(0.));
#ifndef XSIMD_NO_DENORMALS
                auto test = (a < smallestposval<B>()) && isnez;
                if (any(test))
                {
                    k = select(bool_cast(test), k - i_type(54), k);
                    x = select(test, x * B(18014398509481984ull), x);
                }
#endif
                hx += 0x3ff00000 - 0x3fe6a09e;
                k += (hx >> 20) - 0x3ff;
                hx = (hx & i_type(0x000fffff)) + 0x3fe6a09e;
                x = bitwise_cast<B>(hx << 32 | (i_type(0xffffffff) & bitwise_cast<i_type>(x)));
                B f = --x;
                B s = f / (B(2.) + f);
                B z = s * s;
                B w = z * z;
                B t1 = w * horner<B,
                                  0x3fd999999997fa04ll,
                                  0x3fcc71c51d8e78afll,
                                  0x3fc39a09d078c69fll>(w);
                B t2 = z * horner<B,
                                  0x3fe5555555555593ll,
                                  0x3fd2492494229359ll,
                                  0x3fc7466496cb03dell,
                                  0x3fc2f112df3e5244ll>(w);
                B R = t2 + t1;
                B hfsq = B(0.5) * f * f;
                B hi = f - hfsq;
                hi = hi & bitwise_cast<B>((allbits<i_type>() << 32));
                B lo = fma(s, hfsq + R, f - hi - hfsq);
                B val_hi = hi * invlog_2hi<B>();
                B val_lo = fma(lo + hi, invlog_2lo<B>(), lo * invlog_2hi<B>());
                B dk = to_float(k);
                B w1 = dk + val_hi;
                val_lo += (dk - w1) + val_hi;
                val_hi = w1;
                B r = val_lo + val_hi;
#ifndef XSIMD_NO_INFINITIES
                B zz = select(isnez, select(a == infinity<B>(), infinity<B>(), r), minusinfinity<B>());
#else
                B zz = select(isnez, r, minusinfinity<B>());
#endif
                return select(!(a >= B(0.)), nan<B>(), zz);
            }
        };
    }

    template <class B>
    inline batch_type_t<B> log2(const simd_base<B>& x)
    {
        return detail::log2_kernel<batch_type_t<B>>::compute(x());
    }

    /************************
     * log10 implementation *
     ************************/

    namespace detail
    {
        template <class B, class T = typename B::value_type>
        struct log10_kernel;

        template <class B>
        struct log10_kernel<B, float>
        {
            /* origin: FreeBSD /usr/src/lib/msun/src/e_log10f.c */
            /*
             * ====================================================
             * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
             *
             * Developed at SunPro, a Sun Microsystems, Inc. business.
             * Permission to use, copy, modify, and distribute this
             * software is freely granted, provided that this notice
             * is preserved.
             * ====================================================
             */
            static inline B compute(const B& a)
            {
                const B
                    ivln10hi(4.3432617188e-01f),
                    ivln10lo(-3.1689971365e-05f),
                    log10_2hi(3.0102920532e-01f),
                    log10_2lo(7.9034151668e-07f);
                using i_type = as_integer_t<B>;
                B x = a;
                i_type k(0);
                auto isnez = (a != B(0.));
#ifndef XSIMD_NO_DENORMALS
                auto test = (a < smallestposval<B>()) && isnez;
                if (any(test))
                {
                    k = select(bool_cast(test), k - i_type(25), k);
                    x = select(test, x * B(33554432ul), x);
                }
#endif
                i_type ix = bitwise_cast<i_type>(x);
                ix += 0x3f800000 - 0x3f3504f3;
                k += (ix >> 23) - 0x7f;
                ix = (ix & i_type(0x007fffff)) + 0x3f3504f3;
                x = bitwise_cast<B>(ix);
                B f = --x;
                B s = f / (B(2.) + f);
                B z = s * s;
                B w = z * z;
                B t1 = w * horner<B, 0x3eccce13, 0x3e789e26>(w);
                B t2 = z * horner<B, 0x3f2aaaaa, 0x3e91e9ee>(w);
                B R = t2 + t1;
                B dk = to_float(k);
                B hfsq = B(0.5) * f * f;
                B hibits = f - hfsq;
                hibits &= bitwise_cast<B>(i_type(0xfffff000));
                B lobits = fma(s, hfsq + R, f - hibits - hfsq);
                B r = fma(dk, log10_2hi,
                          fma(hibits, ivln10hi,
                              fma(lobits, ivln10hi,
                                  fma(lobits + hibits, ivln10lo, dk * log10_2lo))));
#ifndef XSIMD_NO_INFINITIES
                B zz = select(isnez, select(a == infinity<B>(), infinity<B>(), r), minusinfinity<B>());
#else
                B zz = select(isnez, r, minusinfinity<B>());
#endif
                return select(!(a >= B(0.)), nan<B>(), zz);
            }
        };

        template <class B>
        struct log10_kernel<B, double>
        {
            /* origin: FreeBSD /usr/src/lib/msun/src/e_log10f.c */
            /*
             * ====================================================
             * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
             *
             * Developed at SunPro, a Sun Microsystems, Inc. business.
             * Permission to use, copy, modify, and distribute this
             * software is freely granted, provided that this notice
             * is preserved.
             * ====================================================
             */
            static inline B compute(const B& a)
            {
                const B
                    ivln10hi(4.34294481878168880939e-01),
                    ivln10lo(2.50829467116452752298e-11),
                    log10_2hi(3.01029995663611771306e-01),
                    log10_2lo(3.69423907715893078616e-13);
                using i_type = as_integer_t<B>;
                B x = a;
                i_type hx = bitwise_cast<i_type>(x) >> 32;
                i_type k = zero<i_type>();
                auto isnez = (a != B(0.));
#ifndef XSIMD_NO_DENORMALS
                auto test = (a < smallestposval<B>()) && isnez;
                if (any(test))
                {
                    k = select(bool_cast(test), k - i_type(54), k);
                    x = select(test, x * B(18014398509481984ull), x);
                }
#endif
                hx += 0x3ff00000 - 0x3fe6a09e;
                k += (hx >> 20) - 0x3ff;
                hx = (hx & i_type(0x000fffff)) + 0x3fe6a09e;
                x = bitwise_cast<B>(hx << 32 | (i_type(0xffffffff) & bitwise_cast<i_type>(x)));
                B f = --x;
                B dk = to_float(k);
                B s = f / (B(2.) + f);
                B z = s * s;
                B w = z * z;
                B t1 = w * horner<B,
                                  0x3fd999999997fa04ll,
                                  0x3fcc71c51d8e78afll,
                                  0x3fc39a09d078c69fll>(w);
                B t2 = z * horner<B,
                                  0x3fe5555555555593ll,
                                  0x3fd2492494229359ll,
                                  0x3fc7466496cb03dell,
                                  0x3fc2f112df3e5244ll>(w);
                B R = t2 + t1;
                B hfsq = B(0.5) * f * f;
                B hi = f - hfsq;
                hi = hi & bitwise_cast<B>(allbits<i_type>() << 32);
                B lo = f - hi - hfsq + s * (hfsq + R);
                B val_hi = hi * ivln10hi;
                B y = dk * log10_2hi;
                B val_lo = dk * log10_2lo + (lo + hi) * ivln10lo + lo * ivln10hi;
                B w1 = y + val_hi;
                val_lo += (y - w1) + val_hi;
                val_hi = w1;
                B r = val_lo + val_hi;
#ifndef XSIMD_NO_INFINITIES
                B zz = select(isnez, select(a == infinity<B>(), infinity<B>(), r), minusinfinity<B>());
#else
                B zz = select(isnez, r, minusinfinity<B>());
#endif
                return select(!(a >= B(0.)), nan<B>(), zz);
            }
        };
    }

    template <class B>
    inline batch_type_t<B> log10(const simd_base<B>& x)
    {
        return detail::log10_kernel<batch_type_t<B>>::compute(x());
    }

    /************************
     * log1p implementation *
     ************************/

    namespace detail
    {
        template <class B, class T = typename B::value_type>
        struct log1p_kernel;

        template <class B>
        struct log1p_kernel<B, float>
        {
            /* origin: boost/simd/arch/common/simd/function/log1p.hpp */
            /*
             * ====================================================
             * copyright 2016 NumScale SAS
             *
             * Distributed under the Boost Software License, Version 1.0.
             * (See copy at http://boost.org/LICENSE_1_0.txt)
             * ====================================================
             */
            static inline B compute(const B& a)
            {
                using i_type = as_integer_t<B>;
                const B uf = a + B(1.);
                auto isnez = (uf != B(0.));
                i_type iu = bitwise_cast<i_type>(uf);
                iu += 0x3f800000 - 0x3f3504f3;
                i_type k = (iu >> 23) - 0x7f;
                iu = (iu & i_type(0x007fffff)) + 0x3f3504f3;
                B f = --(bitwise_cast<B>(iu));
                B s = f / (B(2.) + f);
                B z = s * s;
                B w = z * z;
                B t1 = w * horner<B, 0x3eccce13, 0x3e789e26>(w);
                B t2 = z * horner<B, 0x3f2aaaaa, 0x3e91e9ee>(w);
                B R = t2 + t1;
                B hfsq = B(0.5) * f * f;
                B dk = to_float(k);
                /* correction term ~ log(1+x)-log(u), avoid underflow in c/u */
                B c = select(bool_cast(k >= i_type(2)), B(1.) - (uf - a), a - (uf - B(1.))) / uf;
                B r = fma(dk, log_2hi<B>(), fma(s, (hfsq + R), dk * log_2lo<B>() + c) - hfsq + f);
#ifndef XSIMD_NO_INFINITIES
                B zz = select(isnez, select(a == infinity<B>(), infinity<B>(), r), minusinfinity<B>());
#else
                B zz = select(isnez, r, minusinfinity<B>());
#endif
                return select(!(uf >= B(0.)), nan<B>(), zz);
            }
        };

        template <class B>
        struct log1p_kernel<B, double>
        {
            /* origin: boost/simd/arch/common/simd/function/log1p.hpp */
            /*
             * ====================================================
             * copyright 2016 NumScale SAS
             *
             * Distributed under the Boost Software License, Version 1.0.
             * (See copy at http://boost.org/LICENSE_1_0.txt)
             * ====================================================
             */
            static inline B compute(const B& a)
            {
                using i_type = as_integer_t<B>;
                const B uf = a + B(1.);
                auto isnez = (uf != B(0.));
                i_type hu = bitwise_cast<i_type>(uf) >> 32;
                hu += 0x3ff00000 - 0x3fe6a09e;
                i_type k = (hu >> 20) - 0x3ff;
                /* correction term ~ log(1+x)-log(u), avoid underflow in c/u */
                B c = select(bool_cast(k >= i_type(2)), B(1.) - (uf - a), a - (uf - B(1.))) / uf;
                hu = (hu & i_type(0x000fffff)) + 0x3fe6a09e;
                B f = bitwise_cast<B>((hu << 32) | (i_type(0xffffffff) & bitwise_cast<i_type>(uf)));
                f = --f;
                B hfsq = B(0.5) * f * f;
                B s = f / (B(2.) + f);
                B z = s * s;
                B w = z * z;
                B t1 = w * horner<B,
                                  0x3fd999999997fa04ll,
                                  0x3fcc71c51d8e78afll,
                                  0x3fc39a09d078c69fll>(w);
                B t2 = z * horner<B,
                                  0x3fe5555555555593ll,
                                  0x3fd2492494229359ll,
                                  0x3fc7466496cb03dell,
                                  0x3fc2f112df3e5244ll>(w);
                B R = t2 + t1;
                B dk = to_float(k);
                B r = fma(dk, log_2hi<B>(), fma(s, hfsq + R, dk * log_2lo<B>() + c) - hfsq + f);
#ifndef XSIMD_NO_INFINITIES
                B zz = select(isnez, select(a == infinity<B>(), infinity<B>(), r), minusinfinity<B>());
#else
                B zz = select(isnez, r, minusinfinity<B>());
#endif
                return select(!(uf >= B(0.)), nan<B>(), zz);
            }
        };
    }

    template <class B>
    inline batch_type_t<B> log1p(const simd_base<B>& x)
    {
        return detail::log1p_kernel<batch_type_t<B>>::compute(x());
    }
}

#endif
