| | | 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 | | |
| | | 8 | | using System; |
| | | 9 | | using System.Runtime.CompilerServices; |
| | | 10 | | |
| | | 11 | | namespace 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> |
| | 1 | 92 | | 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) => |
| | 6 | 115 | | 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) => |
| | 24 | 124 | | 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) => |
| | 298 | 131 | | 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 | | { |
| | 4 | 144 | | if (value.CompareTo(max) > 0) return max; |
| | 3 | 145 | | if (value.CompareTo(min) < 0) return min; |
| | 1 | 146 | | 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) => |
| | 5 | 156 | | 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 |
| | 4900 | 164 | | if (value.m_rawValue == MIN_VALUE_L) |
| | 1 | 165 | | return new Fixed64(MAX_VALUE_L); |
| | | 166 | | |
| | | 167 | | // Use branchless absolute value calculation |
| | 4899 | 168 | | long mask = value.m_rawValue >> 63; // If negative, mask will be all 1s; if positive, all 0s |
| | 4899 | 169 | | 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 | | { |
| | 14 | 178 | | bool hasFractionalPart = (value.m_rawValue & MAX_SHIFTED_AMOUNT_UI) != 0; |
| | 14 | 179 | | 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) => |
| | 62 | 188 | | 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)] |
| | 1493 | 194 | | 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)] |
| | 1526 | 200 | | 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 | | { |
| | 30 | 207 | | long fractionalPart = value.m_rawValue & MAX_SHIFTED_AMOUNT_UI; |
| | 30 | 208 | | Fixed64 integralPart = value.Floor(); |
| | 30 | 209 | | if (fractionalPart < Fixed64.Half.m_rawValue) |
| | 7 | 210 | | return integralPart; |
| | | 211 | | |
| | 23 | 212 | | if (fractionalPart > Fixed64.Half.m_rawValue) |
| | 11 | 213 | | return integralPart + Fixed64.One; |
| | | 214 | | |
| | | 215 | | // When value is exactly Fixed64.Halfway between two numbers |
| | 12 | 216 | | return mode switch |
| | 12 | 217 | | { |
| | 12 | 218 | | // For negative midpoints, Floor() is already away from zero |
| | 3 | 219 | | MidpointRounding.AwayFromZero => value.m_rawValue > 0 ? integralPart + Fixed64.One : integralPart, |
| | 12 | 220 | | // Rounds to the nearest even number (default behavior) |
| | 9 | 221 | | _ => (integralPart.m_rawValue & ONE_L) == 0 ? integralPart : integralPart + Fixed64.One, |
| | 12 | 222 | | }; |
| | | 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 | | { |
| | 7 | 230 | | if (decimalPlaces < 0 || decimalPlaces >= Pow10Lookup.Length) |
| | 2 | 231 | | throw new ArgumentOutOfRangeException(nameof(decimalPlaces), "Decimal places out of range."); |
| | | 232 | | |
| | 5 | 233 | | int factor = Pow10Lookup[decimalPlaces]; |
| | 5 | 234 | | Fixed64 scaled = value * factor; |
| | 5 | 235 | | long rounded = Round(scaled, mode).m_rawValue; |
| | 5 | 236 | | 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)] |
| | 2 | 245 | | 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 | | { |
| | 9 | 260 | | if (t.m_rawValue <= 0) |
| | 1 | 261 | | return a; |
| | 8 | 262 | | if (t.m_rawValue >= ONE_L) |
| | 1 | 263 | | return b; |
| | | 264 | | |
| | 7 | 265 | | Fixed64 t2 = t * t; |
| | 7 | 266 | | Fixed64 t3 = t2 * t; |
| | 7 | 267 | | 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 | | { |
| | 5 | 281 | | Fixed64 t2 = t * t; |
| | 5 | 282 | | Fixed64 t3 = t2 * t; |
| | 5 | 283 | | return (Fixed64.Two * p0 - Fixed64.Two * p1 + m0 + m1) * t3 |
| | 5 | 284 | | + (-Fixed64.Three * p0 + Fixed64.Three * p1 - Fixed64.Two * m0 - m1) * t2 |
| | 5 | 285 | | + 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 | | { |
| | 98 | 301 | | if (t.m_rawValue >= ONE_L) |
| | 4 | 302 | | return to; |
| | 94 | 303 | | if (t.m_rawValue <= 0) |
| | 5 | 304 | | return from; |
| | | 305 | | |
| | 89 | 306 | | 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 | | { |
| | 4 | 321 | | Fixed64 t2 = t * t; |
| | 4 | 322 | | Fixed64 t3 = t2 * t; |
| | 4 | 323 | | return ((-t3 + 2 * t2 - t) * p0 + |
| | 4 | 324 | | (3 * t3 - 5 * t2 + 2) * p1 + |
| | 4 | 325 | | (-3 * t3 + 4 * t2 + t) * p2 + |
| | 4 | 326 | | (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 | | { |
| | 6 | 345 | | if ((amount - Fixed64.Zero).LessThanEpsilon()) |
| | 1 | 346 | | return value1; |
| | | 347 | | |
| | 5 | 348 | | if ((amount - Fixed64.One).LessThanEpsilon()) |
| | 1 | 349 | | return value2; |
| | | 350 | | |
| | 20 | 351 | | Fixed64 v1 = value1, v2 = value2, t1 = tangent1, t2 = tangent2, s = amount; |
| | 4 | 352 | | Fixed64 sCubed = s * s * s; |
| | 4 | 353 | | Fixed64 sSquared = s * s; |
| | 4 | 354 | | Fixed64 result = ( |
| | 4 | 355 | | ((2 * v1 - 2 * v2 + t2 + t1) * sCubed) + |
| | 4 | 356 | | ((3 * v2 - 3 * v1 - 2 * t1 - t2) * sSquared) + |
| | 4 | 357 | | (t1 * s) + |
| | 4 | 358 | | v1 |
| | 4 | 359 | | ); |
| | | 360 | | |
| | 4 | 361 | | 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 |
| | 51 | 380 | | ) => 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) => |
| | 4 | 390 | | (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 | | { |
| | 4 | 404 | | Fixed64 firstSum = firstA + firstB + firstC; |
| | 4 | 405 | | Fixed64 secondSum = secondA + secondB + secondC; |
| | 4 | 406 | | Fixed64 matchingProducts = (firstA * secondA) + (firstB * secondB) + (firstC * secondC); |
| | 4 | 407 | | 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)] |
| | 5 | 419 | | 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)] |
| | 5 | 430 | | 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 | | { |
| | 1968 | 443 | | long xl = x.m_rawValue; |
| | 1968 | 444 | | long yl = y.m_rawValue; |
| | | 445 | | |
| | 1968 | 446 | | if ((xl & MAX_SHIFTED_AMOUNT_UI) == 0) |
| | 1590 | 447 | | return Fixed64.FromRaw((xl >> SHIFT_AMOUNT_I) * yl); |
| | | 448 | | |
| | 378 | 449 | | if ((yl & MAX_SHIFTED_AMOUNT_UI) == 0) |
| | 1 | 450 | | return Fixed64.FromRaw((yl >> SHIFT_AMOUNT_I) * xl); |
| | | 451 | | |
| | | 452 | | // Split values into high and low bits for long multiplication |
| | 377 | 453 | | ulong xlo = (ulong)(xl & MAX_SHIFTED_AMOUNT_UI); |
| | 377 | 454 | | long xhi = xl >> SHIFT_AMOUNT_I; |
| | 377 | 455 | | ulong ylo = (ulong)(yl & MAX_SHIFTED_AMOUNT_UI); |
| | 377 | 456 | | long yhi = yl >> SHIFT_AMOUNT_I; |
| | | 457 | | |
| | | 458 | | // Perform partial products |
| | 377 | 459 | | ulong lolo = xlo * ylo; |
| | 377 | 460 | | long lohi = (long)xlo * yhi; |
| | 377 | 461 | | long hilo = xhi * (long)ylo; |
| | 377 | 462 | | long hihi = xhi * yhi; |
| | | 463 | | |
| | | 464 | | // Combine the results |
| | 377 | 465 | | ulong loResult = lolo >> SHIFT_AMOUNT_I; |
| | 377 | 466 | | long midResult1 = lohi; |
| | 377 | 467 | | long midResult2 = hilo; |
| | 377 | 468 | | long hiResult = hihi << SHIFT_AMOUNT_I; |
| | | 469 | | |
| | 377 | 470 | | long sum = (long)loResult + midResult1 + midResult2 + hiResult; |
| | 377 | 471 | | 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 | | { |
| | 308 | 486 | | long xl = x.m_rawValue; |
| | 308 | 487 | | long yl = y.m_rawValue; |
| | | 488 | | |
| | 308 | 489 | | if (yl <= 0) |
| | 2 | 490 | | return x / y; |
| | | 491 | | |
| | 306 | 492 | | ulong remainder = (ulong)(xl < 0 ? -xl : xl); |
| | 306 | 493 | | ulong divider = (ulong)yl; |
| | 306 | 494 | | ulong quotient = 0UL; |
| | 306 | 495 | | int bitPos = SHIFT_AMOUNT_I + 1; |
| | | 496 | | |
| | 1779 | 497 | | while ((divider & 0xF) == 0 && bitPos >= 4) |
| | | 498 | | { |
| | 1473 | 499 | | divider >>= 4; |
| | 1473 | 500 | | bitPos -= 4; |
| | | 501 | | } |
| | | 502 | | |
| | 643 | 503 | | while (remainder != 0 && bitPos >= 0) |
| | | 504 | | { |
| | 339 | 505 | | int shift = Fixed64.CountLeadingZeroes(remainder); |
| | 339 | 506 | | if (shift > bitPos) |
| | 237 | 507 | | shift = bitPos; |
| | | 508 | | |
| | 339 | 509 | | remainder <<= shift; |
| | 339 | 510 | | bitPos -= shift; |
| | | 511 | | |
| | 339 | 512 | | ulong div = remainder / divider; |
| | 339 | 513 | | remainder %= divider; |
| | 339 | 514 | | quotient += div << bitPos; |
| | | 515 | | |
| | 339 | 516 | | if ((div & ~(0xFFFFFFFFFFFFFFFF >> bitPos)) != 0) |
| | 2 | 517 | | return xl >= 0 |
| | 2 | 518 | | ? new Fixed64(MAX_VALUE_L) |
| | 2 | 519 | | : new Fixed64(MIN_VALUE_L); |
| | | 520 | | |
| | 337 | 521 | | remainder <<= 1; |
| | 337 | 522 | | --bitPos; |
| | | 523 | | } |
| | | 524 | | |
| | 304 | 525 | | if ((quotient & 0x1) != 0) |
| | 112 | 526 | | quotient += 1; |
| | | 527 | | |
| | 304 | 528 | | long result = (long)(quotient >> 1); |
| | 304 | 529 | | if (xl < 0) |
| | 45 | 530 | | result = -result; |
| | | 531 | | |
| | 304 | 532 | | 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)] |
| | 5 | 544 | | 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 | | { |
| | 5 | 552 | | if (from < to) |
| | | 553 | | { |
| | 2 | 554 | | from += maxAmount; |
| | 2 | 555 | | if (from > to) |
| | 1 | 556 | | from = to; |
| | | 557 | | } |
| | 3 | 558 | | else if (from > to) |
| | | 559 | | { |
| | 2 | 560 | | from -= maxAmount; |
| | 2 | 561 | | if (from < to) |
| | 1 | 562 | | from = to; |
| | | 563 | | } |
| | | 564 | | |
| | 5 | 565 | | 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 | | { |
| | 3 | 586 | | long sum = x + y; |
| | | 587 | | // Check for overflow using sign bit changes |
| | 3 | 588 | | overflow |= ((x ^ y ^ sum) & MIN_VALUE_L) != 0; |
| | | 589 | | // Special check for the case when x is long.Fixed64.MinValue and y is negative |
| | 3 | 590 | | if (x == long.MinValue && y == -1) |
| | 1 | 591 | | overflow = true; |
| | 3 | 592 | | return sum; |
| | | 593 | | } |
| | | 594 | | |
| | | 595 | | #endregion |
| | | 596 | | } |
| | | 597 | | } |