/*
 * Floating Point Convert Doubles to Various
 *
 * Copyright (c) 2019 Linaro
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <float.h>
#include <fenv.h>


#include "float_helpers.h"

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

typedef struct {
    int flag;
    char *desc;
} float_mapping;

float_mapping round_flags[] = {
    { FE_TONEAREST, "to nearest" },
#ifdef FE_UPWARD
    { FE_UPWARD, "upwards" },
#endif
#ifdef FE_DOWNWARD
    { FE_DOWNWARD, "downwards" },
#endif
#ifdef FE_TOWARDZERO
    { FE_TOWARDZERO, "to zero" }
#endif
};

static void print_input(double input)
{
    char *in_fmt = fmt_f64(input);
    printf("from double: %s\n", in_fmt);
    free(in_fmt);
}

static void convert_double_to_single(double input)
{
    float output;
    char *out_fmt, *flag_fmt;

    feclearexcept(FE_ALL_EXCEPT);

    output = input;

    flag_fmt = fmt_flags();
    out_fmt = fmt_f32(output);
    printf("  to single: %s (%s)\n", out_fmt, flag_fmt);
    free(out_fmt);
    free(flag_fmt);
}

#define xstr(a) str(a)
#define str(a) #a

#define CONVERT_DOUBLE_TO_INT(TYPE, FMT)                            \
    static void convert_double_to_ ## TYPE(double input)            \
    {                                                               \
        TYPE ## _t output;                                          \
        char *flag_fmt;                                             \
        const char to[] = "to " xstr(TYPE);                         \
        feclearexcept(FE_ALL_EXCEPT);                               \
        output = input;                                             \
        flag_fmt = fmt_flags();                                     \
        printf("%11s: %" FMT " (%s)\n", to, output, flag_fmt);      \
        free(flag_fmt);                                             \
    }

CONVERT_DOUBLE_TO_INT( int32, PRId32)
CONVERT_DOUBLE_TO_INT(uint32, PRId32)
CONVERT_DOUBLE_TO_INT( int64, PRId64)
CONVERT_DOUBLE_TO_INT(uint64, PRId64)

int main(int argc, char *argv[argc])
{
    int i, j, nums;

    nums = get_num_f64();

    for (i = 0; i < ARRAY_SIZE(round_flags); ++i) {
        if (fesetround(round_flags[i].flag) != 0) {
            printf("### Rounding %s skipped\n", round_flags[i].desc);
            continue;
        }
        printf("### Rounding %s\n", round_flags[i].desc);
        for (j = 0; j < nums; j++) {
            double input = get_f64(j);
            print_input(input);
            convert_double_to_single(input);
            convert_double_to_int32(input);
            convert_double_to_int64(input);
            convert_double_to_uint32(input);
            convert_double_to_uint64(input);
        }
    }

    return 0;
}
