< Summary

Line coverage
100%
Covered lines: 448
Uncovered lines: 0
Coverable lines: 448
Total lines: 1295
Line coverage: 100%
Branch coverage
99%
Covered branches: 253
Total branches: 254
Branch coverage: 99.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: .cctor()100%11100%
File 1: CopySign(...)100%22100%
File 1: Clamp01(...)100%44100%
File 1: Clamp(...)100%44100%
File 1: Clamp(...)100%44100%
File 1: ClampOne(...)100%44100%
File 1: Abs(...)100%22100%
File 1: Ceil(...)100%22100%
File 1: Floor(...)100%11100%
File 1: Max(...)100%22100%
File 1: Min(...)100%22100%
File 1: Round(...)100%1010100%
File 1: RoundToPrecision(...)100%44100%
File 1: Squared(...)100%11100%
File 1: SmoothStep(...)100%44100%
File 1: CubicInterpolate(...)100%11100%
File 1: Lerp(...)100%44100%
File 1: CatmullRom(...)100%11100%
File 1: HermiteSpline(...)100%44100%
File 1: BarycentricCoordinate(...)100%11100%
File 1: SumSquaredBarycentricProducts(...)100%11100%
File 1: SumBarycentricProducts(...)100%11100%
File 1: FastAdd(...)100%11100%
File 1: FastSub(...)100%11100%
File 1: FastMul(...)100%44100%
File 1: FastDiv(...)100%2222100%
File 1: FastMod(...)100%11100%
File 1: MoveTowards(...)100%88100%
File 1: AddOverflowHelper(...)75%44100%
File 2: .cctor()100%11100%
File 2: get_Pow10Lookup()100%11100%
File 2: Pow(...)100%88100%
File 2: Pow2(...)100%1414100%
File 2: Log2(...)100%1010100%
File 2: Ln(...)100%22100%
File 2: Pow2Fractional(...)100%44100%
File 2: ShiftRightRounded(...)100%22100%
File 2: FloorLog2(...)100%1212100%
File 2: Sqrt(...)100%1818100%
File 2: RadToDeg(...)100%11100%
File 2: DegToRad(...)100%11100%
File 2: Sin(...)100%2424100%
File 2: Cos(...)100%22100%
File 2: GetHypotenuse(...)100%11100%
File 2: SinToCos(...)100%11100%
File 2: Tan(...)100%1414100%
File 2: SinReduced(...)100%11100%
File 2: Asin(...)100%1616100%
File 2: Acos(...)100%1010100%
File 2: Atan(...)100%1818100%
File 2: Atan2(...)100%1010100%

File(s)

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Core/FixedMath.cs

#LineLine coverage
 1//=======================================================================
 2// FixedMath.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024–present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using System.Runtime.CompilerServices;
 10
 11namespace FixedMathSharp
 12{
 13    /// <summary>
 14    /// A static class that provides a variety of fixed-point math functions.
 15    /// Fixed-point numbers are represented as <see cref="Fixed64"/>.
 16    /// </summary>
 17    public static partial class FixedMath
 18    {
 19        #region Fields and Constants
 20
 21        /// <summary>
 22        /// Represents the number of bits to shift for fixed-point representation.
 23        /// </summary>
 24        public const int SHIFT_AMOUNT_I = 32;
 25        /// <summary>
 26        /// Represents the maximum value that can be produced by left-shifting 1 by SHIFT_AMOUNT_I bits and subtracting 
 27        /// </summary>
 28        /// <remarks>
 29        /// This constant is typically used as a bitmask to extract or limit values to the range
 30        /// defined by SHIFT_AMOUNT_I.
 31        /// The value is always non-negative and fits within a 32-bit unsigned
 32        /// integer.
 33        /// </remarks>
 34        public const uint MAX_SHIFTED_AMOUNT_UI = (uint)((1L << SHIFT_AMOUNT_I) - 1);
 35        /// <summary>
 36        /// Represents a bitmask with all bits set except for the lowest SHIFT_AMOUNT_I bits.
 37        /// </summary>
 38        /// <remarks>
 39        /// This constant is typically used to isolate or clear the lower SHIFT_AMOUNT_I bits of
 40        /// an unsigned 64-bit value.
 41        /// The value of SHIFT_AMOUNT_I determines how many least significant bits are masked out.
 42        /// </remarks>
 43        public const ulong MASK_UL = (ulong)(ulong.MaxValue << SHIFT_AMOUNT_I);
 44
 45        /// <summary>
 46        /// Represents the largest possible value for a 64-bit fixed-point number.
 47        /// </summary>
 48        /// <remarks>
 49        /// Use this constant to perform comparisons or to initialize variables that require the
 50        /// maximum representable value for a 64-bit fixed-point type.
 51        /// </remarks>
 52        public const long MAX_VALUE_L = long.MaxValue;
 53        /// <summary>
 54        /// Represents the smallest possible value for a 64-bit fixed-point number.
 55        /// </summary>
 56        /// <remarks>
 57        /// Use this constant to check for underflow conditions or to initialize variables that
 58        /// require the minimum representable value for a 64-bit fixed-point type.
 59        /// </remarks>
 60        public const long MIN_VALUE_L = long.MinValue;
 61
 62        /// <summary>
 63        /// Represents the value 1 shifted left by the number of bits specified by SHIFT_AMOUNT_I.
 64        /// </summary>
 65        public const long ONE_L = 1L << SHIFT_AMOUNT_I;
 66
 67        internal const double MIN_RAW_D = -9223372036854775808d;
 68        internal const double MAX_RAW_EXCLUSIVE_D = 9223372036854775808d;
 69
 70        // Precomputed scale factors only for performance-critical scenarios to avoid division at runtime
 71
 72        /// <summary>
 73        /// Represents the precomputed scale factor used for floating-point calculations.
 74        /// </summary>
 75        /// <remarks>
 76        /// This constant is intended only for converting fixed-point values to floating-point representations in perfor
 77        /// </remarks>
 78        public const float SCALE_FACTOR_F = 1.0f / ONE_L;
 79        /// <summary>
 80        /// Represents the precomputed scale factor used for double-precision calculations.
 81        /// </summary>
 82        /// <remarks>
 83        /// This constant is intended only for converting fixed-point values to double-precision representations in perf
 84        /// </remarks>
 85        public const double SCALE_FACTOR_D = 1.0 / ONE_L;
 86        /// <summary>
 87        /// Represents the precomputed scale factor used for decimal calculations.
 88        /// </summary>
 89        /// <remarks>
 90        /// This constant is intended only for converting fixed-point values to decimal representations in performance-c
 91        /// </remarks>
 192        public const decimal SCALE_FACTOR_M = 1.0m / ONE_L;
 93
 94        /// <summary>
 95        /// The smallest non-zero raw increment representable by Fixed64.
 96        /// </summary>
 97        public const long MIN_INCREMENT_L = 1L;
 98
 99        /// <summary>
 100        /// Default tolerance for fuzzy comparisons.
 101        /// Approximately 2^-24 (~5.96e-8) in value space.
 102        /// </summary>
 103        public const long DEFAULT_TOLERANCE_L = 1L << (SHIFT_AMOUNT_I - 24);
 104
 105
 106        #endregion
 107
 108        #region FixedMath Operations
 109
 110        /// <summary>
 111        /// Produces a value with the magnitude of the first argument and the sign of the second argument.
 112        /// </summary>
 113        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 114        public static Fixed64 CopySign(Fixed64 x, Fixed64 y) =>
 6115            y >= Fixed64.Zero ? x.Abs() : -x.Abs();
 116
 117        /// <summary>
 118        /// Clamps value between 0 and 1 and returns value.
 119        /// </summary>
 120        /// <param name="value"></param>
 121        /// <returns></returns>
 122        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 123        public static Fixed64 Clamp01(Fixed64 value) =>
 24124            value < Fixed64.Zero ? Fixed64.Zero : value > Fixed64.One ? Fixed64.One : value;
 125
 126        /// <summary>
 127        /// Clamps a fixed-point value between the given minimum and maximum values.
 128        /// </summary>
 129        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 130        public static Fixed64 Clamp(Fixed64 f1, Fixed64 min, Fixed64 max) =>
 298131            f1 < min ? min : f1 > max ? max : f1;
 132
 133        /// <summary>
 134        /// Clamps a value to the inclusive range [min, max].
 135        /// </summary>
 136        /// <typeparam name="T">The type of the value, must implement <see cref="IComparable{T}"/>.</typeparam>
 137        /// <param name="value">The value to clamp.</param>
 138        /// <param name="min">The minimum allowed value.</param>
 139        /// <param name="max">The maximum allowed value.</param>
 140        /// <returns>The clamped value.</returns>
 141        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 142        public static T Clamp<T>(T value, T min, T max) where T : IComparable<T>
 143        {
 4144            if (value.CompareTo(max) > 0) return max;
 3145            if (value.CompareTo(min) < 0) return min;
 1146            return value;
 147        }
 148
 149        /// <summary>
 150        /// Clamps the value between -1 and 1 inclusive.
 151        /// </summary>
 152        /// <param name="f1">The Fixed64 value to clamp.</param>
 153        /// <returns>Returns a value clamped between -1 and 1.</returns>
 154        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 155        public static Fixed64 ClampOne(Fixed64 f1) =>
 5156             f1 > Fixed64.One ? Fixed64.One : f1 < -Fixed64.One ? -Fixed64.One : f1;
 157
 158        /// <summary>
 159        /// Returns the absolute value of a Fixed64 number.
 160        /// </summary>
 161        public static Fixed64 Abs(Fixed64 value)
 162        {
 163            // For the minimum value, return the max to avoid overflow
 4900164            if (value.m_rawValue == MIN_VALUE_L)
 1165                return new Fixed64(MAX_VALUE_L);
 166
 167            // Use branchless absolute value calculation
 4899168            long mask = value.m_rawValue >> 63; // If negative, mask will be all 1s; if positive, all 0s
 4899169            return Fixed64.FromRaw((value.m_rawValue + mask) ^ mask);
 170        }
 171
 172        /// <summary>
 173        /// Returns the smallest integral value that is greater than or equal to the specified number.
 174        /// </summary>
 175        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 176        public static Fixed64 Ceil(Fixed64 value)
 177        {
 14178            bool hasFractionalPart = (value.m_rawValue & MAX_SHIFTED_AMOUNT_UI) != 0;
 14179            return hasFractionalPart ? value.Floor() + Fixed64.One : value;
 180        }
 181
 182        /// <summary>
 183        /// Returns the largest integer less than or equal to the specified number (floor function).
 184        /// Efficiently zeroes out the fractional part.
 185        /// </summary>
 186        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 187        public static Fixed64 Floor(Fixed64 value) =>
 62188            Fixed64.FromRaw((long)((ulong)value.m_rawValue & MASK_UL));
 189
 190        /// <summary>
 191        /// Returns the larger of two fixed-point values.
 192        /// </summary>
 193        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1493194        public static Fixed64 Max(Fixed64 a, Fixed64 b) => a > b ? a : b;
 195
 196        /// <summary>
 197        /// Returns the smaller of two fixed-point values.
 198        /// </summary>
 199        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1526200        public static Fixed64 Min(Fixed64 a, Fixed64 b) => a < b ? a : b;
 201
 202        /// <summary>
 203        /// Rounds a fixed-point number to the nearest integral value, based on the specified rounding mode.
 204        /// </summary>
 205        public static Fixed64 Round(Fixed64 value, MidpointRounding mode = MidpointRounding.ToEven)
 206        {
 30207            long fractionalPart = value.m_rawValue & MAX_SHIFTED_AMOUNT_UI;
 30208            Fixed64 integralPart = value.Floor();
 30209            if (fractionalPart < Fixed64.Half.m_rawValue)
 7210                return integralPart;
 211
 23212            if (fractionalPart > Fixed64.Half.m_rawValue)
 11213                return integralPart + Fixed64.One;
 214
 215            // When value is exactly Fixed64.Halfway between two numbers
 12216            return mode switch
 12217            {
 12218                // For negative midpoints, Floor() is already away from zero
 3219                MidpointRounding.AwayFromZero => value.m_rawValue > 0 ? integralPart + Fixed64.One : integralPart,
 12220                // Rounds to the nearest even number (default behavior)
 9221                _ => (integralPart.m_rawValue & ONE_L) == 0 ? integralPart : integralPart + Fixed64.One,
 12222            };
 223        }
 224
 225        /// <summary>
 226        /// Rounds a fixed-point number to a specific number of decimal places.
 227        /// </summary>
 228        public static Fixed64 RoundToPrecision(Fixed64 value, int decimalPlaces, MidpointRounding mode = MidpointRoundin
 229        {
 7230            if (decimalPlaces < 0 || decimalPlaces >= Pow10Lookup.Length)
 2231                throw new ArgumentOutOfRangeException(nameof(decimalPlaces), "Decimal places out of range.");
 232
 5233            int factor = Pow10Lookup[decimalPlaces];
 5234            Fixed64 scaled = value * factor;
 5235            long rounded = Round(scaled, mode).m_rawValue;
 5236            return new Fixed64(rounded + (factor / 2)) / factor;
 237        }
 238
 239        /// <summary>
 240        /// Squares the Fixed64 value.
 241        /// </summary>
 242        /// <param name="value">The Fixed64 value to square.</param>
 243        /// <returns>The squared value.</returns>
 244        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2245        public static Fixed64 Squared(Fixed64 value) => value * value;
 246
 247        /// <summary>
 248        /// Performs a smooth step interpolation using a cubic Hermite curve between two values.
 249        /// </summary>
 250        /// <remarks>
 251        /// The interpolation follows a cubic Hermite curve where the function starts at <paramref name="a"/>,
 252        /// accelerates, and then decelerates towards <paramref name="b"/>, ensuring smooth transitions.
 253        /// </remarks>
 254        /// <param name="a">The starting value.</param>
 255        /// <param name="b">The ending value.</param>
 256        /// <param name="t">A value between 0 and 1 that represents the interpolation factor.</param>
 257        /// <returns>The interpolated value between <paramref name="a"/> and <paramref name="b"/>.</returns>
 258        public static Fixed64 SmoothStep(Fixed64 a, Fixed64 b, Fixed64 t)
 259        {
 9260            if (t.m_rawValue <= 0)
 1261                return a;
 8262            if (t.m_rawValue >= ONE_L)
 1263                return b;
 264
 7265            Fixed64 t2 = t * t;
 7266            Fixed64 t3 = t2 * t;
 7267            return a + (b - a) * (Fixed64.Three * t2 - Fixed64.Two * t3);
 268        }
 269
 270        /// <summary>
 271        /// Performs cubic interpolation between two points with tangents at those points.
 272        /// </summary>
 273        /// <param name="p0">The first point.</param>
 274        /// <param name="p1">The second point.</param>
 275        /// <param name="m0">The tangent at <paramref name="p0"/>.</param>
 276        /// <param name="m1">The tangent at <paramref name="p1"/>.</param>
 277        /// <param name="t">A value between 0 and 1 that represents the interpolation factor.</param>
 278        /// <returns>The interpolated value between <paramref name="p0"/> and <paramref name="p1"/>.</returns>
 279        public static Fixed64 CubicInterpolate(Fixed64 p0, Fixed64 p1, Fixed64 m0, Fixed64 m1, Fixed64 t)
 280        {
 5281            Fixed64 t2 = t * t;
 5282            Fixed64 t3 = t2 * t;
 5283            return (Fixed64.Two * p0 - Fixed64.Two * p1 + m0 + m1) * t3
 5284                 + (-Fixed64.Three * p0 + Fixed64.Three * p1 - Fixed64.Two * m0 - m1) * t2
 5285                 + m0 * t + p0;
 286        }
 287
 288        /// <summary>
 289        /// Linearly interpolates between two fixed-point values based on a given interpolation factor.
 290        /// </summary>
 291        /// <param name="from">The starting value.</param>
 292        /// <param name="to">The ending value.</param>
 293        /// <param name="t">A value between 0 and 1 that represents the interpolation factor.</param>
 294        /// <returns>The interpolated value between <paramref name="from"/> and <paramref name="to"/>.</returns>
 295        /// <remarks>
 296        /// The interpolation is clamped between <paramref name="from"/> and <paramref name="to"/> based on the value of
 297        /// If <paramref name="t"/> is less than 0, the result is <paramref name="from"/>. If <paramref name="t"/> is gr
 298        /// </remarks>
 299        public static Fixed64 Lerp(Fixed64 from, Fixed64 to, Fixed64 t)
 300        {
 98301            if (t.m_rawValue >= ONE_L)
 4302                return to;
 94303            if (t.m_rawValue <= 0)
 5304                return from;
 305
 89306            return from + (to - from) * t;
 307        }
 308
 309        /// <summary>
 310        /// Computes the interpolated point along a Catmull-Rom spline given four control points.
 311        /// </summary>
 312        /// <param name="p0">The first control point.</param>
 313        /// <param name="p1">The second control point.</param>
 314        /// <param name="p2">The third control point.</param>
 315        /// <param name="p3">The fourth control point.</param>
 316        /// <param name="t">Interpolation factor between 0 and 1.</param>
 317        /// <returns>The interpolated point on the spline.</returns>
 318        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 319        public static Fixed64 CatmullRom(Fixed64 p0, Fixed64 p1, Fixed64 p2, Fixed64 p3, Fixed64 t)
 320        {
 4321            Fixed64 t2 = t * t;
 4322            Fixed64 t3 = t2 * t;
 4323            return ((-t3 + 2 * t2 - t) * p0 +
 4324                 (3 * t3 - 5 * t2 + 2) * p1 +
 4325                 (-3 * t3 + 4 * t2 + t) * p2 +
 4326                 (t3 - t2) * p3) / 2;
 327        }
 328
 329        /// <summary>
 330        /// Performs a Hermite interpolation between two Fixed64 values, using the specified tangents and interpolation 
 331        /// </summary>
 332        /// <param name="value1">The first value.</param>
 333        /// <param name="tangent1">The tangent at the first value.</param>
 334        /// <param name="value2">The second value.</param>
 335        /// <param name="tangent2">The tangent at the second value.</param>
 336        /// <param name="amount">The interpolation amount.</param>
 337        /// <returns>The Hermite spline interpolated value.</returns>
 338        public static Fixed64 HermiteSpline(
 339            Fixed64 value1,
 340            Fixed64 tangent1,
 341            Fixed64 value2,
 342            Fixed64 tangent2,
 343            Fixed64 amount)
 344        {
 6345            if ((amount - Fixed64.Zero).LessThanEpsilon())
 1346                return value1;
 347
 5348            if ((amount - Fixed64.One).LessThanEpsilon())
 1349                return value2;
 350
 20351            Fixed64 v1 = value1, v2 = value2, t1 = tangent1, t2 = tangent2, s = amount;
 4352            Fixed64 sCubed = s * s * s;
 4353            Fixed64 sSquared = s * s;
 4354            Fixed64 result = (
 4355                ((2 * v1 - 2 * v2 + t2 + t1) * sCubed) +
 4356                ((3 * v2 - 3 * v1 - 2 * t1 - t2) * sSquared) +
 4357                (t1 * s) +
 4358                v1
 4359            );
 360
 4361            return result;
 362        }
 363
 364        /// <summary>
 365        /// Performs barycentric interpolation between three scalar coordinates from a triangle.
 366        /// </summary>
 367        /// <param name="coordA">The coordinate of the first vertex.</param>
 368        /// <param name="coordB">The coordinate of the second vertex.</param>
 369        /// <param name="coordC">The coordinate of the third vertex.</param>
 370        /// <param name="weightB">The barycentric weight for the second vertex.</param>
 371        /// <param name="weightC">The barycentric weight for the third vertex.</param>
 372        /// <returns>The interpolated scalar coordinate.</returns>
 373        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 374        public static Fixed64 BarycentricCoordinate(
 375            Fixed64 coordA,
 376            Fixed64 coordB,
 377            Fixed64 coordC,
 378            Fixed64 weightB,
 379            Fixed64 weightC
 51380        ) => coordA + (coordB - coordA) * weightB + (coordC - coordA) * weightC;
 381
 382        /// <summary>
 383        /// Returns the second-order scalar product sum for three barycentric vertices.
 384        /// </summary>
 385        /// <remarks>
 386        /// Computes <c>a * a + b * b + c * c + a * b + a * c + b * c</c>.
 387        /// </remarks>
 388        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 389        public static Fixed64 SumSquaredBarycentricProducts(Fixed64 a, Fixed64 b, Fixed64 c) =>
 4390            (a * a) + (b * b) + (c * c) + (a * b) + (a * c) + (b * c);
 391
 392        /// <summary>
 393        /// Returns the cross scalar product sum for two sets of three barycentric vertices.
 394        /// </summary>
 395        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 396        public static Fixed64 SumBarycentricProducts(
 397            Fixed64 firstA,
 398            Fixed64 firstB,
 399            Fixed64 firstC,
 400            Fixed64 secondA,
 401            Fixed64 secondB,
 402            Fixed64 secondC)
 403        {
 4404            Fixed64 firstSum = firstA + firstB + firstC;
 4405            Fixed64 secondSum = secondA + secondB + secondC;
 4406            Fixed64 matchingProducts = (firstA * secondA) + (firstB * secondB) + (firstC * secondC);
 4407            return firstSum * secondSum + matchingProducts;
 408        }
 409
 410        /// <summary>
 411        /// Adds two fixed-point numbers by adding their raw Q32.32 payloads without saturation.
 412        /// </summary>
 413        /// <remarks>
 414        /// This is an unchecked hot-path helper. Use it only when the raw sum is known to fit in
 415        /// <see cref="long"/> or raw wraparound is an intentional part of the algorithm. Use
 416        /// <see cref="Fixed64.op_Addition(Fixed64, Fixed64)"/> for the public saturating add contract.
 417        /// </remarks>
 418        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 5419        public static Fixed64 FastAdd(Fixed64 x, Fixed64 y) => Fixed64.FromRaw(x.m_rawValue + y.m_rawValue);
 420
 421        /// <summary>
 422        /// Subtracts two fixed-point numbers by subtracting their raw Q32.32 payloads without saturation.
 423        /// </summary>
 424        /// <remarks>
 425        /// This is an unchecked hot-path helper. Use it only when the raw difference is known to fit in
 426        /// <see cref="long"/> or raw wraparound is an intentional part of the algorithm. Use
 427        /// <see cref="Fixed64.op_Subtraction(Fixed64, Fixed64)"/> for the public saturating subtract contract.
 428        /// </remarks>
 429        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 5430        public static Fixed64 FastSub(Fixed64 x, Fixed64 y) => Fixed64.FromRaw(x.m_rawValue - y.m_rawValue);
 431
 432        /// <summary>
 433        /// Multiplies two fixed-point numbers using unchecked Q32.32 partial products.
 434        /// </summary>
 435        /// <remarks>
 436        /// This is an unchecked hot-path helper. It skips the full-width overflow/saturation path used by
 437        /// <see cref="Fixed64.op_Multiply(Fixed64, Fixed64)"/> and truncates discarded fractional bits instead
 438        /// of applying the operator's round-half-to-even behavior. Use it only when inputs are constrained and
 439        /// that precision tradeoff is acceptable. Integral operands take a direct raw multiplication path.
 440        /// </remarks>
 441        public static Fixed64 FastMul(Fixed64 x, Fixed64 y)
 442        {
 1968443            long xl = x.m_rawValue;
 1968444            long yl = y.m_rawValue;
 445
 1968446            if ((xl & MAX_SHIFTED_AMOUNT_UI) == 0)
 1590447                return Fixed64.FromRaw((xl >> SHIFT_AMOUNT_I) * yl);
 448
 378449            if ((yl & MAX_SHIFTED_AMOUNT_UI) == 0)
 1450                return Fixed64.FromRaw((yl >> SHIFT_AMOUNT_I) * xl);
 451
 452            // Split values into high and low bits for long multiplication
 377453            ulong xlo = (ulong)(xl & MAX_SHIFTED_AMOUNT_UI);
 377454            long xhi = xl >> SHIFT_AMOUNT_I;
 377455            ulong ylo = (ulong)(yl & MAX_SHIFTED_AMOUNT_UI);
 377456            long yhi = yl >> SHIFT_AMOUNT_I;
 457
 458            // Perform partial products
 377459            ulong lolo = xlo * ylo;
 377460            long lohi = (long)xlo * yhi;
 377461            long hilo = xhi * (long)ylo;
 377462            long hihi = xhi * yhi;
 463
 464            // Combine the results
 377465            ulong loResult = lolo >> SHIFT_AMOUNT_I;
 377466            long midResult1 = lohi;
 377467            long midResult2 = hilo;
 377468            long hiResult = hihi << SHIFT_AMOUNT_I;
 469
 377470            long sum = (long)loResult + midResult1 + midResult2 + hiResult;
 377471            return Fixed64.FromRaw(sum);
 472        }
 473
 474        /// <summary>
 475        /// Divides two fixed-point numbers with an optimized path for known-positive divisors.
 476        /// </summary>
 477        /// <remarks>
 478        /// This helper preserves the same deterministic rounding, divide-by-zero, and saturation semantics
 479        /// as <see cref="Fixed64.op_Division(Fixed64, Fixed64)"/>. The fast path is only used when
 480        /// <paramref name="y"/> is positive; non-positive divisors fall back to the guarded division operator.
 481        /// Prefer the operator unless the divisor positivity invariant is already proven by the caller.
 482        /// </remarks>
 483        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 484        public static Fixed64 FastDiv(Fixed64 x, Fixed64 y)
 485        {
 308486            long xl = x.m_rawValue;
 308487            long yl = y.m_rawValue;
 488
 308489            if (yl <= 0)
 2490                return x / y;
 491
 306492            ulong remainder = (ulong)(xl < 0 ? -xl : xl);
 306493            ulong divider = (ulong)yl;
 306494            ulong quotient = 0UL;
 306495            int bitPos = SHIFT_AMOUNT_I + 1;
 496
 1779497            while ((divider & 0xF) == 0 && bitPos >= 4)
 498            {
 1473499                divider >>= 4;
 1473500                bitPos -= 4;
 501            }
 502
 643503            while (remainder != 0 && bitPos >= 0)
 504            {
 339505                int shift = Fixed64.CountLeadingZeroes(remainder);
 339506                if (shift > bitPos)
 237507                    shift = bitPos;
 508
 339509                remainder <<= shift;
 339510                bitPos -= shift;
 511
 339512                ulong div = remainder / divider;
 339513                remainder %= divider;
 339514                quotient += div << bitPos;
 515
 339516                if ((div & ~(0xFFFFFFFFFFFFFFFF >> bitPos)) != 0)
 2517                    return xl >= 0
 2518                        ? new Fixed64(MAX_VALUE_L)
 2519                        : new Fixed64(MIN_VALUE_L);
 520
 337521                remainder <<= 1;
 337522                --bitPos;
 523            }
 524
 304525            if ((quotient & 0x1) != 0)
 112526                quotient += 1;
 527
 304528            long result = (long)(quotient >> 1);
 304529            if (xl < 0)
 45530                result = -result;
 531
 304532            return Fixed64.FromRaw(result);
 533        }
 534
 535        /// <summary>
 536        /// Computes the raw remainder of two fixed-point numbers without special-case guards.
 537        /// </summary>
 538        /// <remarks>
 539        /// This is an unchecked hot-path helper. It delegates directly to the raw <see cref="long"/> remainder
 540        /// operation, so raw zero divisors and integer edge cases follow runtime integer remainder behavior.
 541        /// Use <see cref="Fixed64.op_Modulus(Fixed64, Fixed64)"/> for the public guarded remainder contract.
 542        /// </remarks>
 543        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 5544        public static Fixed64 FastMod(Fixed64 x, Fixed64 y) => Fixed64.FromRaw(x.m_rawValue % y.m_rawValue);
 545
 546        /// <summary>
 547        /// Moves a value from 'from' to 'to' by a maximum step of 'maxAmount'.
 548        /// Ensures the value does not exceed 'to'.
 549        /// </summary>
 550        public static Fixed64 MoveTowards(Fixed64 from, Fixed64 to, Fixed64 maxAmount)
 551        {
 5552            if (from < to)
 553            {
 2554                from += maxAmount;
 2555                if (from > to)
 1556                    from = to;
 557            }
 3558            else if (from > to)
 559            {
 2560                from -= maxAmount;
 2561                if (from < to)
 1562                    from = to;
 563            }
 564
 5565            return Fixed64.FromRaw(from.m_rawValue);
 566        }
 567
 568        /// <summary>
 569        /// Adds two <see cref="long"/> values and checks for overflow.
 570        /// If an overflow occurs during addition, the <paramref name="overflow"/> parameter is set to true.
 571        /// </summary>
 572        /// <param name="x">The first operand to add.</param>
 573        /// <param name="y">The second operand to add.</param>
 574        /// <param name="overflow">
 575        /// A reference parameter that is set to true if an overflow is detected during the addition.
 576        /// The existing value of <paramref name="overflow"/> is preserved if already true.
 577        /// </param>
 578        /// <returns>The sum of <paramref name="x"/> and <paramref name="y"/>.</returns>
 579        /// <remarks>
 580        /// Overflow is detected by checking for a change in the sign bit that indicates a wrap-around.
 581        /// Additionally, a special check is performed for adding <see cref="Fixed64.MinValue"/> and -1,
 582        /// as this is a known edge case for overflow.
 583        /// </remarks>
 584        public static long AddOverflowHelper(long x, long y, ref bool overflow)
 585        {
 3586            long sum = x + y;
 587            // Check for overflow using sign bit changes
 3588            overflow |= ((x ^ y ^ sum) & MIN_VALUE_L) != 0;
 589            // Special check for the case when x is long.Fixed64.MinValue and y is negative
 3590            if (x == long.MinValue && y == -1)
 1591                overflow = true;
 3592            return sum;
 593        }
 594
 595        #endregion
 596    }
 597}

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Core/FixedMath.Trigonometry.cs

#LineLine coverage
 1//=======================================================================
 2// FixedMath.Trigonometry.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024–present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using System.Runtime.CompilerServices;
 10
 11namespace FixedMathSharp
 12{
 13    public static partial class FixedMath
 14    {
 15        #region Fields and Constants
 16
 117        private static readonly int[] s_pow10Lookup = {
 118            1,           // 10^0
 119            10,          // 10^1
 120            100,         // 10^2
 121            1000,        // 10^3
 122            10000,       // 10^4
 123            100000,      // 10^5
 124            1000000,     // 10^6
 125            10000000,    // 10^7
 126            100000000,   // 10^8
 127            1000000000,  // 10^9
 128        };
 29
 30        /// <summary>
 31        /// Provides a lookup table of integer powers of 10 from 10^0 to 10^9.
 32        /// </summary>
 33        /// <remarks>
 34        /// This array can be used to efficiently retrieve the value of 10 raised to an integer
 35        /// exponent within the supported range, avoiding repeated calculations.
 36        /// The index corresponds to the exponent.
 37        /// </remarks>
 1438        public static ReadOnlySpan<int> Pow10Lookup => s_pow10Lookup;
 39
 40        // Trigonometric and logarithmic constants
 41
 42        internal const double PI_DOUBLE = 3.14159265358979323846d;
 43        /// <summary>
 44        /// Represents the mathematical constant π (pi).
 45        /// </summary>
 46        /// <remarks>The value is approximately 3.14159265358979323846.</remarks>
 147        internal static readonly long PI_LONG = (long)(PI_DOUBLE * ONE_L);
 48
 49        internal const double LN2_DOUBLE = 0.6931471805599453d;
 50        /// <summary>
 51        /// Represents the mathematical constant natural logarithm of 2 (ln(2)).
 52        /// </summary>
 53        /// <remarks>The value is approximately 0.6931471805599453.</remarks>
 154        internal static readonly long LN2_LONG = (long)(LN2_DOUBLE * ONE_L);
 55
 56        // Asin Padé approximations
 57        internal const double PADE_A1_DOUBLE = 0.183320102d;
 158        internal static readonly long PADE_A1_LONG = (long)(PADE_A1_DOUBLE * ONE_L);
 59        internal const double PADE_A2_DOUBLE = 0.0218804099d;
 160        internal static readonly long PADE_A2_LONG = (long)(PADE_A2_DOUBLE * ONE_L);
 61
 62        // Carefully optimized polynomial coefficients for sin(x), ensuring maximum precision in Fixed64 math.
 63        internal const double SIN_COEFF_3_DOUBLE = 0.16666667605750262737274169921875d; // 1/3!
 164        internal static readonly long SIN_COEFF_3_LONG = (long)(SIN_COEFF_3_DOUBLE * ONE_L);
 65        internal const double SIN_COEFF_5_DOUBLE = 0.0083328341133892536163330078125d; // 1/5!
 166        internal static readonly long SIN_COEFF_5_LONG = (long)(SIN_COEFF_5_DOUBLE * ONE_L);
 67        internal const double SIN_COEFF_7_DOUBLE = 0.00019588856957852840423583984375d; // 1/7!
 168        internal static readonly long SIN_COEFF_7_LONG = (long)(SIN_COEFF_7_DOUBLE * ONE_L);
 69
 170        private static readonly long[] s_pow2PositiveFractionLookup =
 171        {
 172            6074001000L,
 173            5107605667L,
 174            4683695048L,
 175            4485121744L,
 176            4389014833L,
 177            4341736423L,
 178            4318288544L,
 179            4306612134L,
 180            4300785774L,
 181            4297875550L,
 182            4296421177L,
 183            4295694175L,
 184            4295330720L,
 185            4295149004L,
 186            4295058149L,
 187            4295012722L,
 188            4294990009L,
 189            4294978653L,
 190            4294972974L,
 191            4294970135L,
 192            4294968716L,
 193            4294968006L,
 194            4294967651L,
 195            4294967473L,
 196            4294967385L,
 197            4294967340L,
 198            4294967318L,
 199            4294967307L,
 1100            4294967302L,
 1101            4294967299L,
 1102            4294967297L,
 1103            4294967297L
 1104        };
 105
 1106        private static readonly long[] s_pow2NegativeFractionLookup =
 1107        {
 1108            3037000500L,
 1109            3611622603L,
 1110            3938502376L,
 1111            4112874773L,
 1112            4202935003L,
 1113            4248701965L,
 1114            4271771996L,
 1115            4283353945L,
 1116            4289156690L,
 1117            4292061010L,
 1118            4293513907L,
 1119            4294240540L,
 1120            4294603903L,
 1121            4294785595L,
 1122            4294876445L,
 1123            4294921870L,
 1124            4294944583L,
 1125            4294955939L,
 1126            4294961618L,
 1127            4294964457L,
 1128            4294965876L,
 1129            4294966586L,
 1130            4294966941L,
 1131            4294967119L,
 1132            4294967207L,
 1133            4294967252L,
 1134            4294967274L,
 1135            4294967285L,
 1136            4294967290L,
 1137            4294967293L,
 1138            4294967295L,
 1139            4294967295L
 1140        };
 141
 142        #endregion
 143
 144        #region FixedTrigonometry Operations
 145
 146        /// <summary>
 147        /// Raises the base number b to the power of exp.
 148        /// Uses logarithms to compute power efficiently for fixed-point values.
 149        /// </summary>
 150        /// <exception cref="DivideByZeroException">
 151        /// The base was Fixed64.Zero, with a negative expFixed64.Onent
 152        /// </exception>
 153        /// <exception cref="ArgumentOutOfRangeException">
 154        /// The base was negative, with a non-Fixed64.Zero expFixed64.Onent
 155        /// </exception>
 156        public static Fixed64 Pow(Fixed64 b, Fixed64 exp)
 157        {
 14158            if (b == Fixed64.One)
 1159                return Fixed64.One;
 160
 13161            if (exp.m_rawValue == 0)
 1162                return Fixed64.One;
 163
 12164            if (b.m_rawValue == 0)
 165            {
 2166                if (exp.m_rawValue < 0)
 1167                    throw new DivideByZeroException("Cannot raise 0 to a negative power.");
 168
 1169                return Fixed64.Zero;
 170            }
 171
 10172            Fixed64 log2 = Log2(b);  // Calculate logarithm base 2
 10173            return Pow2(exp * log2);  // Raise 2 to the power of log2 result
 174        }
 175
 176        /// <summary>
 177        /// Raises 2 to the power of x.
 178        /// Provides high accuracy for small values of x.
 179        /// </summary>
 180        public static Fixed64 Pow2(Fixed64 x)
 181        {
 54182            if (x.m_rawValue == 0)
 2183                return Fixed64.One;
 184
 52185            bool neg = x.m_rawValue < 0;
 52186            if (neg)
 18187                x = -x;
 188
 52189            if (x == Fixed64.One)
 6190                return neg ? Fixed64.One / Fixed64.Two : Fixed64.Two;
 191
 46192            int integerPart = (int)(x.m_rawValue >> SHIFT_AMOUNT_I);
 46193            long fractionalRaw = x.m_rawValue & MAX_SHIFTED_AMOUNT_UI;
 194
 46195            if (neg)
 196            {
 15197                if (integerPart >= SHIFT_AMOUNT_I)
 1198                    return Fixed64.MinIncrement;
 199
 14200                Fixed64 result = Pow2Fractional(fractionalRaw, s_pow2NegativeFractionLookup);
 14201                return Fixed64.FromRaw(ShiftRightRounded(result.m_rawValue, integerPart));
 202            }
 203
 31204            if (integerPart >= 31)
 2205                return Fixed64.MaxValue;
 206
 29207            Fixed64 positiveResult = Pow2Fractional(fractionalRaw, s_pow2PositiveFractionLookup);
 29208            long shifted = positiveResult.m_rawValue << integerPart;
 209
 29210            return Fixed64.FromRaw(shifted);
 211        }
 212
 213        /// <summary>
 214        /// Returns the base-2 logarithm of a specified number.
 215        /// Provides at least 9 decimals of accuracy.
 216        /// </summary>
 217        /// <remarks>
 218        /// This implementation is based on Clay. S. Turner's fast binary logarithm algorithm
 219        /// (C. S. Turner,  "A Fast Binary Logarithm Algorithm", IEEE Signal Processing Mag., pp. 124,140, Sep. 2010.)
 220        /// </remarks>
 221        public static Fixed64 Log2(Fixed64 x)
 222        {
 59223            if (x.m_rawValue <= 0)
 1224                throw new ArgumentOutOfRangeException(nameof(x), "Cannot compute logarithm of non-positive number.");
 225
 58226            long b = 1U << (SHIFT_AMOUNT_I - 1);  // Initial value for binary logarithm
 58227            long rawX = x.m_rawValue;
 58228            int shift = FloorLog2((ulong)rawX) - SHIFT_AMOUNT_I;
 58229            long y = (long)shift << SHIFT_AMOUNT_I;
 230
 58231            if (shift > 0)
 37232                rawX >>= shift;
 21233            else if (shift < 0)
 17234                rawX <<= -shift;
 235
 58236            Fixed64 z = Fixed64.FromRaw(rawX);  // Remaining fraction
 237
 3828238            for (int i = 0; i < SHIFT_AMOUNT_I; i++)
 239            {
 1856240                z = FastMul(z, z);
 1856241                if (z.m_rawValue >= (ONE_L << 1))
 242                {
 137243                    z = Fixed64.FromRaw(z.m_rawValue >> 1);
 137244                    y += b;
 245                }
 1856246                b >>= 1;
 247            }
 248
 58249            return Fixed64.FromRaw(y);
 250        }
 251
 252        /// <summary>
 253        /// Returns the natural logarithm of a specified fixed-point number.
 254        /// Provides at least 7 decimals of accuracy.
 255        /// </summary>
 256        public static Fixed64 Ln(Fixed64 x)
 257        {
 12258            if (x.m_rawValue <= 0)
 1259                throw new ArgumentOutOfRangeException(nameof(x), "Cannot compute logarithm of non-positive number.");
 260
 11261            return FastMul(Log2(x), Fixed64.Ln2);
 262        }
 263
 264        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 265        private static Fixed64 Pow2Fractional(long fractionalRaw, long[] lookup)
 266        {
 43267            Fixed64 result = Fixed64.One;
 43268            long mask = 1L << (SHIFT_AMOUNT_I - 1);
 269
 2838270            for (int i = 0; i < SHIFT_AMOUNT_I; i++)
 271            {
 1376272                if ((fractionalRaw & mask) != 0)
 93273                    result = FastMul(result, Fixed64.FromRaw(lookup[i]));
 274
 1376275                mask >>= 1;
 276            }
 277
 43278            return result;
 279        }
 280
 281        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 282        private static long ShiftRightRounded(long value, int shift)
 283        {
 14284            if (shift == 0)
 2285                return value;
 286
 12287            long half = 1L << (shift - 1);
 12288            return (value + half) >> shift;
 289        }
 290
 291        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 292        private static int FloorLog2(ulong value)
 293        {
 807294            int result = 0;
 295
 807296            if (value >= 1UL << 32)
 297            {
 700298                value >>= 32;
 700299                result = 32;
 300            }
 301
 807302            if (value >= 1UL << 16)
 303            {
 127304                value >>= 16;
 127305                result += 16;
 306            }
 307
 807308            if (value >= 1UL << 8)
 309            {
 253310                value >>= 8;
 253311                result += 8;
 312            }
 313
 807314            if (value >= 1UL << 4)
 315            {
 374316                value >>= 4;
 374317                result += 4;
 318            }
 319
 807320            if (value >= 1UL << 2)
 321            {
 363322                value >>= 2;
 363323                result += 2;
 324            }
 325
 807326            if (value >= 1UL << 1)
 372327                result++;
 328
 807329            return result;
 330        }
 331
 332        /// <summary>
 333        /// Returns the square root of a specified fixed-point number.
 334        /// </summary>
 335        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 336        public static Fixed64 Sqrt(Fixed64 x)
 337        {
 754338            if (x.m_rawValue < 0)
 1339                throw new ArgumentOutOfRangeException(nameof(x), "Cannot compute square root of a negative number.");
 340
 753341            ulong num = (ulong)x.m_rawValue;
 753342            if (num == 0UL)
 4343                return Fixed64.Zero;
 344
 749345            ulong result = 0UL;
 749346            ulong bit = 1UL << (FloorLog2(num) & ~1);
 347
 348            // Perform the square root calculation using bitwise shifts
 4494349            for (int i = 0; i < 2; ++i)
 350            {
 351                // Calculate the top bits of the square root result
 27599352                while (bit != 0)
 353                {
 26101354                    if (num >= result + bit)
 355                    {
 6411356                        num -= result + bit;
 6411357                        result = (result >> 1) + bit;
 358                    }
 359                    else
 360                    {
 19690361                        result >>= 1;
 362                    }
 363
 26101364                    bit >>= 2;
 365                }
 366
 1498367                if (i == 0)
 368                {
 369                    // Process it again to get the remaining bits
 749370                    if (num > ((1UL << SHIFT_AMOUNT_I) - 1))
 371                    {
 372                        // Handle large remainders by adjusting the result
 5373                        num -= result;
 5374                        num = (num << SHIFT_AMOUNT_I) - (ulong)Fixed64.Half.m_rawValue;
 5375                        result = (result << SHIFT_AMOUNT_I) + (ulong)Fixed64.Half.m_rawValue;
 376                    }
 377                    else
 378                    {
 744379                        num <<= SHIFT_AMOUNT_I;
 744380                        result <<= SHIFT_AMOUNT_I;
 381                    }
 382
 749383                    bit = 1UL << (SHIFT_AMOUNT_I - 2);
 384                }
 385            }
 386
 387            // Rounding: round up if necessary
 749388            if (num > result && (num - result) > (result >> 1))
 110389                ++result;
 390
 749391            return Fixed64.FromRaw((long)result);
 392        }
 393
 394        /// <summary>
 395        /// Converts a value in radians to degrees.
 396        /// </summary>
 397        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 30398        public static Fixed64 RadToDeg(Fixed64 rad) => (rad * Fixed64.OneEighty) / Fixed64.Pi;
 399
 400        /// <summary>
 401        /// Converts a value in degrees to radians.
 402        /// </summary>
 403        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 122404        public static Fixed64 DegToRad(Fixed64 deg) => (deg * Fixed64.Pi) / Fixed64.OneEighty;
 405
 406        /// <summary>
 407        /// Computes the sine of a given angle in radians using an optimized
 408        /// minimax polynomial approximation.
 409        /// </summary>
 410        /// <param name="x">The angle in radians.</param>
 411        /// <returns>The sine of the given angle, in fixed-point format.</returns>
 412        /// <remarks>
 413        /// - This function uses a Chebyshev-polynomial-based approximation to ensure high accuracy
 414        ///   while maintaining performance in fixed-point arithmetic.
 415        /// - The coefficients have been carefully tuned to minimize fixed-point truncation errors.
 416        /// - The error is less than 1 ULP (unit in the last place) at key reference points,
 417        ///   ensuring <c>Sin(π/4) = 0.707106781192124</c> exactly within Fixed64 precision.
 418        /// - The function automatically normalizes input values to the range [-π, π] for stability.
 419        /// </remarks>
 420        public static Fixed64 Sin(Fixed64 x)
 421        {
 422            // Check for special cases
 594423            if (x == Fixed64.Zero) return Fixed64.Zero;   // sin(0) = 0
 539424            if (x == Fixed64.HalfPi) return Fixed64.One;         // sin(π/2) = 1
 316425            if (x == -Fixed64.HalfPi) return -Fixed64.One;       // sin(-π/2) = -1
 304426            if (x == Fixed64.Pi) return Fixed64.Zero;             // sin(π) = 0
 334427            if (x == -Fixed64.Pi) return Fixed64.Zero;            // sin(-π) = 0
 268428            if (x == Fixed64.TwoPi || x == -Fixed64.TwoPi) return Fixed64.Zero;  // sin(2π) = 0
 429
 430            // Normalize x to [-π, π]
 264431            x %= Fixed64.TwoPi;
 264432            if (x < -Fixed64.Pi)
 120433                x += Fixed64.TwoPi;
 144434            else if (x > Fixed64.Pi)
 1435                x -= Fixed64.TwoPi;
 436
 264437            bool flip = false;
 264438            if (x < Fixed64.Zero)
 439            {
 10440                x = -x;
 10441                flip = true;
 442            }
 443
 264444            if (x > Fixed64.HalfPi)
 127445                x = Fixed64.Pi - x;
 446
 264447            Fixed64 result = SinReduced(x);
 448
 264449            return flip ? -result : result;
 450        }
 451
 452        /// <summary>
 453        /// Computes the cosine of a given angle in radians using a sine-based identity transformation.
 454        /// </summary>
 455        /// <param name="x">The angle in radians.</param>
 456        /// <returns>The cosine of the given angle, in fixed-point format.</returns>
 457        /// <remarks>
 458        /// - Instead of directly approximating cosine, this function derives <c>cos(x)</c> using
 459        ///   the identity <c>cos(x) = sin(x + π/2)</c>. This ensures maximum accuracy.
 460        /// - The underlying sine function is computed using a highly optimized minimax polynomial approximation.
 461        /// - By leveraging this transformation, cosine achieves the same precision guarantees
 462        ///   as sine, including <c>Cos(π/4) = 0.707106781192124</c> exactly within Fixed64 precision.
 463        /// - The function automatically normalizes input values to the range [-π, π] for stability.
 464        /// </remarks>
 465        public static Fixed64 Cos(Fixed64 x)
 466        {
 253467            long xl = x.m_rawValue;
 253468            long rawAngle = xl + (xl > 0 ? -Fixed64.Pi.m_rawValue - Fixed64.HalfPi.m_rawValue : Fixed64.HalfPi.m_rawValu
 253469            return Sin(Fixed64.FromRaw(rawAngle));
 470        }
 471
 472        /// <summary>
 473        /// Calculates the hypotenuse of a right triangle given sides a and b using the Pythagorean theorem: sqrt(a^2 + 
 474        /// </summary>
 475        /// <param name="a">The length of side a.</param>
 476        /// <param name="b">The length of side b.</param>
 477        /// <returns>The length of the hypotenuse.</returns>
 478        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 3479        public static Fixed64 GetHypotenuse(Fixed64 a, Fixed64 b) => Sqrt(a * a + b * b);
 480
 481        /// <summary>
 482        /// Calculates the cosine value corresponding to a given sine value, assuming the angle is in the first or
 483        /// second quadrant.
 484        /// </summary>
 485        /// <remarks>
 486        /// This method returns the principal (non-negative) value of the cosine.
 487        /// If the input is outside the valid range for sine values, the result may not be meaningful.
 488        /// </remarks>
 489        /// <param name="sin">The sine of the angle. Must be in the range [-1, 1].</param>
 490        /// <returns>The cosine of the angle, computed as the positive square root of (1 - sin²).</returns>
 1491        public static Fixed64 SinToCos(Fixed64 sin) => Sqrt(Fixed64.One - sin * sin);
 492
 493        /// <summary>
 494        /// Returns the tangent of x.
 495        /// </summary>
 496        /// <remarks>
 497        /// This function is not well-tested. It may be wildly inaccurate.
 498        /// </remarks>
 499        public static Fixed64 Tan(Fixed64 x)
 500        {
 501            // Check for special cases
 25502            if (x == Fixed64.Zero) return Fixed64.Zero;
 29503            if (x == Fixed64.PiOver4) return Fixed64.One;
 18504            if (x == -Fixed64.PiOver4) return -Fixed64.One;
 505
 506            // Normalize x to [-π/2, π/2]
 16507            x %= Fixed64.Pi;
 16508            if (x < -Fixed64.HalfPi)
 1509                x += Fixed64.Pi;
 15510            else if (x > Fixed64.HalfPi)
 1511                x -= Fixed64.Pi;
 512
 16513            bool flip = x < Fixed64.Zero;
 16514            if (flip)
 6515                x = -x;
 516
 16517            Fixed64 sin = SinReduced(x);
 16518            Fixed64 cos = SinReduced(Fixed64.HalfPi - x);
 16519            Fixed64 result = sin / cos;
 520
 16521            return flip ? -result : result;
 522        }
 523
 524        /// <summary>
 525        /// Computes the sine of an angle x (in radians) using a minimax polynomial approximation.
 526        /// </summary>
 527        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 528        private static Fixed64 SinReduced(Fixed64 x)
 529        {
 296530            Fixed64 x2 = x * x;
 296531            Fixed64 x4 = x2 * x2;
 532
 296533            return x * (Fixed64.One
 296534                - x2 * Fixed64.SinCoeff3
 296535                + x4 * Fixed64.SinCoeff5
 296536                - x4 * x2 * Fixed64.SinCoeff7);
 537        }
 538
 539        /// <summary>
 540        /// Returns the arc-sine of a fixed-point number x, which is the angle in radians
 541        /// whose sine is x, using a combination of a Taylor series expansion and trigonometric identities.
 542        ///
 543        /// For values of x near ±1, the identity asin(x) = π/2 - acos(x) is used for stability.
 544        /// For values of x near 0, a Taylor series expansion is used.
 545        /// </summary>
 546        /// <param name="x">The input value (sine) whose arcsine is to be computed. Should be in the range [-1, 1].</par
 547        /// <returns>The arc-sine of x in radians.</returns>
 548        /// <exception cref="ArithmeticException">Thrown if x is outside the domain [-1, 1].</exception>
 549        public static Fixed64 Asin(Fixed64 x)
 550        {
 551            // Ensure x is within the domain [-1, 1]
 15552            if (x < -Fixed64.One || x > Fixed64.One)
 2553                throw new ArithmeticException("Input out of domain for Asin: " + x);
 554
 555            // Handle boundary cases for -1 and 1
 14556            if (x == Fixed64.One) return Fixed64.HalfPi;  // asin(1) = π/2
 13557            if (x == -Fixed64.One) return -Fixed64.HalfPi;  // asin(-1) = -π/2
 558
 559            // Special case handling for asin(0.5) -> π/6 and asin(-0.5) -> -π/6
 14560            if (x == Fixed64.Half) return Fixed64.PiOver6;
 9561            if (x == -Fixed64.Half) return -Fixed64.PiOver6;
 562
 563            // For values close to 0, use a Padé approximation for better precision
 7564            if (x.Abs() < Fixed64.Half)
 565            {
 566                // Padé approximation of asin(x) for |x| < 0.5
 5567                Fixed64 xSquared = x * x;
 5568                Fixed64 numerator = x * (Fixed64.One + (xSquared * (Fixed64.PadeA1 + (xSquared * Fixed64.PadeA2))));
 5569                return numerator;
 570            }
 571
 2572            return x > Fixed64.Zero
 2573                ? Fixed64.HalfPi - Acos(x)
 2574                : -Fixed64.HalfPi + Acos(-x);
 575        }
 576
 577        /// <summary>
 578        /// Returns the arccosine of the specified number x, calculated using a combination of the atan and sqrt functio
 579        /// </summary>
 580        /// <param name="x">The input value whose arccosine is to be computed. Should be in the range [-1, 1].</param>
 581        /// <returns>The arccosine of x in radians.</returns>
 582        /// <exception cref="ArgumentOutOfRangeException">Thrown if x is outside the domain [-1, 1].</exception>
 583        public static Fixed64 Acos(Fixed64 x)
 584        {
 32585            if (Abs(x) > Fixed64.One)
 2586                throw new ArithmeticException("Input out of domain for Acos: " + x);
 587
 588            // For values near 1 or -1, the result is directly known.
 34589            if (x == Fixed64.One) return Fixed64.Zero;      // acos(1) = 0
 27590            if (x == -Fixed64.One) return Fixed64.Pi;       // acos(-1) = π
 32591            if (x == Fixed64.Zero) return Fixed64.HalfPi;  // acos(0) = π/2
 592
 593            // Compute using the relationship acos(x) = atan(sqrt(1 - x^2) / x) + π/2 when x is negative
 18594            var sqrtTerm = Sqrt(Fixed64.One - x * x);   // sqrt(1 - x^2)
 18595            var atanTerm = Atan(sqrtTerm / x);
 596
 18597            return x < Fixed64.Zero
 18598                    ? atanTerm + Fixed64.Pi   // acos(-x) = atan(...) + π
 18599                    : atanTerm;               // Otherwise, return just atan(sqrt(...))
 600        }
 601
 602        /// <summary>
 603        /// Returns the arctangent of the specified number, using a more accurate approximation for larger values.
 604        /// This function has at least 7 decimals of accuracy.
 605        /// </summary>
 606        public static Fixed64 Atan(Fixed64 z)
 607        {
 121608            if (z == Fixed64.Zero) return Fixed64.Zero;
 125609            if (z == Fixed64.One) return Fixed64.PiOver4;
 87610            if (z == -Fixed64.One) return -Fixed64.PiOver4;
 611
 75612            bool neg = z < Fixed64.Zero;
 87613            if (neg) z = -z;
 614
 615
 616            Fixed64 adjustedResult;
 617            // Adjust series for z > 1 using the identity atan(z) = π/2 - atan(1/z)
 75618            if (z > Fixed64.One)
 19619                adjustedResult = Fixed64.HalfPi - Atan(Fixed64.One / z);
 620            // For z in (0.5, 1], use a transformation to improve convergence: atan(z) = π/4 - atan((1 - z) / (1 + z))
 56621            else if (z > Fixed64.Half)
 622            {
 623
 11624                Fixed64 transformedZ = (Fixed64.One - z) / (Fixed64.One + z);
 11625                adjustedResult = Fixed64.PiOver4 - Atan(transformedZ);
 626            }
 627            // For z in (0, 0.5], use the standard Taylor series expansion around 0 for better precision on small values
 628            else
 629            {
 45630                Fixed64 zSq = z * z;
 631
 45632                Fixed64 result = z;
 45633                Fixed64 term = z;
 45634                int sign = -1;
 635
 418636                for (int i = 3; i < 15; i += 2)
 637                {
 193638                    term *= zSq;
 193639                    Fixed64 nextTerm = term / i;
 193640                    if (nextTerm.Abs() < Fixed64.Epsilon)
 641                        break;
 642
 164643                    result += nextTerm * sign;
 164644                    sign = -sign;
 645                }
 646
 45647                adjustedResult = result;
 648            }
 649
 75650            return neg ? -adjustedResult : adjustedResult;
 651        }
 652
 653        /// <summary>
 654        /// Computes the angle whose tangent is the quotient of two specified numbers.
 655        /// </summary>
 656        /// <remarks>
 657        /// Uses a fixed-point arithmetic approximation for the arc tangent function, which is more efficient than using
 658        /// especially on systems where floating-point operations are expensive.
 659        /// </remarks>
 660        /// <param name="y">The y-coordinate of the point to which the angle is measured.</param>
 661        /// <param name="x">The x-coordinate of the point to which the angle is measured.</param>
 662        /// <returns>An angle, θ, measured in radians, such that -π ≤ θ ≤ π, and tan(θ) = y / x,
 663        /// taking into account the quadrants of the inputs to determine the sign of the result.</returns>
 664        public static Fixed64 Atan2(Fixed64 y, Fixed64 x)
 665        {
 33666            if (x == Fixed64.Zero)
 667            {
 4668                if (y > Fixed64.Zero)
 2669                    return Fixed64.HalfPi;
 2670                if (y == Fixed64.Zero)
 1671                    return Fixed64.Zero;
 1672                return -Fixed64.HalfPi;
 673            }
 674
 29675            Fixed64 atan = Atan(y / x);
 676
 677            // Adjust based on the quadrant
 29678            if (x < Fixed64.Zero)
 679            {
 7680                if (y >= Fixed64.Zero)
 681                {
 682                    // Second quadrant
 3683                    return atan + Fixed64.Pi;
 684                }
 685                else
 686                {
 687                    // Third quadrant
 4688                    return atan - Fixed64.Pi;
 689                }
 690            }
 691
 692            // First or fourth quadrant
 22693            return atan;
 694        }
 695
 696        #endregion
 697    }
 698}

Methods/Properties

.cctor()
CopySign(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
Clamp01(FixedMathSharp.Fixed64)
Clamp(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
Clamp(T,T,T)
ClampOne(FixedMathSharp.Fixed64)
Abs(FixedMathSharp.Fixed64)
Ceil(FixedMathSharp.Fixed64)
Floor(FixedMathSharp.Fixed64)
Max(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
Min(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
Round(FixedMathSharp.Fixed64,System.MidpointRounding)
RoundToPrecision(FixedMathSharp.Fixed64,System.Int32,System.MidpointRounding)
Squared(FixedMathSharp.Fixed64)
SmoothStep(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
CubicInterpolate(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
Lerp(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
CatmullRom(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
HermiteSpline(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
BarycentricCoordinate(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
SumSquaredBarycentricProducts(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
SumBarycentricProducts(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
FastAdd(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
FastSub(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
FastMul(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
FastDiv(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
FastMod(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
MoveTowards(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
AddOverflowHelper(System.Int64,System.Int64,System.Boolean&)
.cctor()
get_Pow10Lookup()
Pow(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
Pow2(FixedMathSharp.Fixed64)
Log2(FixedMathSharp.Fixed64)
Ln(FixedMathSharp.Fixed64)
Pow2Fractional(System.Int64,System.Int64[])
ShiftRightRounded(System.Int64,System.Int32)
FloorLog2(System.UInt64)
Sqrt(FixedMathSharp.Fixed64)
RadToDeg(FixedMathSharp.Fixed64)
DegToRad(FixedMathSharp.Fixed64)
Sin(FixedMathSharp.Fixed64)
Cos(FixedMathSharp.Fixed64)
GetHypotenuse(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
SinToCos(FixedMathSharp.Fixed64)
Tan(FixedMathSharp.Fixed64)
SinReduced(FixedMathSharp.Fixed64)
Asin(FixedMathSharp.Fixed64)
Acos(FixedMathSharp.Fixed64)
Atan(FixedMathSharp.Fixed64)
Atan2(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)