private static void TestUnaryOperationFloatApproximate(float x, float expected, UnaryOperationType op, float allowedErrorMultiplier = 1.0f) { UnaryOperation func = unaryOperations[(int)op]; sfloat result = func((sfloat)x); if (float.IsNaN(expected) && result.IsNaN()) { // special case, NaN-s cannot be compared return; } if (float.IsInfinity(expected) && result.IsInfinity() && MathF.Sign(expected) == result.Sign()) { // both are the same infinities return; } float allowedError = MathF.Max(1e-6f * allowedErrorMultiplier * MathF.Pow(2.0f, MathF.Log2(MathF.Abs(expected) + 1.0f)), 1e-6f); float difference = MathF.Abs((float)result - expected); bool isOk = difference <= allowedError; if (!isOk && (op == UnaryOperationType.Round || op == UnaryOperationType.Floor || op == UnaryOperationType.Ceiling)) { // Because of the loss of precision that can result from representing decimal values // as floating-point numbers or performing arithmetic operations on floating-point values, // in some cases the Round method may not appear to round midpoint values to the nearest even integer. // https://docs.microsoft.com/en-us/dotnet/api/system.math.round if (MathF.Abs(x % 1.0f) - 0.5f < 0.01f) { // x is near a midpoint, it's possible that rounding happened in a different direction isOk = MathF.Abs((float)result - expected) <= 1.0f; } } Debug.Assert(isOk); }
private static void TestBinaryOperationFloatApproximate(float a, float b, float expected, BinaryOperationType op) { BinaryOperation func = binaryOperations[(int)op]; sfloat result = func((sfloat)a, (sfloat)b); if (float.IsNaN(expected) && result.IsNaN()) { // special case, NaN-s cannot be compared return; } if (float.IsInfinity(expected) && result.IsInfinity() && MathF.Sign(expected) == result.Sign()) { // both are the same infinities return; } float allowedError = MathF.Max(1e-6f * MathF.Pow(2.0f, MathF.Log2(MathF.Abs(expected) + 1.0f)), 1e-6f); float difference = MathF.Abs((float)result - expected); bool isOk = difference < allowedError; Debug.Assert(isOk); }
/// <summary> /// Returns the signed angle between the positive x axis, and the direction (x, y) /// </summary> public static sfloat atan2f(sfloat y, sfloat x) { if (x.IsNaN() || y.IsNaN()) { return(x + y); } uint ix = x.RawValue; uint iy = y.RawValue; if (ix == 0x3f800000) { /* x=1.0 */ return(atanf(y)); } uint m = ((iy >> 31) & 1) | ((ix >> 30) & 2); /* 2*sign(x)+sign(y) */ ix &= 0x7fffffff; iy &= 0x7fffffff; const uint PI_LO_U32 = 0xb3bbbd2e; // -8.7422776573e-08 /* when y = 0 */ if (iy == 0) { switch (m) { case 0: case 1: return(y); /* atan(+-0,+anything)=+-0 */ case 2: return(sfloat.FromRaw(pi)); /* atan(+0,-anything) = pi */ case 3: default: return(-sfloat.FromRaw(pi)); /* atan(-0,-anything) =-pi */ } } /* when x = 0 */ if (ix == 0) { return((m & 1) != 0 ? -sfloat.FromRaw(half_pi) : sfloat.FromRaw(half_pi)); } /* when x is INF */ if (ix == 0x7f800000) { if (iy == 0x7f800000) { switch (m) { case 0: return(sfloat.FromRaw(pi_over_4)); /* atan(+INF,+INF) */ case 1: return(-sfloat.FromRaw(pi_over_4)); /* atan(-INF,+INF) */ case 2: return(sfloat.FromRaw(pi_times_3_over_4)); /* atan(+INF,-INF)*/ case 3: default: return(-sfloat.FromRaw(pi_times_3_over_4)); /* atan(-INF,-INF)*/ } } else { switch (m) { case 0: return(sfloat.Zero); /* atan(+...,+INF) */ case 1: return(-sfloat.Zero); /* atan(-...,+INF) */ case 2: return(sfloat.FromRaw(pi)); /* atan(+...,-INF) */ case 3: default: return(-sfloat.FromRaw(pi)); /* atan(-...,-INF) */ } } } /* |y/x| > 0x1p26 */ if (ix + (26 << 23) < iy || iy == 0x7f800000) { return((m & 1) != 0 ? -sfloat.FromRaw(half_pi) : sfloat.FromRaw(half_pi)); } /* z = atan(|y/x|) with correct underflow */ sfloat z = (m & 2) != 0 && iy + (26 << 23) < ix ? sfloat.Zero /*|y/x| < 0x1p-26, x < 0 */ : atanf(sfloat.Abs(y / x)); switch (m) { case 0: return(z); /* atan(+,+) */ case 1: return(-z); /* atan(-,+) */ case 2: return(sfloat.FromRaw(pi) - (z - sfloat.FromRaw(PI_LO_U32))); /* atan(+,-) */ case 3: default: return((z - sfloat.FromRaw(PI_LO_U32)) - sfloat.FromRaw(pi)); /* atan(-,-) */ } }
/// <summary> /// Returns the arctangent of x /// </summary> public unsafe static sfloat atanf(sfloat x) { sfloat z; uint ix = x.RawValue; bool sign = (ix >> 31) != 0; ix &= 0x7fffffff; if (ix >= 0x4c800000) { /* if |x| >= 2**26 */ if (x.IsNaN()) { return(x); } sfloat x1p_120 = sfloat.FromRaw(0x03800000); // 0x1p-120 === 2 ^ (-120) z = sfloat.FromRaw(ATAN_HI[3]) + x1p_120; return(sign ? -z : z); } int id; if (ix < 0x3ee00000) { /* |x| < 0.4375 */ if (ix < 0x39800000) { /* |x| < 2**-12 */ //if (ix < 0x00800000) //{ // /* raise underflow for subnormal x */ // force_eval!(x * x); //} return(x); } id = -1; } else { x = sfloat.Abs(x); if (ix < 0x3f980000) { /* |x| < 1.1875 */ if (ix < 0x3f300000) { /* 7/16 <= |x| < 11/16 */ x = ((sfloat)2.0f * x - (sfloat)1.0f) / ((sfloat)2.0f + x); id = 0; } else { /* 11/16 <= |x| < 19/16 */ x = (x - (sfloat)1.0f) / (x + (sfloat)1.0f); id = 1; } } else if (ix < 0x401c0000) { /* |x| < 2.4375 */ x = (x - (sfloat)1.5f) / ((sfloat)1.0f + (sfloat)1.5f * x); id = 2; } else { /* 2.4375 <= |x| < 2**26 */ x = (sfloat)(-1.0f) / x; id = 3; } }; /* end of argument reduction */ z = x * x; sfloat w = z * z; /* break sum from i=0 to 10 aT[i]z**(i+1) into odd and even poly */ sfloat s1 = z * (sfloat.FromRaw(A_T[0]) + w * (sfloat.FromRaw(A_T[2]) + w * sfloat.FromRaw(A_T[4]))); sfloat s2 = w * (sfloat.FromRaw(A_T[1]) + w * sfloat.FromRaw(A_T[3])); if (id < 0) { return(x - x * (s1 + s2)); } z = sfloat.FromRaw(ATAN_HI[id]) - ((x * (s1 + s2) - sfloat.FromRaw(ATAN_LO[id])) - x); return(sign ? -z : z); }
/// <summary> /// Returns the remainder and the quotient when dividing x by y, so that x == y * quotient + remainder /// </summary> public static void remquof(sfloat x, sfloat y, out sfloat remainder, out int quotient) { uint ux = x.RawValue; uint uy = y.RawValue; int ex = (int)((ux >> 23) & 0xff); int ey = (int)((uy >> 23) & 0xff); bool sx = (ux >> 31) != 0; bool sy = (uy >> 31) != 0; uint q; uint i; var uxi = ux; if ((uy << 1) == 0 || y.IsNaN() || ex == 0xff) { sfloat m = (x * y); remainder = m / m; quotient = 0; return; } if ((ux << 1) == 0) { remainder = x; quotient = 0; return; } /* normalize x and y */ if (ex == 0) { i = uxi << 9; while ((i >> 31) == 0) { ex -= 1; i <<= 1; } uxi <<= -ex + 1; } else { uxi &= (~0u) >> 9; uxi |= 1 << 23; } if (ey == 0) { i = uy << 9; while ((i >> 31) == 0) { ey -= 1; i <<= 1; } uy <<= -ey + 1; } else { uy &= (~0u) >> 9; uy |= 1 << 23; } q = 0; if (ex + 1 != ey) { if (ex < ey) { remainder = x; quotient = 0; return; } /* x mod y */ while (ex > ey) { i = uxi - uy; if ((i >> 31) == 0) { uxi = i; q += 1; } uxi <<= 1; q <<= 1; ex -= 1; } i = uxi - uy; if ((i >> 31) == 0) { uxi = i; q += 1; } if (uxi == 0) { ex = -30; } else { while ((uxi >> 23) == 0) { uxi <<= 1; ex -= 1; } } } /* scale result and decide between |x| and |x|-|y| */ if (ex > 0) { uxi -= 1 << 23; uxi |= ((uint)ex) << 23; } else { uxi >>= -ex + 1; } x = sfloat.FromRaw(uxi); if (sy) { y = -y; } if ((ex == ey || (ex + 1 == ey && ((sfloat)2.0f * x > y || ((sfloat)2.0f * x == y && (q % 2) != 0)))) && x > y) { x -= y; q += 1; } q &= 0x7fffffff; int quo = sx ^ sy ? -(int)q : (int)q; remainder = sx ? -x : x; quotient = quo; }
/// <summary> /// Returns the square root of x /// </summary> public static sfloat sqrtf(sfloat x) { int sign = unchecked ((int)0x80000000); int ix; int s; int q; int m; int t; int i; uint r; ix = (int)x.RawValue; /* take care of Inf and NaN */ if (((uint)ix & 0x7f800000) == 0x7f800000) { //return x * x + x; /* sqrt(NaN)=NaN, sqrt(+inf)=+inf, sqrt(-inf)=sNaN */ if (x.IsNaN() || x.IsNegativeInfinity()) { return(sfloat.NaN); } else // if (x.IsPositiveInfinity()) { return(sfloat.PositiveInfinity); } } /* take care of zero */ if (ix <= 0) { if ((ix & ~sign) == 0) { return(x); /* sqrt(+-0) = +-0 */ } if (ix < 0) { //return (x - x) / (x - x); /* sqrt(-ve) = sNaN */ return(sfloat.NaN); } } /* normalize x */ m = ix >> 23; if (m == 0) { /* subnormal x */ i = 0; while ((ix & 0x00800000) == 0) { ix <<= 1; i += 1; } m -= i - 1; } m -= 127; /* unbias exponent */ ix = (ix & 0x007fffff) | 0x00800000; if ((m & 1) == 1) { /* odd m, double x to make it even */ ix += ix; } m >>= 1; /* m = [m/2] */ /* generate sqrt(x) bit by bit */ ix += ix; q = 0; s = 0; r = 0x01000000; /* r = moving bit from right to left */ while (r != 0) { t = s + (int)r; if (t <= ix) { s = t + (int)r; ix -= t; q += (int)r; } ix += ix; r >>= 1; } /* use floating add to find out rounding direction */ if (ix != 0) { q += q & 1; } ix = (q >> 1) + 0x3f000000; ix += m << 23; return(sfloat.FromRaw((uint)ix)); }
/// <summary> /// Returns x modulo y /// </summary> public static sfloat fmodf(sfloat x, sfloat y) { uint uxi = x.RawValue; uint uyi = y.RawValue; int ex = (int)(uxi >> 23 & 0xff); int ey = (int)(uyi >> 23 & 0xff); uint sx = uxi & 0x80000000; uint i; if (uyi << 1 == 0 || y.IsNaN() || ex == 0xff) { return((x * y) / (x * y)); } if (uxi << 1 <= uyi << 1) { if (uxi << 1 == uyi << 1) { //return 0.0 * x; return(sfloat.Zero); } return(x); } /* normalize x and y */ if (ex == 0) { i = uxi << 9; while (i >> 31 == 0) { ex -= 1; i <<= 1; } uxi <<= -ex + 1; } else { uxi &= uint.MaxValue >> 9; uxi |= 1 << 23; } if (ey == 0) { i = uyi << 9; while (i >> 31 == 0) { ey -= 1; i <<= 1; } uyi <<= -ey + 1; } else { uyi &= uint.MaxValue >> 9; uyi |= 1 << 23; } /* x mod y */ while (ex > ey) { i = uxi - uyi; if (i >> 31 == 0) { if (i == 0) { //return 0.0 * x; return(sfloat.Zero); } uxi = i; } uxi <<= 1; ex -= 1; } i = uxi - uyi; if (i >> 31 == 0) { if (i == 0) { //return 0.0 * x; return(sfloat.Zero); } uxi = i; } while (uxi >> 23 == 0) { uxi <<= 1; ex -= 1; } /* scale result up */ if (ex > 0) { uxi -= 1 << 23; uxi |= ((uint)ex) << 23; } else { uxi >>= -ex + 1; } uxi |= sx; return(sfloat.FromRaw(uxi)); }