// This function is not well-tested. It may be wildly inaccurate. /// <summary> /// Returns the tangent of the specified angle. /// </summary> /// <param name="x">An angle, measured in radians.</param> /// <returns></returns> public static Fix64 Tan(Fix64 x) { var clampedPi = x.RawValue % _PI; var flip = false; if (clampedPi < 0) { clampedPi = -clampedPi; flip = true; } if (clampedPi > PI_OVER_2) { flip = !flip; clampedPi = PI_OVER_2 - (clampedPi - PI_OVER_2); } var clamped = new Fix64(clampedPi); // Find the two closest values in the LUT and perform linear interpolation var rawIndex = Fix64.FastMultiply(clamped, LutInterval); var roundedIndex = Round(rawIndex); var indexError = Fix64.FastSubtract(rawIndex, roundedIndex); var nearestValue = new Fix64(TanLut[(int)roundedIndex]); var secondNearestValue = new Fix64(TanLut[(int)roundedIndex + Sign(indexError)]); var delta = Fix64.FastMultiply(indexError, FastAbs(Fix64.FastSubtract(nearestValue, secondNearestValue))).RawValue; var interpolatedValue = nearestValue.RawValue + delta; var finalValue = flip ? -interpolatedValue : interpolatedValue; return(new Fix64(finalValue)); }
// Provides at least 6 decimals of accuracy. /// <summary> /// Returns 2 raised to the specified power. /// </summary> /// <param name="x">A number specifying a power.</param> /// <returns></returns> public static Fix64 Pow2(Fix64 x) { if (x.RawValue == 0) { return(Fix64.One); } // Avoid negative arguments by exploiting that exp(-x) = 1/exp(x). bool neg = x.RawValue < 0; if (neg) { x = -x; } if (x == Fix64.One) { return(neg ? Fix64.One / (Fix64)2 : (Fix64)2); } if (x >= Log2Max) { return(neg ? Fix64.One / Fix64.MaxValue : Fix64.MaxValue); } if (x <= Log2Min) { return(neg ? Fix64.MaxValue : Fix64.Zero); } /* The algorithm is based on the power series for exp(x): * http://en.wikipedia.org/wiki/Exponential_function#Formal_definition * * From term n, we get term n+1 by multiplying with x/n. * When the sum term drops to zero, we can stop summing. */ int integerPart = (int)Floor(x); // Take fractional part of exponent x = new Fix64(x.RawValue & 0x00000000FFFFFFFF); var result = Fix64.One; var term = Fix64.One; int i = 1; while (term.RawValue != 0) { term = Fix64.FastMultiply(Fix64.FastMultiply(x, term), E) / (Fix64)i; result += term; i++; } result = Fix64.FromRaw(result.RawValue << integerPart); if (neg) { result = Fix64.One / result; } return(result); }
// Provides at least 9 decimals of accuracy. /// <summary> /// Returns the base 2 logarithm of a specified number. /// </summary> /// <param name="x">A number whose logarithm is to be found.</param> /// <returns></returns> public static Fix64 Log2(Fix64 x) { if (x.RawValue <= 0) { throw new ArgumentOutOfRangeException("Non-positive value passed to Ln", "x"); } // This implementation is based on Clay. S. Turner's fast binary logarithm // algorithm (C. S. Turner, "A Fast Binary Logarithm Algorithm", IEEE Signal // Processing Mag., pp. 124,140, Sep. 2010.) long b = 1U << (Fix64.FRACTIONAL_PLACES - 1); long y = 0; long rawX = x.RawValue; while (rawX < Fix64.ONE) { rawX <<= 1; y -= Fix64.ONE; } while (rawX >= (Fix64.ONE << 1)) { rawX >>= 1; y += Fix64.ONE; } var z = new Fix64(rawX); for (int i = 0; i < Fix64.FRACTIONAL_PLACES; i++) { z = Fix64.FastMultiply(z, z); if (z.RawValue >= (Fix64.ONE << 1)) { z = new Fix64(z.RawValue >> 1); y += b; } b >>= 1; } return(new Fix64(y)); }
// The relative error is less than 1E-10 for x in [-2PI, 2PI], and less than 1E-7 in the worst case. /// <summary> /// Returns the sine of the specified angle. /// </summary> /// <param name="x">An angle, measured in radians.</param> /// <returns></returns> public static Fix64 Sin(Fix64 x) { var clampedL = ClampSinValue(x.RawValue, out var flipHorizontal, out var flipVertical); var clamped = new Fix64(clampedL); // Find the two closest values in the LUT and perform linear interpolation // This is what kills the performance of this function on x86 - x64 is fine though var rawIndex = Fix64.FastMultiply(clamped, LutInterval); var roundedIndex = Round(rawIndex); var indexError = Fix64.FastSubtract(rawIndex, roundedIndex); var nearestValue = new Fix64(SinLut[flipHorizontal ? SinLut.Length - 1 - (int)roundedIndex : (int)roundedIndex]); var secondNearestValue = new Fix64(SinLut[flipHorizontal ? SinLut.Length - 1 - (int)roundedIndex - Sign(indexError) : (int)roundedIndex + Sign(indexError)]); var delta = Fix64.FastMultiply(indexError, FastAbs(Fix64.FastSubtract(nearestValue, secondNearestValue))).RawValue; var interpolatedValue = nearestValue.RawValue + (flipHorizontal ? -delta : delta); var finalValue = flipVertical ? -interpolatedValue : interpolatedValue; return(new Fix64(finalValue)); }
// Provides at least 7 decimals of accuracy. /// <summary> /// Returns the natural (base e) logarithm of a specified number. /// </summary> /// <param name="x">The number whose logarithm is to be found.</param> /// <returns></returns> public static Fix64 Log(Fix64 x) { return(Fix64.FastMultiply(Log2(x), E)); }