Files
vlibc/src/math/remquo.c
T

365 lines
7.8 KiB
C

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <math.h>
/*
* The IEEE remainder x - n*y with n = x/y rounded to nearest, ties to
* even (C23 7.12.10.2), plus the signed low bits of the integer quotient
* n stored through quo (C23 7.12.10.3), all three precisions.
*
* The structure mirrors the host glibc remainder/remquo implementation
* (glibc 2.44, measured byte-for-byte), because remquo's quotient is not
* the full n: glibc tracks only the three lowest quotient bits and still
* returns a remainder in the exact IEEE rounding, so the two functions
* share one reduce-then-round shape. x is first reduced by fmod(x, 8y)
* whenever |y| is small enough for 8y to be finite, which makes |x| < 8y
* and therefore bounds x/y below 8; the tracked quotient cquo collects
* the one 4y subtraction, the one 2y subtraction and the final rounding's
* up-to-two y subtractions, which together hold the three lowest bits of
* n. The final compare against y/2 (using doubled compares when y is so
* small that halving could lose precision) rounds x to the nearest
* multiple of y, ties to even via the natural 0.5 boundary, and each
* subtraction that fires is exact (Sterbenz), so the returned remainder
* is exact.
*
* The x86-64 host collapses the remainder and quotient signs the same
* way the code below does: the magnitude result is computed from |x| and
* |y| and then negated when x < 0, and *quo carries the sign of x/y.
* Domain errors -- |y| == 0, x not finite, y a NaN -- return a NaN (the
* classic indefinite pattern) and leave *quo untouched, as glibc does.
* Like glibc, remquo never touches errno.
*/
/* The double format: bit 63 the sign, bits 62..52 the exponent biased by
* 1023, bits 51..0 the fraction. */
static double
remquo_d(double x, double y, int *quo)
{
const unsigned long long dbl_inf = 0x7FF0000000000000ULL;
unsigned long long xw;
unsigned long long yw;
unsigned long long hx;
unsigned long long hy;
unsigned long long sx;
unsigned long long sy;
double ax;
double ay;
double yh;
int cquo;
int qneg;
__builtin_memcpy(&xw, &x, sizeof xw);
__builtin_memcpy(&yw, &y, sizeof yw);
sx = xw >> 63;
sy = yw >> 63;
qneg = (int)(sx ^ sy);
hx = xw & 0x7FFFFFFFFFFFFFFFULL;
hy = yw & 0x7FFFFFFFFFFFFFFFULL;
if (hy == 0 || hx >= dbl_inf || hy > dbl_inf)
{
xw = 0xFFF8000000000000ULL;
__builtin_memcpy(&x, &xw, sizeof xw);
return x;
}
if (hx == hy)
{
*quo = qneg ? -1 : 1;
return x * 0.0;
}
ax = fabs(x);
ay = fabs(y);
if (hy <= 0x7FBFFFFFFFFFFFFFULL)
{
ax = fmod(ax, 8.0 * ay); /* now ax < 8 * ay */
}
cquo = 0;
if (hy <= 0x7FCFFFFFFFFFFFFFULL && ax >= 4.0 * ay)
{
ax -= 4.0 * ay;
cquo += 4;
}
if (hy <= 0x7FDFFFFFFFFFFFFFULL && ax >= 2.0 * ay)
{
ax -= 2.0 * ay;
cquo += 2;
}
if (hy < 0x0020000000000000ULL)
{
if (ax + ax > ay)
{
ax -= ay;
cquo++;
if (ax + ax >= ay)
{
ax -= ay;
cquo++;
}
}
}
else
{
yh = 0.5 * ay;
if (ax > yh)
{
ax -= ay;
cquo++;
if (ax >= yh)
{
ax -= ay;
cquo++;
}
}
}
*quo = qneg ? -cquo : cquo;
if (ax == 0.0)
{
ax = 0.0;
}
if (sx)
{
ax = -ax;
}
return ax;
}
/* The float format: bit 31 the sign, bits 30..23 the exponent biased by
* 127, bits 22..0 the fraction. */
static float
remquo_f(float x, float y, int *quo)
{
const unsigned int flt_inf = 0x7F800000U;
unsigned int xw;
unsigned int yw;
unsigned int hx;
unsigned int hy;
unsigned int sx;
unsigned int sy;
float ax;
float ay;
float yh;
int cquo;
int qneg;
__builtin_memcpy(&xw, &x, sizeof xw);
__builtin_memcpy(&yw, &y, sizeof yw);
sx = xw >> 31;
sy = yw >> 31;
qneg = (int)(sx ^ sy);
hx = xw & 0x7FFFFFFFU;
hy = yw & 0x7FFFFFFFU;
if (hy == 0 || hx >= flt_inf || hy > flt_inf)
{
xw = 0xFFC00000U;
__builtin_memcpy(&x, &xw, sizeof xw);
return x;
}
if (hx == hy)
{
*quo = qneg ? -1 : 1;
return x * 0.0f;
}
ax = fabsf(x);
ay = fabsf(y);
if (hy <= 0x7DFFFFFFU)
{
ax = fmodf(ax, 8.0f * ay); /* now ax < 8 * ay */
}
cquo = 0;
if (hy <= 0x7E7FFFFFU && ax >= 4.0f * ay)
{
ax -= 4.0f * ay;
cquo += 4;
}
if (hy <= 0x7EFFFFFFU && ax >= 2.0f * ay)
{
ax -= 2.0f * ay;
cquo += 2;
}
if (hy < 0x01000000U)
{
if (ax + ax > ay)
{
ax -= ay;
cquo++;
if (ax + ax >= ay)
{
ax -= ay;
cquo++;
}
}
}
else
{
yh = 0.5f * ay;
if (ax > yh)
{
ax -= ay;
cquo++;
if (ax >= yh)
{
ax -= ay;
cquo++;
}
}
}
*quo = qneg ? -cquo : cquo;
if (ax == 0.0f)
{
ax = 0.0f;
}
if (sx)
{
ax = -ax;
}
return ax;
}
/*
* The x86 80-bit extended format: 64-bit significand m in bytes 0..7 (the
* integer bit is explicit) and a sign/exponent word se in bytes 8..9 with
* the sign in bit 15 and the exponent biased by 16383 in bits 14..0.
*/
struct remquo_ld_word
{
unsigned long long m;
unsigned short se;
};
static long double
remquo_l(long double x, long double y, int *quo)
{
struct remquo_ld_word p;
struct remquo_ld_word q;
int ex;
int ey;
int qneg;
int sx;
int sy;
long double ax;
long double ay;
long double yh;
int cquo;
__builtin_memcpy(&p, &x, sizeof p);
__builtin_memcpy(&q, &y, sizeof q);
sx = (int)(p.se >> 15);
sy = (int)(q.se >> 15);
qneg = sx ^ sy;
ex = p.se & 0x7fff;
ey = q.se & 0x7fff;
if ((q.m == 0 && ey == 0) || ex == 0x7fff ||
(ey == 0x7fff && (q.m & 0x7FFFFFFFFFFFFFFFULL) != 0))
{
p.m = 0xC000000000000000ULL;
p.se = 0xFFFF;
__builtin_memcpy(&x, &p, sizeof p);
return x;
}
if (ex == ey && p.m == q.m)
{
*quo = qneg ? -1 : 1;
return x * 0.0L;
}
ax = fabsl(x);
ay = fabsl(y);
if (ey <= 0x7ffb)
{
ax = fmodl(ax, 8.0L * ay); /* now ax < 8 * ay */
}
cquo = 0;
if (ey <= 0x7ffc && ax >= 4.0L * ay)
{
ax -= 4.0L * ay;
cquo += 4;
}
if (ey <= 0x7ffd && ax >= 2.0L * ay)
{
ax -= 2.0L * ay;
cquo += 2;
}
if (ey < 0x0002)
{
if (ax + ax > ay)
{
ax -= ay;
cquo++;
if (ax + ax >= ay)
{
ax -= ay;
cquo++;
}
}
}
else
{
yh = 0.5L * ay;
if (ax > yh)
{
ax -= ay;
cquo++;
if (ax >= yh)
{
ax -= ay;
cquo++;
}
}
}
*quo = qneg ? -cquo : cquo;
if (ax == 0.0L)
{
ax = 0.0L;
}
if (sx)
{
ax = -ax;
}
return ax;
}
/*
* remquo for a float argument.
*/
float
remquof(float x, float y, int *quo) // NOLINT(bugprone-easily-swappable-parameters)
{
return remquo_f(x, y, quo);
}
/*
* remquo for a double argument.
*/
double
remquo(double x, double y, int *quo) // NOLINT(bugprone-easily-swappable-parameters)
{
return remquo_d(x, y, quo);
}
/*
* remquo for a long double argument.
*/
long double
remquol(long double x, long double y, int *quo) // NOLINT(bugprone-easily-swappable-parameters)
{
return remquo_l(x, y, quo);
}