mirror of
git://sourceware.org/git/newlib-cygwin.git
synced 2025-01-23 15:40:14 +08:00
73bce6597c
RISC-V newlib fails to build for soft float multilibs since long double support was enabled in: commit 04798b7bb69571452d2cfc7e0b052a9bbd3b619d Author: Kito Cheng <kito.cheng@sifive.com> Date: Mon Dec 4 15:41:39 2023 +0800 RISC-V: Support long double math Long double for RISC-V is using 128 bit IEEE 754 format like Aarch64, so we reference AArch64 to support that. The RISC-V soft floating point environment only supports the FE_TONEAREST rounding mode and does not support exceptions. Guard long double rounding and exception support with ifdefs based on the presence of the relevant rounding modes and exceptions. Tested on gcc/g++ testsuite using RISC-V GNU Newlib Toolchain built by riscv-gnu-toolchain with multilibs: riscv-sim/-march=rv32i/-mabi=ilp32/-mcmodel=medlow riscv-sim/-march=rv32iac/-mabi=ilp32/-mcmodel=medlow riscv-sim/-march=rv32im/-mabi=ilp32/-mcmodel=medlow riscv-sim/-march=rv32imac/-mabi=ilp32/-mcmodel=medlow riscv-sim/-march=rv32imafc/-mabi=ilp32f/-mcmodel=medlow riscv-sim/-march=rv64imac/-mabi=lp64/-mcmodel=medlow Co-authored-by: Simon Cook <simon.cook@embecosm.com>
285 lines
7.5 KiB
C
285 lines
7.5 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 2005-2011 David Schultz <das@FreeBSD.ORG>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <fenv.h>
|
|
#include <float.h>
|
|
#include <math.h>
|
|
|
|
#include "fpmath.h"
|
|
|
|
/*
|
|
* A struct dd represents a floating-point number with twice the precision
|
|
* of a long double. We maintain the invariant that "hi" stores the high-order
|
|
* bits of the result.
|
|
*/
|
|
struct dd {
|
|
long double hi;
|
|
long double lo;
|
|
};
|
|
|
|
/*
|
|
* Compute a+b exactly, returning the exact result in a struct dd. We assume
|
|
* that both a and b are finite, but make no assumptions about their relative
|
|
* magnitudes.
|
|
*/
|
|
static inline struct dd
|
|
dd_add(long double a, long double b)
|
|
{
|
|
struct dd ret;
|
|
long double s;
|
|
|
|
ret.hi = a + b;
|
|
s = ret.hi - a;
|
|
ret.lo = (a - (ret.hi - s)) + (b - s);
|
|
return (ret);
|
|
}
|
|
|
|
/*
|
|
* Compute a+b, with a small tweak: The least significant bit of the
|
|
* result is adjusted into a sticky bit summarizing all the bits that
|
|
* were lost to rounding. This adjustment negates the effects of double
|
|
* rounding when the result is added to another number with a higher
|
|
* exponent. For an explanation of round and sticky bits, see any reference
|
|
* on FPU design, e.g.,
|
|
*
|
|
* J. Coonen. An Implementation Guide to a Proposed Standard for
|
|
* Floating-Point Arithmetic. Computer, vol. 13, no. 1, Jan 1980.
|
|
*/
|
|
static inline long double
|
|
add_adjusted(long double a, long double b)
|
|
{
|
|
struct dd sum;
|
|
union IEEEl2bits u;
|
|
|
|
sum = dd_add(a, b);
|
|
if (sum.lo != 0) {
|
|
u.e = sum.hi;
|
|
if ((u.bits.manl & 1) == 0)
|
|
sum.hi = nextafterl(sum.hi, INFINITY * sum.lo);
|
|
}
|
|
return (sum.hi);
|
|
}
|
|
|
|
/*
|
|
* Compute ldexp(a+b, scale) with a single rounding error. It is assumed
|
|
* that the result will be subnormal, and care is taken to ensure that
|
|
* double rounding does not occur.
|
|
*/
|
|
static inline long double
|
|
add_and_denormalize(long double a, long double b, int scale)
|
|
{
|
|
struct dd sum;
|
|
int bits_lost;
|
|
union IEEEl2bits u;
|
|
|
|
sum = dd_add(a, b);
|
|
|
|
/*
|
|
* If we are losing at least two bits of accuracy to denormalization,
|
|
* then the first lost bit becomes a round bit, and we adjust the
|
|
* lowest bit of sum.hi to make it a sticky bit summarizing all the
|
|
* bits in sum.lo. With the sticky bit adjusted, the hardware will
|
|
* break any ties in the correct direction.
|
|
*
|
|
* If we are losing only one bit to denormalization, however, we must
|
|
* break the ties manually.
|
|
*/
|
|
if (sum.lo != 0) {
|
|
u.e = sum.hi;
|
|
bits_lost = -u.bits.exp - scale + 1;
|
|
if ((bits_lost != 1) ^ (int)(u.bits.manl & 1))
|
|
sum.hi = nextafterl(sum.hi, INFINITY * sum.lo);
|
|
}
|
|
return (ldexp(sum.hi, scale));
|
|
}
|
|
|
|
/*
|
|
* Compute a*b exactly, returning the exact result in a struct dd. We assume
|
|
* that both a and b are normalized, so no underflow or overflow will occur.
|
|
* The current rounding mode must be round-to-nearest.
|
|
*/
|
|
static inline struct dd
|
|
dd_mul(long double a, long double b)
|
|
{
|
|
#if LDBL_MANT_DIG == 64
|
|
static const long double split = 0x1p32L + 1.0;
|
|
#elif LDBL_MANT_DIG == 113
|
|
static const long double split = 0x1p57L + 1.0;
|
|
#endif
|
|
struct dd ret;
|
|
long double ha, hb, la, lb, p, q;
|
|
|
|
p = a * split;
|
|
ha = a - p;
|
|
ha += p;
|
|
la = a - ha;
|
|
|
|
p = b * split;
|
|
hb = b - p;
|
|
hb += p;
|
|
lb = b - hb;
|
|
|
|
p = ha * hb;
|
|
q = ha * lb + la * hb;
|
|
|
|
ret.hi = p + q;
|
|
ret.lo = p - ret.hi + q + la * lb;
|
|
return (ret);
|
|
}
|
|
|
|
/*
|
|
* Fused multiply-add: Compute x * y + z with a single rounding error.
|
|
*
|
|
* We use scaling to avoid overflow/underflow, along with the
|
|
* canonical precision-doubling technique adapted from:
|
|
*
|
|
* Dekker, T. A Floating-Point Technique for Extending the
|
|
* Available Precision. Numer. Math. 18, 224-242 (1971).
|
|
*/
|
|
long double
|
|
fmal(long double x, long double y, long double z)
|
|
{
|
|
long double xs, ys, zs, adj;
|
|
struct dd xy, r;
|
|
int oround;
|
|
int ex, ey, ez;
|
|
int spread;
|
|
|
|
/*
|
|
* Handle special cases. The order of operations and the particular
|
|
* return values here are crucial in handling special cases involving
|
|
* infinities, NaNs, overflows, and signed zeroes correctly.
|
|
*/
|
|
if (x == 0.0 || y == 0.0)
|
|
return (x * y + z);
|
|
if (z == 0.0)
|
|
return (x * y);
|
|
if (!isfinite(x) || !isfinite(y))
|
|
return (x * y + z);
|
|
if (!isfinite(z))
|
|
return (z);
|
|
|
|
xs = frexpl(x, &ex);
|
|
ys = frexpl(y, &ey);
|
|
zs = frexpl(z, &ez);
|
|
oround = fegetround();
|
|
spread = ex + ey - ez;
|
|
|
|
/*
|
|
* If x * y and z are many orders of magnitude apart, the scaling
|
|
* will overflow, so we handle these cases specially. Rounding
|
|
* modes other than FE_TONEAREST are painful.
|
|
*/
|
|
if (spread < -LDBL_MANT_DIG) {
|
|
#ifdef FE_INEXACT
|
|
feraiseexcept(FE_INEXACT);
|
|
#endif
|
|
#ifdef FE_UNDERFLOW
|
|
if (!isnormal(z))
|
|
feraiseexcept(FE_UNDERFLOW);
|
|
#endif
|
|
switch (oround) {
|
|
default: /* FE_TONEAREST */
|
|
return (z);
|
|
#ifdef FE_TOWARDZERO
|
|
case FE_TOWARDZERO:
|
|
if (x > 0.0 ^ y < 0.0 ^ z < 0.0)
|
|
return (z);
|
|
else
|
|
return (nextafterl(z, 0));
|
|
#endif
|
|
#ifdef FE_DOWNWARD
|
|
case FE_DOWNWARD:
|
|
if (x > 0.0 ^ y < 0.0)
|
|
return (z);
|
|
else
|
|
return (nextafterl(z, -INFINITY));
|
|
#endif
|
|
#ifdef FE_UPWARD
|
|
case FE_UPWARD:
|
|
if (x > 0.0 ^ y < 0.0)
|
|
return (nextafterl(z, INFINITY));
|
|
else
|
|
return (z);
|
|
#endif
|
|
}
|
|
}
|
|
if (spread <= LDBL_MANT_DIG * 2)
|
|
zs = ldexpl(zs, -spread);
|
|
else
|
|
zs = copysignl(LDBL_MIN, zs);
|
|
|
|
fesetround(FE_TONEAREST);
|
|
/* work around clang bug 8100 */
|
|
volatile long double vxs = xs;
|
|
|
|
/*
|
|
* Basic approach for round-to-nearest:
|
|
*
|
|
* (xy.hi, xy.lo) = x * y (exact)
|
|
* (r.hi, r.lo) = xy.hi + z (exact)
|
|
* adj = xy.lo + r.lo (inexact; low bit is sticky)
|
|
* result = r.hi + adj (correctly rounded)
|
|
*/
|
|
xy = dd_mul(vxs, ys);
|
|
r = dd_add(xy.hi, zs);
|
|
|
|
spread = ex + ey;
|
|
|
|
if (r.hi == 0.0) {
|
|
/*
|
|
* When the addends cancel to 0, ensure that the result has
|
|
* the correct sign.
|
|
*/
|
|
fesetround(oround);
|
|
volatile long double vzs = zs; /* XXX gcc CSE bug workaround */
|
|
return (xy.hi + vzs + ldexpl(xy.lo, spread));
|
|
}
|
|
|
|
if (oround != FE_TONEAREST) {
|
|
/*
|
|
* There is no need to worry about double rounding in directed
|
|
* rounding modes.
|
|
*/
|
|
fesetround(oround);
|
|
/* work around clang bug 8100 */
|
|
volatile long double vrlo = r.lo;
|
|
adj = vrlo + xy.lo;
|
|
return (ldexpl(r.hi + adj, spread));
|
|
}
|
|
|
|
adj = add_adjusted(r.lo, xy.lo);
|
|
if (spread + ilogbl(r.hi) > -16383)
|
|
return (ldexpl(r.hi + adj, spread));
|
|
else
|
|
return (add_and_denormalize(r.hi, adj, spread));
|
|
}
|