/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2011 Nexenta Systems, Inc. All rights reserved.
*/
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma weak __fmod = fmod
#include "libm.h"
static const double zero = 0.0;
/*
* The following implementation assumes fast 64-bit integer arith-
* metic. This is fine for sparc because we build libm in v8plus
* mode. It's also fine for sparcv9 and amd64, although we have
* assembly code on amd64. For x86, it would be better to use
* 32-bit code, but we have assembly for x86, too.
*/
double
fmod(double x, double y) {
double w;
long long hx, ix, iy, iz;
int nd, k, ny;
hx = *(long long *)&x;
ix = hx & ~0x8000000000000000ull;
iy = *(long long *)&y & ~0x8000000000000000ull;
/* handle special cases */
if (iy == 0ll)
return (_SVID_libm_err(x, y, 27));
if (ix >= 0x7ff0000000000000ll || iy > 0x7ff0000000000000ll)
return ((x * y) * zero);
if (ix <= iy)
return ((ix < iy)? x : x * zero);
/*
* Set:
* ny = true exponent of y
* nd = true exponent of x minus true exponent of y
* ix = normalized significand of x
* iy = normalized significand of y
*/
ny = iy >> 52;
k = ix >> 52;
if (ny == 0) {
/* y is subnormal, x could be normal or subnormal */
ny = 1;
while (iy < 0x0010000000000000ll) {
ny -= 1;
iy += iy;
}
nd = k - ny;
if (k == 0) {
nd += 1;
while (ix < 0x0010000000000000ll) {
nd -= 1;
ix += ix;
}
} else {
ix = 0x0010000000000000ll | (ix & 0x000fffffffffffffll);
}
} else {
/* both x and y are normal */
nd = k - ny;
ix = 0x0010000000000000ll | (ix & 0x000fffffffffffffll);
iy = 0x0010000000000000ll | (iy & 0x000fffffffffffffll);
}
/* perform fixed point mod */
while (nd--) {
iz = ix - iy;
if (iz >= 0)
ix = iz;
ix += ix;
}
iz = ix - iy;
if (iz >= 0)
ix = iz;
/* convert back to floating point and restore the sign */
if (ix == 0ll)
return (x * zero);
while (ix < 0x0010000000000000ll) {
ix += ix;
ny -= 1;
}
while (ix > 0x0020000000000000ll) { /* XXX can this ever happen? */
ny += 1;
ix >>= 1;
}
if (ny <= 0) {
/* result is subnormal */
k = -ny + 1;
ix >>= k;
*(long long *)&w = (hx & 0x8000000000000000ull) | ix;
return (w);
}
*(long long *)&w = (hx & 0x8000000000000000ull) |
((long long)ny << 52) | (ix & 0x000fffffffffffffll);
return (w);
}