/* Canonical Q16_16 fixed-point arithmetic (C implementation).
 *
 * This is the C side of the Lean <-> C <-> Python roundtrip test.
 * It mirrors python/q16_canonical.py:
 *   - banker's rounding for float->fixed conversion
 *   - saturation to INT32_MIN/INT32_MAX
 *   - exact integer conversions
 *   - saturating add/sub
 *   - mul/div with banker's rounding (double-based, matching Python)
 */

#include <math.h>
#include <stdint.h>
#include <float.h>

#define Q16_SCALE 65536.0
#define Q16_SCALE_INT 65536
#define Q16_MIN_RAW (-2147483648LL)
#define Q16_MAX_RAW 2147483647LL

static int64_t clamp_int64(long long v) {
    if (v < Q16_MIN_RAW) return Q16_MIN_RAW;
    if (v > Q16_MAX_RAW) return Q16_MAX_RAW;
    return v;
}

/* Convert float to Q16_16 raw value using banker's rounding. */
int32_t float_to_q16_nearbyint(double f) {
    if (isnan(f) || isinf(f)) {
        return 0;  /* Python raises; C returns a sentinel. */
    }
    long long r = (long long)nearbyint(f * Q16_SCALE);
    return (int32_t)clamp_int64(r);
}

/* Convert Q16_16 raw value to float (exact). */
double q16_to_float(int32_t q) {
    return (double)q / Q16_SCALE;
}

/* Convert integer to Q16_16 raw value (exact, with saturation). */
int32_t int_to_q16(int32_t i) {
    long long r = (long long)i * Q16_SCALE_INT;
    return (int32_t)clamp_int64(r);
}

/* Convert Q16_16 raw value to integer, truncating toward zero. */
int32_t q16_to_int(int32_t q) {
    return q / Q16_SCALE_INT;
}

/* Saturating addition of two Q16_16 values. */
int32_t q16_add(int32_t a, int32_t b) {
    long long r = (long long)a + (long long)b;
    return (int32_t)clamp_int64(r);
}

/* Saturating subtraction of two Q16_16 values. */
int32_t q16_sub(int32_t a, int32_t b) {
    long long r = (long long)a - (long long)b;
    return (int32_t)clamp_int64(r);
}

/* Multiply two Q16_16 values with banker's rounding (matches Python). */
int32_t q16_mul(int32_t a, int32_t b) {
    double prod = (double)a * (double)b;
    long long r = (long long)nearbyint(prod / Q16_SCALE);
    return (int32_t)clamp_int64(r);
}

/* Divide two Q16_16 values with banker's rounding. */
int32_t q16_div(int32_t a, int32_t b) {
    if (b == 0) return 0;
    double num = (double)a * Q16_SCALE;
    long long r = (long long)nearbyint(num / (double)b);
    return (int32_t)clamp_int64(r);
}
