root/tests/lib/libm/t_fe_round.c
/*      $NetBSD: t_fe_round.c,v 1.21 2025/04/17 13:45:22 riastradh Exp $        */

/*
 * Written by Maya Rashish <maya@NetBSD.org>
 * Public domain.
 *
 * Testing IEEE-754 rounding modes (and lrint)
 */

#include <sys/cdefs.h>
__RCSID("$NetBSD: t_fe_round.c,v 1.21 2025/04/17 13:45:22 riastradh Exp $");

#include <atf-c.h>
#include <fenv.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef __HAVE_FENV

/*#pragma STDC FENV_ACCESS ON gcc?? */

#define INT 9223L

static const char *
rmname(int rm)
{
        switch (rm) {
        case FE_TOWARDZERO:
                return "FE_TOWARDZERO";
        case FE_DOWNWARD:
                return "FE_DOWNWARD";
        case FE_UPWARD:
                return "FE_UPWARD";
        case FE_TONEAREST:
                return "FE_TONEAREST";
        default:
                return "unknown";
        }
}

/*
 * Examples are chosen to fit within the smallest single-precision
 * format any NetBSD port uses, so that we can write the examples once
 * in type double, and convert to single without raising inexact-result
 * exceptions when we're trying to test whether the integer-rounding
 * functions raise them.
 */
static const struct {
        int round_mode;
        double input;
        long int expected;
} values[] = {
        { FE_DOWNWARD,          3.75,           3},
        { FE_DOWNWARD,          -3.75,          -4},
        { FE_DOWNWARD,          +0.,            0},
        { FE_DOWNWARD,          -0.,            0},
        { FE_DOWNWARD,          -INT-0.0625,    -INT-1},
        { FE_DOWNWARD,          +INT-0.0625,    INT-1},
        { FE_DOWNWARD,          -INT+0.0625,    -INT},
        { FE_DOWNWARD,          +INT+0.0625,    INT},

        { FE_UPWARD,            +0.,            0},
        { FE_UPWARD,            -0.,            0},
        { FE_UPWARD,            -123.75,        -123},
        { FE_UPWARD,            123.75,         124},
        { FE_UPWARD,            -INT-0.0625,    -INT},
        { FE_UPWARD,            +INT-0.0625,    INT},
        { FE_UPWARD,            -INT+0.0625,    -INT+1},
        { FE_UPWARD,            +INT+0.0625,    INT+1},

        { FE_TOWARDZERO,        1.9375,         1},
        { FE_TOWARDZERO,        -1.9375,        -1},
        { FE_TOWARDZERO,        0.25,           0},
        { FE_TOWARDZERO,        INT+0.0625,     INT},
        { FE_TOWARDZERO,        INT-0.0625,     INT - 1},
        { FE_TOWARDZERO,        -INT+0.0625,    -INT + 1},
        { FE_TOWARDZERO,        +0.,            0},
        { FE_TOWARDZERO,        -0.,            0},

        { FE_TONEAREST,         -INT-0.0625,    -INT},
        { FE_TONEAREST,         +INT-0.0625,    INT},
        { FE_TONEAREST,         -INT+0.0625,    -INT},
        { FE_TONEAREST,         +INT+0.0625,    INT},
        { FE_TONEAREST,         -INT-0.53125,   -INT-1},
        { FE_TONEAREST,         +INT-0.53125,   INT-1},
        { FE_TONEAREST,         -INT+0.53125,   -INT+1},
        { FE_TONEAREST,         +INT+0.53125,   INT+1},
        { FE_TONEAREST,         +0.,            0},
        { FE_TONEAREST,         -0.,            0},
};

ATF_TC(fe_lrint);
ATF_TC_HEAD(fe_lrint, tc)
{
        atf_tc_set_md_var(tc, "descr",
            "Checking IEEE 754 rounding modes using lrint(3)");
}

ATF_TC_BODY(fe_lrint, tc)
{
        enum {
                LLRINT,
                LLRINTF,
                LRINT,
                LRINTF,
                N_FN,
        } fn;
        static const char *const fnname[] = {
                [LLRINT] = "llrint",
                [LLRINTF] = "llrintf",
                [LRINT] = "lrint",
                [LRINTF] = "lrintf",
        };
        long int received;
        unsigned i;

        for (i = 0; i < __arraycount(values); i++) {
                for (fn = 0; fn < N_FN; fn++) {
                        /*
                         * Set the requested rounding mode.
                         */
                        fesetround(values[i].round_mode);

                        /*
                         * Call the lrint(3)-family function.
                         */
                        switch (fn) {
                        case LLRINT:
                                received = llrint(values[i].input);
                                break;
                        case LLRINTF:
                                received = llrintf(values[i].input);
                                break;
                        case LRINT:
                                received = lrint(values[i].input);
                                break;
                        case LRINTF:
                                received = lrintf(values[i].input);
                                break;
                        default:
                                atf_tc_fail("impossible");
                        }

                        /*
                         * Assuming the result we got has zero
                         * fractional part, casting to long int should
                         * have no rounding.  Verify it matches the
                         * integer we expect.
                         */
                        ATF_CHECK_MSG((long int)received == values[i].expected,
                            "[%u] %s %s(%f): got %ld, expected %ld",
                            i, rmname(values[i].round_mode), fnname[fn],
                            values[i].input,
                            (long int)received, values[i].expected);

                        /* Do we get the same rounding mode out? */
                        ATF_CHECK_MSG(fegetround() == values[i].round_mode,
                            "[%u] %s: set %d (%s), got %d (%s)",
                            i, fnname[fn],
                            values[i].round_mode, rmname(values[i].round_mode),
                            fegetround(), rmname(fegetround()));
                }
        }
}

ATF_TC(fe_nearbyint_rint);
ATF_TC_HEAD(fe_nearbyint_rint, tc)
{
        atf_tc_set_md_var(tc, "descr",
            "Checking IEEE 754 rounding modes using nearbyint/rint");
}

ATF_TC_BODY(fe_nearbyint_rint, tc)
{
        enum {
                NEARBYINT,
                NEARBYINTF,
                NEARBYINTL,
                RINT,
                RINTF,
                RINTL,
                N_FN,
        } fn;
        static const char *const fnname[] = {
                [NEARBYINT] = "nearbyint",
                [NEARBYINTF] = "nearbyintf",
                [NEARBYINTL] = "nearbyintl",
                [RINT] = "rint",
                [RINTF] = "rintf",
                [RINTL] = "rintl",
        };
        double received, ipart, fpart;
        unsigned i;

#ifdef __sparc64__
        atf_tc_expect_fail("PR port-sparc64/59310:"
            " t_fe_round:fe_nearbyint_rint tests are failing");
#endif

        for (i = 0; i < __arraycount(values); i++) {
                for (fn = 0; fn < N_FN; fn++) {
                        bool expect_except =
                            values[i].input != (double)values[i].expected;

                        /*
                         * Set the requested rounding mode.
                         */
                        fesetround(values[i].round_mode);

#ifdef __ia64__
/*
 * Without this barrier, we get:
 *
 * /tmp//ccJayu9g.s:2793: Warning: Use of 'mov.m' violates RAW dependency 'AR[FPSR].sf0.flags' (impliedf)
 * /tmp//ccJayu9g.s:2793: Warning: Only the first path encountering the conflict is reported
 * /tmp//ccJayu9g.s:2757: Warning: This is the location of the conflicting usage
 *
 * (If you fix this, remove the entry from doc/HACKS.)
 */
                        __insn_barrier();
#endif

                        /*
                         * Clear sticky floating-point exception bits
                         * so we can verify whether the FE_INEXACT
                         * exception is raised.
                         */
                        feclearexcept(FE_ALL_EXCEPT);

                        /*
                         * Call the rint(3)-family function.
                         */
                        switch (fn) {
                        case NEARBYINT:
                                received = nearbyint(values[i].input);
                                expect_except = false;
                                break;
                        case NEARBYINTF:
                                received = nearbyintf(values[i].input);
                                expect_except = false;
                                break;
                        case NEARBYINTL:
                                received = nearbyintl(values[i].input);
                                expect_except = false;
                                break;
                        case RINT:
                                received = rint(values[i].input);
                                break;
                        case RINTF:
                                received = rintf(values[i].input);
                                break;
                        case RINTL:
                                received = rintl(values[i].input);
                                break;
                        default:
                                atf_tc_fail("impossible");
                        }

                        /*
                         * Verify FE_INEXACT was raised or not,
                         * depending on whether there was rounding and
                         * whether the function is supposed to raise
                         * exceptions.
                         */
                        if (expect_except) {
                                ATF_CHECK_MSG(fetestexcept(FE_INEXACT) != 0,
                                    "[%u] %s %s(%f)"
                                    " failed to raise FE_INEXACT",
                                    i, rmname(values[i].round_mode),
                                    fnname[fn], values[i].input);
                        } else {
                                ATF_CHECK_MSG(fetestexcept(FE_INEXACT) == 0,
                                    "[%u] %s %s(%f)"
                                    " spuriously raised FE_INEXACT",
                                    i, rmname(values[i].round_mode),
                                    fnname[fn], values[i].input);
                        }

                        /*
                         * Verify the fractional part of the result is
                         * zero -- the result of rounding to an integer
                         * is supposed to be an integer.
                         */
                        fpart = modf(received, &ipart);
                        ATF_CHECK_MSG(fpart == 0,
                            "[%u] %s %s(%f)=%f has fractional part %f"
                            " (integer part %f)",
                            i, rmname(values[i].round_mode), fnname[fn],
                            values[i].input, received, fpart, ipart);

                        /*
                         * Assuming the result we got has zero
                         * fractional part, casting to long int should
                         * have no rounding.  Verify it matches the
                         * integer we expect.
                         */
                        ATF_CHECK_MSG((long int)received == values[i].expected,
                            "[%u] %s %s(%f): got %f, expected %ld",
                            i, rmname(values[i].round_mode), fnname[fn],
                            values[i].input, received, values[i].expected);

                        /* Do we get the same rounding mode out? */
                        ATF_CHECK_MSG(fegetround() == values[i].round_mode,
                            "[%u] %s: set %d (%s), got %d (%s)",
                            i, fnname[fn],
                            values[i].round_mode, rmname(values[i].round_mode),
                            fegetround(), rmname(fegetround()));
                }
        }
}

#ifdef __HAVE_LONG_DOUBLE

/*
 * Use one bit more than fits in IEEE 754 binary64.
 */
static const struct {
        int round_mode;
        long double input;
        int64_t expected;
} valuesl[] = {
        { FE_TOWARDZERO,        0x2.00000000000008p+52L, 0x20000000000000 },
        { FE_DOWNWARD,          0x2.00000000000008p+52L, 0x20000000000000 },
        { FE_UPWARD,            0x2.00000000000008p+52L, 0x20000000000001 },
        { FE_TONEAREST,         0x2.00000000000008p+52L, 0x20000000000000 },
        { FE_TOWARDZERO,        0x2.00000000000018p+52L, 0x20000000000001 },
        { FE_DOWNWARD,          0x2.00000000000018p+52L, 0x20000000000001 },
        { FE_UPWARD,            0x2.00000000000018p+52L, 0x20000000000002 },
        { FE_TONEAREST,         0x2.00000000000018p+52L, 0x20000000000002 },
};

ATF_TC(fe_nearbyintl_rintl);
ATF_TC_HEAD(fe_nearbyintl_rintl, tc)
{
        atf_tc_set_md_var(tc, "descr",
            "Checking IEEE 754 rounding modes using nearbyintl/rintl");
}

ATF_TC_BODY(fe_nearbyintl_rintl, tc)
{
        enum {
                RINTL,
                NEARBYINTL,
                N_FN,
        } fn;
        static const char *const fnname[] = {
                [RINTL] = "rintl",
                [NEARBYINTL] = "nearbyintl",
        };
        long double received, ipart, fpart;
        unsigned i;

#ifdef __sparc64__
        atf_tc_expect_fail("PR port-sparc64/59310:"
            " t_fe_round:fe_nearbyint_rint tests are failing");
#endif

        for (i = 0; i < __arraycount(valuesl); i++) {
                for (fn = 0; fn < N_FN; fn++) {
                        bool expect_except =
                            (valuesl[i].input !=
                                (long double)valuesl[i].expected);

                        /*
                         * Set the requested rounding mode.
                         */
                        fesetround(valuesl[i].round_mode);

                        /*
                         * Clear sticky floating-point exception bits
                         * so we can verify whether the FE_INEXACT
                         * exception is raised.
                         */
                        feclearexcept(FE_ALL_EXCEPT);

                        /*
                         * Call the rint(3)-family function.
                         */
                        switch (fn) {
                        case NEARBYINTL:
                                received = nearbyintl(valuesl[i].input);
                                expect_except = false;
                                break;
                        case RINTL:
                                received = rintl(valuesl[i].input);
                                break;
                        default:
                                atf_tc_fail("impossible");
                        }

                        /*
                         * Verify FE_INEXACT was raised or not,
                         * depending on whether there was rounding and
                         * whether the function is supposed to raise
                         * exceptions.
                         */
                        if (expect_except) {
                                ATF_CHECK_MSG(fetestexcept(FE_INEXACT) != 0,
                                    "[%u] %s %s(%Lf)"
                                    " failed to raise FE_INEXACT",
                                    i, rmname(valuesl[i].round_mode),
                                    fnname[fn], valuesl[i].input);
                        } else {
                                ATF_CHECK_MSG(fetestexcept(FE_INEXACT) == 0,
                                    "[%u] %s %s(%Lf)"
                                    " spuriously raised FE_INEXACT",
                                    i, rmname(valuesl[i].round_mode),
                                    fnname[fn], valuesl[i].input);
                        }

                        /*
                         * Verify the fractional part of the result is
                         * zero -- the result of rounding to an integer
                         * is supposed to be an integer.
                         */
                        fpart = modfl(received, &ipart);
                        ATF_CHECK_MSG(fpart == 0,
                            "[%u] %s %s(%Lf)=%Lf has fractional part %Lf"
                            " (integer part %Lf)",
                            i, rmname(valuesl[i].round_mode), fnname[fn],
                            valuesl[i].input, received, fpart, ipart);

                        /*
                         * Assuming the result we got has zero
                         * fractional part, casting to int64_t should
                         * have no rounding.  Verify it matches the
                         * integer we expect.
                         */
                        ATF_CHECK_MSG(((int64_t)received ==
                                valuesl[i].expected),
                            "[%u] %s %s(%Lf): got %Lf, expected %jd",
                            i, rmname(valuesl[i].round_mode), fnname[fn],
                            valuesl[i].input, received, valuesl[i].expected);

                        /* Do we get the same rounding mode out? */
                        ATF_CHECK_MSG(fegetround() == valuesl[i].round_mode,
                            "[%u] %s: set %d (%s), got %d (%s)",
                            i, fnname[fn],
                            valuesl[i].round_mode,
                            rmname(valuesl[i].round_mode),
                            fegetround(), rmname(fegetround()));
                }
        }
}

#endif  /* __HAVE_LONG_DOUBLE */

ATF_TP_ADD_TCS(tp)
{

        ATF_TP_ADD_TC(tp, fe_lrint);
        ATF_TP_ADD_TC(tp, fe_nearbyint_rint);
#ifdef __HAVE_LONG_DOUBLE
        ATF_TP_ADD_TC(tp, fe_nearbyintl_rintl);
#endif

        return atf_no_error();
}

#else  /* !__HAVE_FENV */

ATF_TP_ADD_TCS(tp)
{

        /*
         * No fenv, no fesetround to test.
         */
        return atf_no_error();
}

#endif  /* __HAVE_FENV */