< Summary

Line coverage
100%
Covered lines: 473
Uncovered lines: 0
Coverable lines: 473
Total lines: 1452
Line coverage: 100%
Branch coverage
100%
Covered branches: 244
Total branches: 244
Branch coverage: 100%
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: Average(...)100%44100%
File 1: Midpoint(...)100%11100%
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%11100%
File 1: CatmullRom(...)100%11100%
File 1: HermiteSpline(...)100%44100%
File 1: FastAdd(...)100%11100%
File 1: FastSub(...)100%11100%
File 1: FastMul(...)100%44100%
File 1: FastDiv(...)100%22100%
File 1: FastMod(...)100%11100%
File 1: MoveTowards(...)100%88100%
File 2: BarycentricCoordinate(...)100%11100%
File 2: SumSquaredBarycentricProducts(...)100%11100%
File 2: SumBarycentricProducts(...)100%11100%
File 2: TryGetCircleCrossSectionRadius(...)100%22100%
File 2: TryGetSphereSlabCrossSectionRadius(...)100%44100%
File 3: .cctor()100%11100%
File 3: get_Pow10Lookup()100%11100%
File 3: get_CanonicalSinCosErrorBound()100%11100%
File 3: Pow(...)100%88100%
File 3: Pow2(...)100%1414100%
File 3: Log2(...)100%1010100%
File 3: Ln(...)100%22100%
File 3: Pow2Fractional(...)100%44100%
File 3: ShiftRightRounded(...)100%22100%
File 3: FloorLog2(...)100%1212100%
File 3: Sqrt(...)100%1818100%
File 3: RadToDeg(...)100%11100%
File 3: DegToRad(...)100%11100%
File 3: Sin(...)100%2424100%
File 3: Cos(...)100%22100%
File 3: GetHypotenuse(...)100%22100%
File 3: GetScaledMagnitude(...)100%22100%
File 3: TryGetScaledMagnitude(...)100%22100%
File 3: SinToCos(...)100%11100%
File 3: Tan(...)100%1414100%
File 3: SinReduced(...)100%22100%
File 3: CosReduced(...)100%11100%
File 3: Asin(...)100%1616100%
File 3: Acos(...)100%1010100%
File 3: Atan(...)100%1818100%
File 3: 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>
 17public 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 1.
 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 performanc
 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 performa
 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-criti
 91    /// </remarks>
 292    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) =>
 5115        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) =>
 517124        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) =>
 681131        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
 515426164        if (value.m_rawValue == MIN_VALUE_L)
 16165            return new Fixed64(MAX_VALUE_L);
 166
 167        // Use branchless absolute value calculation
 515410168        long mask = value.m_rawValue >> 63; // If negative, mask will be all 1s; if positive, all 0s
 515410169        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)]
 184562194    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)]
 5330200    public static Fixed64 Min(Fixed64 a, Fixed64 b) => a < b ? a : b;
 201
 202    /// <summary>
 203    /// Returns the arithmetic average of three fixed-point values without intermediate overflow.
 204    /// </summary>
 205    /// <remarks>
 206    /// The exact raw Q32.32 sum is divided by three and rounded to the nearest raw value.
 207    /// </remarks>
 208    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 209    public static Fixed64 Average(Fixed64 first, Fixed64 second, Fixed64 third)
 210    {
 278211        long quotient = first.m_rawValue / 3
 278212            + second.m_rawValue / 3
 278213            + third.m_rawValue / 3;
 278214        int remainder = (int)(
 278215            first.m_rawValue % 3
 278216            + second.m_rawValue % 3
 278217            + third.m_rawValue % 3);
 218
 278219        quotient += remainder / 3;
 278220        int residual = remainder % 3;
 278221        if (residual == 2)
 78222            quotient++;
 200223        else if (residual == -2)
 3224            quotient--;
 225
 278226        return Fixed64.FromRaw(quotient);
 227    }
 228
 229    /// <summary>
 230    /// Returns the arithmetic midpoint of two fixed-point values without intermediate overflow.
 231    /// </summary>
 232    /// <remarks>
 233    /// A midpoint exactly between two raw Q32.32 values is rounded to the nearest even raw value.
 234    /// </remarks>
 235    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 236    public static Fixed64 Midpoint(Fixed64 left, Fixed64 right)
 237    {
 236238        long floor = (left.m_rawValue & right.m_rawValue)
 236239            + ((left.m_rawValue ^ right.m_rawValue) >> 1);
 236240        long tieToEvenCorrection = (left.m_rawValue ^ right.m_rawValue) & floor & 1L;
 236241        return Fixed64.FromRaw(floor + tieToEvenCorrection);
 242    }
 243
 244    /// <summary>
 245    /// Rounds a fixed-point number to the nearest integral value, based on the specified rounding mode.
 246    /// </summary>
 247    public static Fixed64 Round(Fixed64 value, MidpointRounding mode = MidpointRounding.ToEven)
 248    {
 30249        long fractionalPart = value.m_rawValue & MAX_SHIFTED_AMOUNT_UI;
 30250        Fixed64 integralPart = value.Floor();
 30251        if (fractionalPart < Fixed64.Half.m_rawValue)
 7252            return integralPart;
 253
 23254        if (fractionalPart > Fixed64.Half.m_rawValue)
 11255            return integralPart + Fixed64.One;
 256
 257        // When value is exactly Fixed64.Halfway between two numbers
 12258        return mode switch
 12259        {
 12260            // For negative midpoints, Floor() is already away from zero
 3261            MidpointRounding.AwayFromZero => value.m_rawValue > 0 ? integralPart + Fixed64.One : integralPart,
 12262            // Rounds to the nearest even number (default behavior)
 9263            _ => (integralPart.m_rawValue & ONE_L) == 0 ? integralPart : integralPart + Fixed64.One,
 12264        };
 265    }
 266
 267    /// <summary>
 268    /// Rounds a fixed-point number to a specific number of decimal places.
 269    /// </summary>
 270    public static Fixed64 RoundToPrecision(Fixed64 value, int decimalPlaces, MidpointRounding mode = MidpointRounding.To
 271    {
 7272        if (decimalPlaces < 0 || decimalPlaces >= Pow10Lookup.Length)
 2273            throw new ArgumentOutOfRangeException(nameof(decimalPlaces), "Decimal places out of range.");
 274
 5275        int factor = Pow10Lookup[decimalPlaces];
 5276        Fixed64 scaled = value * factor;
 5277        long rounded = Round(scaled, mode).m_rawValue;
 5278        return new Fixed64(rounded + (factor / 2)) / factor;
 279    }
 280
 281    /// <summary>
 282    /// Squares the Fixed64 value.
 283    /// </summary>
 284    /// <param name="value">The Fixed64 value to square.</param>
 285    /// <returns>The squared value.</returns>
 286    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2287    public static Fixed64 Squared(Fixed64 value) => value * value;
 288
 289    /// <summary>
 290    /// Performs a smooth step interpolation using a cubic Hermite curve between two values.
 291    /// </summary>
 292    /// <remarks>
 293    /// The interpolation follows a cubic Hermite curve where the function starts at <paramref name="a"/>,
 294    /// accelerates, and then decelerates towards <paramref name="b"/>, ensuring smooth transitions.
 295    /// </remarks>
 296    /// <param name="a">The starting value.</param>
 297    /// <param name="b">The ending value.</param>
 298    /// <param name="t">A value between 0 and 1 that represents the interpolation factor.</param>
 299    /// <returns>The interpolated value between <paramref name="a"/> and <paramref name="b"/>.</returns>
 300    public static Fixed64 SmoothStep(Fixed64 a, Fixed64 b, Fixed64 t)
 301    {
 9302        if (t.m_rawValue <= 0)
 1303            return a;
 8304        if (t.m_rawValue >= ONE_L)
 1305            return b;
 306
 7307        Fixed64 t2 = t * t;
 7308        Fixed64 t3 = t2 * t;
 7309        return a + (b - a) * (Fixed64.Three * t2 - Fixed64.Two * t3);
 310    }
 311
 312    /// <summary>
 313    /// Performs cubic interpolation between two points with tangents at those points.
 314    /// </summary>
 315    /// <param name="p0">The first point.</param>
 316    /// <param name="p1">The second point.</param>
 317    /// <param name="m0">The tangent at <paramref name="p0"/>.</param>
 318    /// <param name="m1">The tangent at <paramref name="p1"/>.</param>
 319    /// <param name="t">A value between 0 and 1 that represents the interpolation factor.</param>
 320    /// <returns>The interpolated value between <paramref name="p0"/> and <paramref name="p1"/>.</returns>
 321    public static Fixed64 CubicInterpolate(Fixed64 p0, Fixed64 p1, Fixed64 m0, Fixed64 m1, Fixed64 t)
 322    {
 5323        Fixed64 t2 = t * t;
 5324        Fixed64 t3 = t2 * t;
 5325        return (Fixed64.Two * p0 - Fixed64.Two * p1 + m0 + m1) * t3
 5326             + (-Fixed64.Three * p0 + Fixed64.Three * p1 - Fixed64.Two * m0 - m1) * t2
 5327             + m0 * t + p0;
 328    }
 329
 330    /// <summary>
 331    /// Linearly interpolates between two fixed-point values based on a given interpolation factor.
 332    /// </summary>
 333    /// <param name="from">The starting value.</param>
 334    /// <param name="to">The ending value.</param>
 335    /// <param name="t">A value between 0 and 1 that represents the interpolation factor.</param>
 336    /// <returns>The interpolated value between <paramref name="from"/> and <paramref name="to"/>.</returns>
 337    /// <remarks>
 338    /// The interpolation is clamped between <paramref name="from"/> and <paramref name="to"/> based on the value of <pa
 339    /// If <paramref name="t"/> is less than 0, the result is <paramref name="from"/>. If <paramref name="t"/> is greate
 340    /// </remarks>
 341    public static Fixed64 Lerp(Fixed64 from, Fixed64 to, Fixed64 t) =>
 2253342        Fixed64.LerpFullDomain(from, to, t);
 343
 344    /// <summary>
 345    /// Computes the interpolated point along a Catmull-Rom spline given four control points.
 346    /// </summary>
 347    /// <param name="p0">The first control point.</param>
 348    /// <param name="p1">The second control point.</param>
 349    /// <param name="p2">The third control point.</param>
 350    /// <param name="p3">The fourth control point.</param>
 351    /// <param name="t">Interpolation factor between 0 and 1.</param>
 352    /// <returns>The interpolated point on the spline.</returns>
 353    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 354    public static Fixed64 CatmullRom(Fixed64 p0, Fixed64 p1, Fixed64 p2, Fixed64 p3, Fixed64 t)
 355    {
 4356        Fixed64 t2 = t * t;
 4357        Fixed64 t3 = t2 * t;
 4358        return ((-t3 + 2 * t2 - t) * p0 +
 4359             (3 * t3 - 5 * t2 + 2) * p1 +
 4360             (-3 * t3 + 4 * t2 + t) * p2 +
 4361             (t3 - t2) * p3) / 2;
 362    }
 363
 364    /// <summary>
 365    /// Performs a Hermite interpolation between two Fixed64 values, using the specified tangents and interpolation amou
 366    /// </summary>
 367    /// <param name="value1">The first value.</param>
 368    /// <param name="tangent1">The tangent at the first value.</param>
 369    /// <param name="value2">The second value.</param>
 370    /// <param name="tangent2">The tangent at the second value.</param>
 371    /// <param name="amount">The interpolation amount.</param>
 372    /// <returns>The Hermite spline interpolated value.</returns>
 373    public static Fixed64 HermiteSpline(
 374        Fixed64 value1,
 375        Fixed64 tangent1,
 376        Fixed64 value2,
 377        Fixed64 tangent2,
 378        Fixed64 amount)
 379    {
 6380        if ((amount - Fixed64.Zero).LessThanEpsilon())
 1381            return value1;
 382
 5383        if ((amount - Fixed64.One).LessThanEpsilon())
 1384            return value2;
 385
 20386        Fixed64 v1 = value1, v2 = value2, t1 = tangent1, t2 = tangent2, s = amount;
 4387        Fixed64 sCubed = s * s * s;
 4388        Fixed64 sSquared = s * s;
 4389        Fixed64 result = (
 4390            ((2 * v1 - 2 * v2 + t2 + t1) * sCubed) +
 4391            ((3 * v2 - 3 * v1 - 2 * t1 - t2) * sSquared) +
 4392            (t1 * s) +
 4393            v1
 4394        );
 395
 4396        return result;
 397    }
 398
 399    /// <summary>
 400    /// Adds two fixed-point numbers by adding their raw Q32.32 payloads without saturation.
 401    /// </summary>
 402    /// <remarks>
 403    /// This is an unchecked hot-path helper. Use it only when the raw sum is known to fit in
 404    /// <see cref="long"/> or raw wraparound is an intentional part of the algorithm. Use
 405    /// <see cref="Fixed64.op_Addition(Fixed64, Fixed64)"/> for the public saturating add contract.
 406    /// </remarks>
 407    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 5408    public static Fixed64 FastAdd(Fixed64 x, Fixed64 y) => Fixed64.FromRaw(x.m_rawValue + y.m_rawValue);
 409
 410    /// <summary>
 411    /// Subtracts two fixed-point numbers by subtracting 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 difference 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_Subtraction(Fixed64, Fixed64)"/> for the public saturating subtract contract.
 417    /// </remarks>
 418    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 5419    public static Fixed64 FastSub(Fixed64 x, Fixed64 y) => Fixed64.FromRaw(x.m_rawValue - y.m_rawValue);
 420
 421    /// <summary>
 422    /// Multiplies two fixed-point numbers using unchecked Q32.32 partial products.
 423    /// </summary>
 424    /// <remarks>
 425    /// This is an unchecked hot-path helper. It skips the full-width overflow/saturation path used by
 426    /// <see cref="Fixed64.op_Multiply(Fixed64, Fixed64)"/> and truncates discarded fractional bits instead
 427    /// of applying the operator's round-half-to-even behavior. Use it only when inputs are constrained and
 428    /// that precision tradeoff is acceptable. Integral operands take a direct raw multiplication path.
 429    /// </remarks>
 430    public static Fixed64 FastMul(Fixed64 x, Fixed64 y)
 431    {
 1968432        long xl = x.m_rawValue;
 1968433        long yl = y.m_rawValue;
 434
 1968435        if ((xl & MAX_SHIFTED_AMOUNT_UI) == 0)
 1590436            return Fixed64.FromRaw((xl >> SHIFT_AMOUNT_I) * yl);
 437
 378438        if ((yl & MAX_SHIFTED_AMOUNT_UI) == 0)
 1439            return Fixed64.FromRaw((yl >> SHIFT_AMOUNT_I) * xl);
 440
 441        // Split values into high and low bits for long multiplication
 377442        ulong xlo = (ulong)(xl & MAX_SHIFTED_AMOUNT_UI);
 377443        long xhi = xl >> SHIFT_AMOUNT_I;
 377444        ulong ylo = (ulong)(yl & MAX_SHIFTED_AMOUNT_UI);
 377445        long yhi = yl >> SHIFT_AMOUNT_I;
 446
 447        // Perform partial products
 377448        ulong lolo = xlo * ylo;
 377449        long lohi = (long)xlo * yhi;
 377450        long hilo = xhi * (long)ylo;
 377451        long hihi = xhi * yhi;
 452
 453        // Combine the results
 377454        ulong loResult = lolo >> SHIFT_AMOUNT_I;
 377455        long midResult1 = lohi;
 377456        long midResult2 = hilo;
 377457        long hiResult = hihi << SHIFT_AMOUNT_I;
 458
 377459        long sum = (long)loResult + midResult1 + midResult2 + hiResult;
 377460        return Fixed64.FromRaw(sum);
 461    }
 462
 463    /// <summary>
 464    /// Divides two fixed-point numbers with an optimized path for known-positive divisors.
 465    /// </summary>
 466    /// <remarks>
 467    /// This helper preserves the same deterministic rounding, divide-by-zero, and saturation semantics
 468    /// as <see cref="Fixed64.op_Division(Fixed64, Fixed64)"/>. The fast path is only used when
 469    /// <paramref name="y"/> is positive; non-positive divisors fall back to the guarded division operator.
 470    /// Prefer the operator unless the divisor positivity invariant is already proven by the caller.
 471    /// </remarks>
 472    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 473    public static Fixed64 FastDiv(Fixed64 x, Fixed64 y)
 474    {
 1647475        long xl = x.m_rawValue;
 1647476        long yl = y.m_rawValue;
 477
 1647478        if (yl <= 0)
 2479            return x / y;
 480
 1645481        return Fixed64.DivideMagnitude(
 1645482            Fixed64.AbsToUInt64(xl),
 1645483            (ulong)yl,
 1645484            xl < 0);
 485    }
 486
 487    /// <summary>
 488    /// Computes the raw remainder of two fixed-point numbers without special-case guards.
 489    /// </summary>
 490    /// <remarks>
 491    /// This is an unchecked hot-path helper. It delegates directly to the raw <see cref="long"/> remainder
 492    /// operation, so raw zero divisors and integer edge cases follow runtime integer remainder behavior.
 493    /// Use <see cref="Fixed64.op_Modulus(Fixed64, Fixed64)"/> for the public guarded remainder contract.
 494    /// </remarks>
 495    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 5496    public static Fixed64 FastMod(Fixed64 x, Fixed64 y) => Fixed64.FromRaw(x.m_rawValue % y.m_rawValue);
 497
 498    /// <summary>
 499    /// Moves a value from 'from' to 'to' by a maximum step of 'maxAmount'.
 500    /// Ensures the value does not exceed 'to'.
 501    /// </summary>
 502    public static Fixed64 MoveTowards(Fixed64 from, Fixed64 to, Fixed64 maxAmount)
 503    {
 5504        if (from < to)
 505        {
 2506            from += maxAmount;
 2507            if (from > to)
 1508                from = to;
 509        }
 3510        else if (from > to)
 511        {
 2512            from -= maxAmount;
 2513            if (from < to)
 1514                from = to;
 515        }
 516
 5517        return Fixed64.FromRaw(from.m_rawValue);
 518    }
 519
 520    #endregion
 521}

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

#LineLine coverage
 1//=======================================================================
 2// FixedMath.Geometry.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;
 10using FixedMathSharp.Geometry;
 11
 12namespace FixedMathSharp;
 13
 14/// <content>
 15/// Geometry helpers for barycentric interpolation and sphere/slab cross-section calculations.
 16/// </content>
 17public static partial class FixedMath
 18{
 19    /// <summary>
 20    /// Performs barycentric interpolation between three scalar coordinates from a triangle.
 21    /// </summary>
 22    /// <param name="coordA">The coordinate of the first vertex.</param>
 23    /// <param name="coordB">The coordinate of the second vertex.</param>
 24    /// <param name="coordC">The coordinate of the third vertex.</param>
 25    /// <param name="weightB">The barycentric weight for the second vertex.</param>
 26    /// <param name="weightC">The barycentric weight for the third vertex.</param>
 27    /// <returns>The interpolated scalar coordinate.</returns>
 28    /// <remarks>
 29    /// Endpoint differences, both weighted terms, and the base coordinate
 30    /// are accumulated before one final round-half-to-even conversion.
 31    /// Results outside the <see cref="Fixed64"/> range saturate.
 32    /// </remarks>
 33    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 34    public static Fixed64 BarycentricCoordinate(
 35        Fixed64 coordA,
 36        Fixed64 coordB,
 37        Fixed64 coordC,
 38        Fixed64 weightB,
 39        Fixed64 weightC
 37540    ) => Fixed64.BarycentricCoordinateFullDomain(coordA, coordB, coordC, weightB, weightC);
 41
 42    /// <summary>
 43    /// Returns the second-order scalar product sum for three barycentric vertices.
 44    /// </summary>
 45    /// <remarks>
 46    /// Computes <c>a * a + b * b + c * c + a * b + a * c + b * c</c>.
 47    /// </remarks>
 48    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 49    public static Fixed64 SumSquaredBarycentricProducts(Fixed64 a, Fixed64 b, Fixed64 c) =>
 450        (a * a) + (b * b) + (c * c) + (a * b) + (a * c) + (b * c);
 51
 52    /// <summary>
 53    /// Returns the cross scalar product sum for two sets of three barycentric vertices.
 54    /// </summary>
 55    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 56    public static Fixed64 SumBarycentricProducts(
 57        Fixed64 firstA,
 58        Fixed64 firstB,
 59        Fixed64 firstC,
 60        Fixed64 secondA,
 61        Fixed64 secondB,
 62        Fixed64 secondC)
 63    {
 464        Fixed64 firstSum = firstA + firstB + firstC;
 465        Fixed64 secondSum = secondA + secondB + secondC;
 466        Fixed64 matchingProducts = (firstA * secondA) + (firstB * secondB) + (firstC * secondC);
 467        return firstSum * secondSum + matchingProducts;
 68    }
 69
 70    /// <summary>
 71    /// Attempts to get the radius of a circular sphere cross-section at the
 72    /// specified signed distance from the sphere center.
 73    /// </summary>
 74    /// <param name="radius">The non-negative sphere radius.</param>
 75    /// <param name="offset">The signed distance from the sphere center to the cross-section plane.</param>
 76    /// <param name="crossSectionRadius">The nearest-even cross-section radius, or zero when the plane misses the sphere
 77    /// <returns><see langword="true"/> when the plane intersects or is tangent to the sphere; otherwise, <see langword=
 78    /// <remarks>
 79    /// The difference of squares and square root remain exact until the final
 80    /// deterministic <see cref="Fixed64"/> conversion.
 81    /// </remarks>
 82    /// <exception cref="ArgumentOutOfRangeException"><paramref name="radius"/> is negative.</exception>
 83    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 84    public static bool TryGetCircleCrossSectionRadius(
 85        Fixed64 radius,
 86        Fixed64 offset,
 87        out Fixed64 crossSectionRadius)
 88    {
 889        if (radius < Fixed64.Zero)
 190            throw new ArgumentOutOfRangeException(nameof(radius), "Radius must be non-negative.");
 91
 792        return WideRadialGeometry.TryGetCircleCrossSectionRadius(
 793            radius,
 794            offset,
 795            out crossSectionRadius);
 96    }
 97
 98    /// <summary>
 99    /// Attempts to get the largest circular sphere cross-section that lies
 100    /// within a centered finite slab.
 101    /// </summary>
 102    /// <param name="sphereCenter">The sphere-center coordinate on the slab axis.</param>
 103    /// <param name="sphereRadius">The non-negative sphere radius.</param>
 104    /// <param name="slabCenter">The slab-center coordinate on the same axis.</param>
 105    /// <param name="slabHalfThickness">The non-negative slab half-thickness.</param>
 106    /// <param name="crossSectionRadius">
 107    /// The nearest-even radius at the slab plane closest to the sphere center,
 108    /// or zero when the slab and sphere do not intersect.
 109    /// </param>
 110    /// <returns><see langword="true"/> when the slab intersects or is tangent to the sphere; otherwise, <see langword="
 111    /// <remarks>
 112    /// Center separation, slab projection, the difference of squares, and the
 113    /// square root remain exact until the final deterministic
 114    /// <see cref="Fixed64"/> conversion.
 115    /// </remarks>
 116    /// <exception cref="ArgumentOutOfRangeException">
 117    /// <paramref name="sphereRadius"/> or <paramref name="slabHalfThickness"/> is negative.
 118    /// </exception>
 119    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 120    public static bool TryGetSphereSlabCrossSectionRadius(
 121        Fixed64 sphereCenter,
 122        Fixed64 sphereRadius,
 123        Fixed64 slabCenter,
 124        Fixed64 slabHalfThickness,
 125        out Fixed64 crossSectionRadius)
 126    {
 7127        if (sphereRadius < Fixed64.Zero)
 1128            throw new ArgumentOutOfRangeException(nameof(sphereRadius), "Sphere radius must be non-negative.");
 6129        if (slabHalfThickness < Fixed64.Zero)
 1130            throw new ArgumentOutOfRangeException(nameof(slabHalfThickness), "Slab half-thickness must be non-negative."
 131
 5132        return WideRadialGeometry.TryGetSphereSlabCrossSectionRadius(
 5133            sphereCenter,
 5134            sphereRadius,
 5135            slabCenter,
 5136            slabHalfThickness,
 5137            out crossSectionRadius);
 138    }
 139}

/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/// <content>
 14/// Trigonometric, logarithmic, and related constants/lookup tables for fixed-point math,
 15/// along with sine/cosine/asin approximation coefficients used by FixedMath.
 16/// </content>
 17public static partial class FixedMath
 18{
 19    #region Fields and Constants
 20
 221    private static readonly int[] s_pow10Lookup = {
 222            1,           // 10^0
 223            10,          // 10^1
 224            100,         // 10^2
 225            1000,        // 10^3
 226            10000,       // 10^4
 227            100000,      // 10^5
 228            1000000,     // 10^6
 229            10000000,    // 10^7
 230            100000000,   // 10^8
 231            1000000000,  // 10^9
 232        };
 33
 34    /// <summary>
 35    /// Provides a lookup table of integer powers of 10 from 10^0 to 10^9.
 36    /// </summary>
 37    /// <remarks>
 38    /// This array can be used to efficiently retrieve the value of 10 raised to an integer
 39    /// exponent within the supported range, avoiding repeated calculations.
 40    /// The index corresponds to the exponent.
 41    /// </remarks>
 1442    public static ReadOnlySpan<int> Pow10Lookup => s_pow10Lookup;
 43
 44    // Trigonometric and logarithmic constants
 45
 46    internal const double PI_DOUBLE = 3.14159265358979323846d;
 47    /// <summary>
 48    /// Represents the mathematical constant π (pi).
 49    /// </summary>
 50    /// <remarks>The value is approximately 3.14159265358979323846.</remarks>
 51    internal const long PI_LONG = (long)(PI_DOUBLE * ONE_L);
 52
 53    internal const double LN2_DOUBLE = 0.6931471805599453d;
 54    /// <summary>
 55    /// Represents the mathematical constant natural logarithm of 2 (ln(2)).
 56    /// </summary>
 57    /// <remarks>The value is approximately 0.6931471805599453.</remarks>
 58    internal const long LN2_LONG = (long)(LN2_DOUBLE * ONE_L);
 59
 60    // Asin Padé approximations
 61    internal const double PADE_A1_DOUBLE = 0.183320102d;
 62    internal const long PADE_A1_LONG = (long)(PADE_A1_DOUBLE * ONE_L);
 63    internal const double PADE_A2_DOUBLE = 0.0218804099d;
 64    internal const long PADE_A2_LONG = (long)(PADE_A2_DOUBLE * ONE_L);
 65
 66    // Minimax sine coefficients for [0, pi/4].
 67    internal const long SIN_COEFF_3_LONG = 715827922L;
 68    internal const long SIN_COEFF_5_LONG = 35789249L;
 69    internal const long SIN_COEFF_7_LONG = 841334L;
 70
 71    // Nearest Q32.32 Taylor coefficients for cosine on [0, pi/4].
 72    internal const long COS_COEFF_2_LONG = 2147483648L; // round(2^32 / 2!)
 73    internal const long COS_COEFF_4_LONG = 178956971L;  // round(2^32 / 4!)
 74    internal const long COS_COEFF_6_LONG = 5965232L;    // round(2^32 / 6!)
 75    internal const long COS_COEFF_8_LONG = 106522L;     // round(2^32 / 8!)
 76
 77    /// <summary>
 78    /// Gets the conservative absolute approximation-error bound for
 79    /// <see cref="Sin(Fixed64)"/> and <see cref="Cos(Fixed64)"/> when the
 80    /// input is already canonical in [-π, π].
 81    /// </summary>
 82    /// <remarks>
 83    /// The bound includes the degree-8 cosine remainder on [0, π/4],
 84    /// coefficient quantization, fixed-point Horner rounding, and the tuned
 85    /// sine approximation error. Raw-neighborhood and principal-range tests
 86    /// validate range-reduction seams independently of exact anchors.
 87    ///
 88    /// This bound does not include phase error accumulated while reducing a
 89    /// large multi-turn input by the fixed-point approximation of 2π. A
 90    /// consumer propagating a strict error budget must canonicalize its
 91    /// angle before turns accumulate or account for that phase error too.
 92    ///
 93    /// Consumers that propagate sine/cosine error through rotations must
 94    /// also account for their own multiply, add, and normalization error.
 95    /// </remarks>
 439796    public static Fixed64 CanonicalSinCosErrorBound => Fixed64.FromRaw(DEFAULT_TOLERANCE_L * 8);
 97
 298    private static readonly long[] s_pow2PositiveFractionLookup =
 299    {
 2100            6074001000L,
 2101            5107605667L,
 2102            4683695048L,
 2103            4485121744L,
 2104            4389014833L,
 2105            4341736423L,
 2106            4318288544L,
 2107            4306612134L,
 2108            4300785774L,
 2109            4297875550L,
 2110            4296421177L,
 2111            4295694175L,
 2112            4295330720L,
 2113            4295149004L,
 2114            4295058149L,
 2115            4295012722L,
 2116            4294990009L,
 2117            4294978653L,
 2118            4294972974L,
 2119            4294970135L,
 2120            4294968716L,
 2121            4294968006L,
 2122            4294967651L,
 2123            4294967473L,
 2124            4294967385L,
 2125            4294967340L,
 2126            4294967318L,
 2127            4294967307L,
 2128            4294967302L,
 2129            4294967299L,
 2130            4294967297L,
 2131            4294967297L
 2132        };
 133
 2134    private static readonly long[] s_pow2NegativeFractionLookup =
 2135    {
 2136            3037000500L,
 2137            3611622603L,
 2138            3938502376L,
 2139            4112874773L,
 2140            4202935003L,
 2141            4248701965L,
 2142            4271771996L,
 2143            4283353945L,
 2144            4289156690L,
 2145            4292061010L,
 2146            4293513907L,
 2147            4294240540L,
 2148            4294603903L,
 2149            4294785595L,
 2150            4294876445L,
 2151            4294921870L,
 2152            4294944583L,
 2153            4294955939L,
 2154            4294961618L,
 2155            4294964457L,
 2156            4294965876L,
 2157            4294966586L,
 2158            4294966941L,
 2159            4294967119L,
 2160            4294967207L,
 2161            4294967252L,
 2162            4294967274L,
 2163            4294967285L,
 2164            4294967290L,
 2165            4294967293L,
 2166            4294967295L,
 2167            4294967295L
 2168        };
 169
 170    /// <summary>
 171    /// Squared magnitudes at or below this value use component scaling so
 172    /// fixed-point squaring cannot dominate the normalized direction's
 173    /// relative error.
 174    /// </summary>
 2175    internal static readonly Fixed64 ScaleSafeMagnitudeSquaredThreshold = Fixed64.FromFraction(1, 256);
 176
 177    /// <summary>
 178    /// Magnitudes at or below this value normalize in scale-relative
 179    /// coordinates so quantizing the final scalar length cannot distort
 180    /// component ratios.
 181    /// </summary>
 2182    internal static readonly Fixed64 ScaleSafeMagnitudeThreshold = Fixed64.FromFraction(1, 16);
 183
 184    #endregion
 185
 186    #region FixedTrigonometry Operations
 187
 188    /// <summary>
 189    /// Raises the base number b to the power of exp.
 190    /// Uses logarithms to compute power efficiently for fixed-point values.
 191    /// </summary>
 192    /// <exception cref="DivideByZeroException">
 193    /// The base was Fixed64.Zero, with a negative expFixed64.Onent
 194    /// </exception>
 195    /// <exception cref="ArgumentOutOfRangeException">
 196    /// The base was negative, with a non-Fixed64.Zero expFixed64.Onent
 197    /// </exception>
 198    public static Fixed64 Pow(Fixed64 b, Fixed64 exp)
 199    {
 14200        if (b == Fixed64.One)
 1201            return Fixed64.One;
 202
 13203        if (exp.m_rawValue == 0)
 1204            return Fixed64.One;
 205
 12206        if (b.m_rawValue == 0)
 207        {
 2208            if (exp.m_rawValue < 0)
 1209                throw new DivideByZeroException("Cannot raise 0 to a negative power.");
 210
 1211            return Fixed64.Zero;
 212        }
 213
 10214        Fixed64 log2 = Log2(b);  // Calculate logarithm base 2
 10215        return Pow2(exp * log2);  // Raise 2 to the power of log2 result
 216    }
 217
 218    /// <summary>
 219    /// Raises 2 to the power of x.
 220    /// Provides high accuracy for small values of x.
 221    /// </summary>
 222    public static Fixed64 Pow2(Fixed64 x)
 223    {
 54224        if (x.m_rawValue == 0)
 2225            return Fixed64.One;
 226
 52227        bool neg = x.m_rawValue < 0;
 52228        if (neg)
 18229            x = -x;
 230
 52231        if (x == Fixed64.One)
 6232            return neg ? Fixed64.One / Fixed64.Two : Fixed64.Two;
 233
 46234        int integerPart = (int)(x.m_rawValue >> SHIFT_AMOUNT_I);
 46235        long fractionalRaw = x.m_rawValue & MAX_SHIFTED_AMOUNT_UI;
 236
 46237        if (neg)
 238        {
 15239            if (integerPart >= SHIFT_AMOUNT_I)
 1240                return Fixed64.MinIncrement;
 241
 14242            Fixed64 result = Pow2Fractional(fractionalRaw, s_pow2NegativeFractionLookup);
 14243            return Fixed64.FromRaw(ShiftRightRounded(result.m_rawValue, integerPart));
 244        }
 245
 31246        if (integerPart >= 31)
 2247            return Fixed64.MaxValue;
 248
 29249        Fixed64 positiveResult = Pow2Fractional(fractionalRaw, s_pow2PositiveFractionLookup);
 29250        long shifted = positiveResult.m_rawValue << integerPart;
 251
 29252        return Fixed64.FromRaw(shifted);
 253    }
 254
 255    /// <summary>
 256    /// Returns the base-2 logarithm of a specified number.
 257    /// Provides at least 9 decimals of accuracy.
 258    /// </summary>
 259    /// <remarks>
 260    /// This implementation is based on Clay. S. Turner's fast binary logarithm algorithm
 261    /// (C. S. Turner,  "A Fast Binary Logarithm Algorithm", IEEE Signal Processing Mag., pp. 124,140, Sep. 2010.)
 262    /// </remarks>
 263    public static Fixed64 Log2(Fixed64 x)
 264    {
 59265        if (x.m_rawValue <= 0)
 1266            throw new ArgumentOutOfRangeException(nameof(x), "Cannot compute logarithm of non-positive number.");
 267
 58268        long b = 1U << (SHIFT_AMOUNT_I - 1);  // Initial value for binary logarithm
 58269        long rawX = x.m_rawValue;
 58270        int shift = FloorLog2((ulong)rawX) - SHIFT_AMOUNT_I;
 58271        long y = (long)shift << SHIFT_AMOUNT_I;
 272
 58273        if (shift > 0)
 37274            rawX >>= shift;
 21275        else if (shift < 0)
 17276            rawX <<= -shift;
 277
 58278        Fixed64 z = Fixed64.FromRaw(rawX);  // Remaining fraction
 279
 3828280        for (int i = 0; i < SHIFT_AMOUNT_I; i++)
 281        {
 1856282            z = FastMul(z, z);
 1856283            if (z.m_rawValue >= (ONE_L << 1))
 284            {
 137285                z = Fixed64.FromRaw(z.m_rawValue >> 1);
 137286                y += b;
 287            }
 1856288            b >>= 1;
 289        }
 290
 58291        return Fixed64.FromRaw(y);
 292    }
 293
 294    /// <summary>
 295    /// Returns the natural logarithm of a specified fixed-point number.
 296    /// Provides at least 7 decimals of accuracy.
 297    /// </summary>
 298    public static Fixed64 Ln(Fixed64 x)
 299    {
 12300        if (x.m_rawValue <= 0)
 1301            throw new ArgumentOutOfRangeException(nameof(x), "Cannot compute logarithm of non-positive number.");
 302
 11303        return FastMul(Log2(x), Fixed64.Ln2);
 304    }
 305
 306    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 307    private static Fixed64 Pow2Fractional(long fractionalRaw, long[] lookup)
 308    {
 43309        Fixed64 result = Fixed64.One;
 43310        long mask = 1L << (SHIFT_AMOUNT_I - 1);
 311
 2838312        for (int i = 0; i < SHIFT_AMOUNT_I; i++)
 313        {
 1376314            if ((fractionalRaw & mask) != 0)
 93315                result = FastMul(result, Fixed64.FromRaw(lookup[i]));
 316
 1376317            mask >>= 1;
 318        }
 319
 43320        return result;
 321    }
 322
 323    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 324    private static long ShiftRightRounded(long value, int shift)
 325    {
 14326        if (shift == 0)
 2327            return value;
 328
 12329        long half = 1L << (shift - 1);
 12330        return (value + half) >> shift;
 331    }
 332
 333    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 334    private static int FloorLog2(ulong value)
 335    {
 10841336        int result = 0;
 337
 10841338        if (value >= 1UL << 32)
 339        {
 10656340            value >>= 32;
 10656341            result = 32;
 342        }
 343
 10841344        if (value >= 1UL << 16)
 345        {
 214346            value >>= 16;
 214347            result += 16;
 348        }
 349
 10841350        if (value >= 1UL << 8)
 351        {
 326352            value >>= 8;
 326353            result += 8;
 354        }
 355
 10841356        if (value >= 1UL << 4)
 357        {
 448358            value >>= 4;
 448359            result += 4;
 360        }
 361
 10841362        if (value >= 1UL << 2)
 363        {
 579364            value >>= 2;
 579365            result += 2;
 366        }
 367
 10841368        if (value >= 1UL << 1)
 848369            result++;
 370
 10841371        return result;
 372    }
 373
 374    /// <summary>
 375    /// Returns the square root of a specified fixed-point number.
 376    /// </summary>
 377    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 378    public static Fixed64 Sqrt(Fixed64 x)
 379    {
 10802380        if (x.m_rawValue < 0)
 1381            throw new ArgumentOutOfRangeException(nameof(x), "Cannot compute square root of a negative number.");
 382
 10801383        ulong num = (ulong)x.m_rawValue;
 10801384        if (num == 0UL)
 18385            return Fixed64.Zero;
 386
 10783387        ulong result = 0UL;
 10783388        ulong bit = 1UL << (FloorLog2(num) & ~1);
 389
 390        // Perform the square root calculation using bitwise shifts
 64698391        for (int i = 0; i < 2; ++i)
 392        {
 393            // Calculate the top bits of the square root result
 378893394            while (bit != 0)
 395            {
 357327396                if (num >= result + bit)
 397                {
 65997398                    num -= result + bit;
 65997399                    result = (result >> 1) + bit;
 400                }
 401                else
 402                {
 291330403                    result >>= 1;
 404                }
 405
 357327406                bit >>= 2;
 407            }
 408
 21566409            if (i == 0)
 410            {
 411                // Process it again to get the remaining bits
 10783412                if (num > ((1UL << SHIFT_AMOUNT_I) - 1))
 413                {
 414                    // Handle large remainders by adjusting the result
 5415                    num -= result;
 5416                    num = (num << SHIFT_AMOUNT_I) - (ulong)Fixed64.Half.m_rawValue;
 5417                    result = (result << SHIFT_AMOUNT_I) + (ulong)Fixed64.Half.m_rawValue;
 418                }
 419                else
 420                {
 10778421                    num <<= SHIFT_AMOUNT_I;
 10778422                    result <<= SHIFT_AMOUNT_I;
 423                }
 424
 10783425                bit = 1UL << (SHIFT_AMOUNT_I - 2);
 426            }
 427        }
 428
 429        // Rounding: round up if necessary
 10783430        if (num > result && (num - result) > (result >> 1))
 1196431            ++result;
 432
 10783433        return Fixed64.FromRaw((long)result);
 434    }
 435
 436    /// <summary>
 437    /// Converts a value in radians to degrees.
 438    /// </summary>
 439    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 440    public static Fixed64 RadToDeg(Fixed64 rad) =>
 40441        Fixed64.MultiplyDivide(rad, Fixed64.OneEighty, Fixed64.Pi, out _);
 442
 443    /// <summary>
 444    /// Converts a value in degrees to radians.
 445    /// </summary>
 446    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 447    public static Fixed64 DegToRad(Fixed64 deg) =>
 1148448        Fixed64.MultiplyDivide(deg, Fixed64.Pi, Fixed64.OneEighty, out _);
 449
 450    /// <summary>
 451    /// Computes the sine of a given angle in radians using complementary
 452    /// reduced-range polynomial approximations.
 453    /// </summary>
 454    /// <param name="x">The angle in radians.</param>
 455    /// <returns>The sine of the given angle, in fixed-point format.</returns>
 456    /// <remarks>
 457    /// The input is normalized to [-π, π], reflected into [0, π/2], and
 458    /// evaluated as sine on [0, π/4] or cosine on [0, π/4]. Exact quadrant
 459    /// anchors remain exact without introducing a discontinuity beside them.
 460    /// </remarks>
 461    public static Fixed64 Sin(Fixed64 x)
 462    {
 463        // Check for special cases
 94097464        if (x == Fixed64.Zero) return Fixed64.Zero;   // sin(0) = 0
 79582465        if (x == Fixed64.HalfPi) return Fixed64.One;         // sin(π/2) = 1
 45786466        if (x == -Fixed64.HalfPi) return -Fixed64.One;       // sin(-π/2) = -1
 45747467        if (x == Fixed64.Pi) return Fixed64.Zero;             // sin(π) = 0
 46949468        if (x == -Fixed64.Pi) return Fixed64.Zero;            // sin(-π) = 0
 44529469        if (x == Fixed64.TwoPi || x == -Fixed64.TwoPi) return Fixed64.Zero;  // sin(2π) = 0
 470
 471        // Normalize x to [-π, π]
 44521472        x %= Fixed64.TwoPi;
 44521473        if (x < -Fixed64.Pi)
 11274474            x += Fixed64.TwoPi;
 33247475        else if (x > Fixed64.Pi)
 2058476            x -= Fixed64.TwoPi;
 477
 44521478        bool flip = false;
 44521479        if (x < Fixed64.Zero)
 480        {
 16298481            x = -x;
 16298482            flip = true;
 483        }
 484
 44521485        if (x > Fixed64.HalfPi)
 19003486            x = Fixed64.Pi - x;
 487
 44521488        Fixed64 result = SinReduced(x);
 489
 44521490        return flip ? -result : result;
 491    }
 492
 493    /// <summary>
 494    /// Computes the cosine of a given angle in radians using a sine-based identity transformation.
 495    /// </summary>
 496    /// <param name="x">The angle in radians.</param>
 497    /// <returns>The cosine of the given angle, in fixed-point format.</returns>
 498    /// <remarks>
 499    /// - Instead of directly approximating cosine, this function derives <c>cos(x)</c> using
 500    ///   the identity <c>cos(x) = sin(x + π/2)</c>.
 501    /// - The underlying sine function uses complementary reduced-range
 502    ///   sine and cosine polynomials so quadrant anchors remain continuous.
 503    /// - The function automatically normalizes input values to the range [-π, π] for stability.
 504    /// </remarks>
 505    public static Fixed64 Cos(Fixed64 x)
 506    {
 39190507        long xl = x.m_rawValue;
 39190508        long rawAngle = xl + (xl > 0 ? -Fixed64.Pi.m_rawValue - Fixed64.HalfPi.m_rawValue : Fixed64.HalfPi.m_rawValue);
 39190509        return Sin(Fixed64.FromRaw(rawAngle));
 510    }
 511
 512    /// <summary>
 513    /// Calculates the hypotenuse of a right triangle given sides a and b using the Pythagorean theorem: sqrt(a^2 + b^2)
 514    /// </summary>
 515    /// <param name="a">The length of side a.</param>
 516    /// <param name="b">The length of side b.</param>
 517    /// <returns>The length of the hypotenuse.</returns>
 518    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 519    public static Fixed64 GetHypotenuse(Fixed64 a, Fixed64 b)
 520    {
 5521        Fixed64 squareSum = a * a + b * b;
 5522        return squareSum == Fixed64.MaxValue
 5523            ? GetScaledMagnitude(a, b, Fixed64.Zero, Fixed64.Zero)
 5524            : Sqrt(squareSum);
 525    }
 526
 527    internal static Fixed64 GetScaledMagnitude(Fixed64 x, Fixed64 y, Fixed64 z, Fixed64 w)
 528    {
 9751529        x = Abs(x);
 9751530        y = Abs(y);
 9751531        z = Abs(z);
 9751532        w = Abs(w);
 9751533        Fixed64 scale = Max(Max(x, y), Max(z, w));
 9751534        if (scale == Fixed64.Zero)
 51535            return Fixed64.Zero;
 536
 9700537        x /= scale;
 9700538        y /= scale;
 9700539        z /= scale;
 9700540        w /= scale;
 9700541        return scale * Sqrt(x * x + y * y + z * z + w * w);
 542    }
 543
 544    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 545    internal static bool TryGetScaledMagnitude(
 546        Fixed64 x,
 547        Fixed64 y,
 548        Fixed64 z,
 549        Fixed64 w,
 550        out Fixed64 magnitude)
 551    {
 67552        if (!Fixed64.IsMagnitudeRepresentable(x, y, z, w))
 553        {
 40554            magnitude = Fixed64.MaxValue;
 40555            return false;
 556        }
 557
 27558        magnitude = GetScaledMagnitude(x, y, z, w);
 27559        return true;
 560    }
 561
 562    /// <summary>
 563    /// Calculates the cosine value corresponding to a given sine value, assuming the angle is in the first or
 564    /// second quadrant.
 565    /// </summary>
 566    /// <remarks>
 567    /// This method returns the principal (non-negative) value of the cosine.
 568    /// If the input is outside the valid range for sine values, the result may not be meaningful.
 569    /// </remarks>
 570    /// <param name="sin">The sine of the angle. Must be in the range [-1, 1].</param>
 571    /// <returns>The cosine of the angle, computed as the positive square root of (1 - sin²).</returns>
 1572    public static Fixed64 SinToCos(Fixed64 sin) => Sqrt(Fixed64.One - sin * sin);
 573
 574    /// <summary>
 575    /// Returns the tangent of x.
 576    /// </summary>
 577    /// <remarks>
 578    /// This function is not well-tested. It may be wildly inaccurate.
 579    /// </remarks>
 580    public static Fixed64 Tan(Fixed64 x)
 581    {
 582        // Check for special cases
 25583        if (x == Fixed64.Zero) return Fixed64.Zero;
 29584        if (x == Fixed64.PiOver4) return Fixed64.One;
 18585        if (x == -Fixed64.PiOver4) return -Fixed64.One;
 586
 587        // Normalize x to [-π/2, π/2]
 16588        x %= Fixed64.Pi;
 16589        if (x < -Fixed64.HalfPi)
 1590            x += Fixed64.Pi;
 15591        else if (x > Fixed64.HalfPi)
 1592            x -= Fixed64.Pi;
 593
 16594        bool flip = x < Fixed64.Zero;
 16595        if (flip)
 6596            x = -x;
 597
 16598        Fixed64 sin = SinReduced(x);
 16599        Fixed64 cos = SinReduced(Fixed64.HalfPi - x);
 16600        Fixed64 result = sin / cos;
 601
 16602        return flip ? -result : result;
 603    }
 604
 605    /// <summary>
 606    /// Computes sine on [0, π/2] using complementary approximations on [0, π/4].
 607    /// </summary>
 608    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 609    private static Fixed64 SinReduced(Fixed64 x)
 610    {
 44553611        if (x > Fixed64.PiOver4)
 20981612            return CosReduced(Fixed64.HalfPi - x);
 613
 23572614        Fixed64 x2 = x * x;
 23572615        Fixed64 x4 = x2 * x2;
 616
 23572617        return x * (Fixed64.One
 23572618            - x2 * Fixed64.SinCoeff3
 23572619            + x4 * Fixed64.SinCoeff5
 23572620            - x4 * x2 * Fixed64.SinCoeff7);
 621    }
 622
 623    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 624    private static Fixed64 CosReduced(Fixed64 x)
 625    {
 20981626        Fixed64 x2 = x * x;
 20981627        return Fixed64.One - x2 * (
 20981628            Fixed64.CosCoeff2 - x2 * (
 20981629                Fixed64.CosCoeff4 - x2 * (
 20981630                    Fixed64.CosCoeff6 - x2 * (
 20981631                        Fixed64.CosCoeff8))));
 632    }
 633
 634    /// <summary>
 635    /// Returns the arc-sine of a fixed-point number x, which is the angle in radians
 636    /// whose sine is x, using a combination of a Taylor series expansion and trigonometric identities.
 637    ///
 638    /// For values of x near ±1, the identity asin(x) = π/2 - acos(x) is used for stability.
 639    /// For values of x near 0, a Taylor series expansion is used.
 640    /// </summary>
 641    /// <param name="x">The input value (sine) whose arcsine is to be computed. Should be in the range [-1, 1].</param>
 642    /// <returns>The arc-sine of x in radians.</returns>
 643    /// <exception cref="ArithmeticException">Thrown if x is outside the domain [-1, 1].</exception>
 644    public static Fixed64 Asin(Fixed64 x)
 645    {
 646        // Ensure x is within the domain [-1, 1]
 15647        if (x < -Fixed64.One || x > Fixed64.One)
 2648            throw new ArithmeticException("Input out of domain for Asin: " + x);
 649
 650        // Handle boundary cases for -1 and 1
 14651        if (x == Fixed64.One) return Fixed64.HalfPi;  // asin(1) = π/2
 13652        if (x == -Fixed64.One) return -Fixed64.HalfPi;  // asin(-1) = -π/2
 653
 654        // Special case handling for asin(0.5) -> π/6 and asin(-0.5) -> -π/6
 14655        if (x == Fixed64.Half) return Fixed64.PiOver6;
 9656        if (x == -Fixed64.Half) return -Fixed64.PiOver6;
 657
 658        // For values close to 0, use a Padé approximation for better precision
 7659        if (x.Abs() < Fixed64.Half)
 660        {
 661            // Padé approximation of asin(x) for |x| < 0.5
 5662            Fixed64 xSquared = x * x;
 5663            Fixed64 numerator = x * (Fixed64.One + (xSquared * (Fixed64.PadeA1 + (xSquared * Fixed64.PadeA2))));
 5664            return numerator;
 665        }
 666
 2667        return x > Fixed64.Zero
 2668            ? Fixed64.HalfPi - Acos(x)
 2669            : -Fixed64.HalfPi + Acos(-x);
 670    }
 671
 672    /// <summary>
 673    /// Returns the arccosine of the specified number x, calculated using a combination of the atan and sqrt functions.
 674    /// </summary>
 675    /// <param name="x">The input value whose arccosine is to be computed. Should be in the range [-1, 1].</param>
 676    /// <returns>The arccosine of x in radians.</returns>
 677    /// <exception cref="ArgumentOutOfRangeException">Thrown if x is outside the domain [-1, 1].</exception>
 678    public static Fixed64 Acos(Fixed64 x)
 679    {
 45680        if (Abs(x) > Fixed64.One)
 4681            throw new ArithmeticException("Input out of domain for Acos: " + x);
 682
 683        // For values near 1 or -1, the result is directly known.
 49684        if (x == Fixed64.One) return Fixed64.Zero;      // acos(1) = 0
 38685        if (x == -Fixed64.One) return Fixed64.Pi;       // acos(-1) = π
 35686        if (x == Fixed64.Zero) return Fixed64.HalfPi;  // acos(0) = π/2
 687
 688        // Compute using the relationship acos(x) = atan(sqrt(1 - x^2) / x) + π/2 when x is negative
 21689        var sqrtTerm = Sqrt(Fixed64.One - x * x);   // sqrt(1 - x^2)
 21690        var atanTerm = Atan(sqrtTerm / x);
 691
 21692        return x < Fixed64.Zero
 21693                ? atanTerm + Fixed64.Pi   // acos(-x) = atan(...) + π
 21694                : atanTerm;               // Otherwise, return just atan(sqrt(...))
 695    }
 696
 697    /// <summary>
 698    /// Returns the arctangent of the specified number, using a more accurate approximation for larger values.
 699    /// This function has at least 7 decimals of accuracy.
 700    /// </summary>
 701    public static Fixed64 Atan(Fixed64 z)
 702    {
 153703        if (z == Fixed64.Zero) return Fixed64.Zero;
 155704        if (z == Fixed64.One) return Fixed64.PiOver4;
 117705        if (z == -Fixed64.One) return -Fixed64.PiOver4;
 706
 105707        bool neg = z < Fixed64.Zero;
 118708        if (neg) z = -z;
 709
 710
 711        Fixed64 adjustedResult;
 712        // Adjust series for z > 1 using the identity atan(z) = π/2 - atan(1/z)
 105713        if (z > Fixed64.One)
 27714            adjustedResult = Fixed64.HalfPi - Atan(Fixed64.One / z);
 715        // For z in (0.5, 1], use a transformation to improve convergence: atan(z) = π/4 - atan((1 - z) / (1 + z))
 78716        else if (z > Fixed64.Half)
 717        {
 718
 21719            Fixed64 transformedZ = (Fixed64.One - z) / (Fixed64.One + z);
 21720            adjustedResult = Fixed64.PiOver4 - Atan(transformedZ);
 721        }
 722        // For z in (0, 0.5], use the standard Taylor series expansion around 0 for better precision on small values.
 723        else
 724        {
 57725            Fixed64 zSq = z * z;
 726
 57727            Fixed64 result = z;
 57728            Fixed64 term = z;
 57729            int sign = -1;
 730
 510731            for (int i = 3; i < 15; i += 2)
 732            {
 239733                term *= zSq;
 239734                Fixed64 nextTerm = term / i;
 239735                if (nextTerm.Abs() < Fixed64.Epsilon)
 736                    break;
 737
 198738                result += nextTerm * sign;
 198739                sign = -sign;
 740            }
 741
 57742            adjustedResult = result;
 743        }
 744
 105745        return neg ? -adjustedResult : adjustedResult;
 746    }
 747
 748    /// <summary>
 749    /// Computes the angle whose tangent is the quotient of two specified numbers.
 750    /// </summary>
 751    /// <remarks>
 752    /// Uses a fixed-point arithmetic approximation for the arc tangent function, which is more efficient than using flo
 753    /// especially on systems where floating-point operations are expensive.
 754    /// </remarks>
 755    /// <param name="y">The y-coordinate of the point to which the angle is measured.</param>
 756    /// <param name="x">The x-coordinate of the point to which the angle is measured.</param>
 757    /// <returns>An angle, θ, measured in radians, such that -π ≤ θ ≤ π, and tan(θ) = y / x,
 758    /// taking into account the quadrants of the inputs to determine the sign of the result.</returns>
 759    public static Fixed64 Atan2(Fixed64 y, Fixed64 x)
 760    {
 46761        if (x == Fixed64.Zero)
 762        {
 7763            if (y > Fixed64.Zero)
 3764                return Fixed64.HalfPi;
 4765            if (y == Fixed64.Zero)
 2766                return Fixed64.Zero;
 2767            return -Fixed64.HalfPi;
 768        }
 769
 39770        Fixed64 atan = Atan(y / x);
 771
 772        // Adjust based on the quadrant
 39773        if (x < Fixed64.Zero)
 774        {
 7775            if (y >= Fixed64.Zero)
 776            {
 777                // Second quadrant
 3778                return atan + Fixed64.Pi;
 779            }
 780            else
 781            {
 782                // Third quadrant
 4783                return atan - Fixed64.Pi;
 784            }
 785        }
 786
 787        // First or fourth quadrant
 32788        return atan;
 789    }
 790
 791    #endregion
 792}

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)
Average(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
Midpoint(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)
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)
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)
TryGetCircleCrossSectionRadius(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
TryGetSphereSlabCrossSectionRadius(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
.cctor()
get_Pow10Lookup()
get_CanonicalSinCosErrorBound()
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)
GetScaledMagnitude(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryGetScaledMagnitude(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
SinToCos(FixedMathSharp.Fixed64)
Tan(FixedMathSharp.Fixed64)
SinReduced(FixedMathSharp.Fixed64)
CosReduced(FixedMathSharp.Fixed64)
Asin(FixedMathSharp.Fixed64)
Acos(FixedMathSharp.Fixed64)
Atan(FixedMathSharp.Fixed64)
Atan2(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)