< Summary

Information
Class: FixedMathSharp.Fixed64
Assembly: FixedMathSharp
File(s): /home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Numerics/Scalars/Fixed64.cs
Line coverage
99%
Covered lines: 306
Uncovered lines: 1
Coverable lines: 307
Total lines: 919
Line coverage: 99.6%
Branch coverage
96%
Covered branches: 85
Total branches: 88
Branch coverage: 96.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
Offset(...)100%11100%
RawToString()100%11100%
Fraction(...)100%11100%
PostIncrement(...)100%11100%
PostDecrement(...)100%11100%
CountLeadingZeroes(...)100%44100%
Sign(...)100%44100%
IsInteger(...)100%11100%
op_Explicit(...)100%11100%
op_Explicit(...)100%11100%
op_Explicit(...)100%11100%
op_Explicit(...)100%11100%
op_Explicit(...)100%11100%
op_Explicit(...)100%11100%
op_Explicit(...)100%11100%
op_Explicit(...)100%11100%
op_Explicit(...)100%11100%
op_Explicit(...)100%11100%
op_Addition(...)100%44100%
op_Addition(...)100%11100%
op_Addition(...)100%11100%
op_Subtraction(...)100%44100%
op_Subtraction(...)100%11100%
op_Subtraction(...)100%11100%
op_Multiply(...)81.25%161695.65%
AbsToUInt64(...)100%22100%
Multiply64To128(...)100%11100%
ShiftRightRoundedToEven(...)100%66100%
op_Multiply(...)100%11100%
op_Multiply(...)100%11100%
op_Division(...)100%2424100%
op_Division(...)100%11100%
op_Division(...)100%11100%
op_Modulus(...)100%44100%
op_UnaryNegation(...)100%22100%
op_Increment(...)100%11100%
op_Decrement(...)100%11100%
op_LeftShift(...)100%11100%
op_RightShift(...)100%11100%
op_GreaterThan(...)100%11100%
op_LessThan(...)100%11100%
op_GreaterThanOrEqual(...)100%11100%
op_LessThanOrEqual(...)100%11100%
op_Equality(...)100%11100%
op_Inequality(...)100%11100%
ToString()100%11100%
ToString(...)100%11100%
Parse(...)100%88100%
TryParse(...)100%88100%
FromRaw(...)100%11100%
ToDouble(...)100%11100%
ToFloat(...)100%11100%
ToDecimal(...)100%11100%
Equals(...)100%22100%
Equals(...)100%11100%
Equals(...)100%11100%
GetHashCode()100%11100%
GetHashCode(...)100%11100%
CompareTo(...)100%11100%

File(s)

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Numerics/Scalars/Fixed64.cs

#LineLine coverage
 1using MemoryPack;
 2using System;
 3using System.Collections.Generic;
 4using System.Globalization;
 5using System.Runtime.CompilerServices;
 6using System.Text.Json.Serialization;
 7
 8namespace FixedMathSharp;
 9
 10/// <summary>
 11/// Represents a Q(64-SHIFT_AMOUNT).SHIFT_AMOUNT fixed-point number.
 12/// Provides high precision for fixed-point arithmetic where SHIFT_AMOUNT bits
 13/// are used for the fractional part and (64 - SHIFT_AMOUNT) bits for the integer part.
 14/// The precision is determined by SHIFT_AMOUNT, which defines the resolution of fractional values.
 15/// </summary>
 16[Serializable]
 17[MemoryPackable]
 18public partial struct Fixed64 : IEquatable<Fixed64>, IComparable<Fixed64>, IEqualityComparer<Fixed64>
 19{
 20    #region Static Readonly Fields
 21
 22    /// <inheritdoc cref="FixedMath.MAX_VALUE_L" />
 23    public static readonly Fixed64 MAX_VALUE = new(FixedMath.MAX_VALUE_L);
 24    /// <inheritdoc cref="FixedMath.MIN_VALUE_L" />
 25    public static readonly Fixed64 MIN_VALUE = new(FixedMath.MIN_VALUE_L);
 26
 27    /// <inheritdoc cref="FixedMath.ONE_L" />
 28    public static readonly Fixed64 One = new(FixedMath.ONE_L);
 29    /// <summary>
 30    /// Represents the value 2 shifted left by the number of bits specified by SHIFT_AMOUNT_I.
 31    /// </summary>
 32    public static readonly Fixed64 Two = One * 2;
 33    /// <summary>
 34    /// Represents the value 3 shifted left by the number of bits specified by SHIFT_AMOUNT_I.
 35    /// </summary>
 36    public static readonly Fixed64 Three = One * 3;
 37    /// <summary>
 38    /// Represents the value 0.5 shifted left by the number of bits specified by SHIFT_AMOUNT_I.
 39    /// </summary>
 40    public static readonly Fixed64 Half = One / 2;
 41    /// <summary>
 42    /// Represents the value 0.25 shifted left by the number of bits specified by SHIFT_AMOUNT_I.
 43    /// </summary>
 44    public static readonly Fixed64 Quarter = One / 4;
 45    /// <summary>
 46    /// Represents the value 0.125 shifted left by the number of bits specified by SHIFT_AMOUNT_I.
 47    /// </summary>
 48    public static readonly Fixed64 Eighth = One / 8;
 49    /// <summary>
 50    /// Represents the value 0 as a fixed-point number.
 51    /// </summary>
 52    public static readonly Fixed64 Zero = new(0);
 53
 54
 55    /// <inheritdoc cref="FixedMath.MIN_INCREMENT_L" />
 56    public static readonly Fixed64 MinIncrement = new(FixedMath.MIN_INCREMENT_L);
 57
 58    /// <inheritdoc cref="FixedMath.DEFAULT_TOLERANCE_L" />
 59    public static readonly Fixed64 Epsilon = new(FixedMath.DEFAULT_TOLERANCE_L);
 60
 61    #endregion
 62
 63    #region Fields
 64
 65    /// <summary>
 66    /// The underlying raw long value representing the fixed-point number.
 67    /// </summary>
 68    [JsonInclude]
 69    [MemoryPackInclude]
 70    public long m_rawValue;
 71
 72    #endregion
 73
 74    #region Constructors
 75
 76    /// <summary>
 77    /// Internal constructor for a Fixed64 from a raw long value.
 78    /// </summary>
 79    /// <param name="rawValue">Raw long value representing the fixed-point number.</param>
 10340880    internal Fixed64(long rawValue) => m_rawValue = rawValue;
 81
 82    /// <summary>
 83    /// Constructs a Fixed64 from an integer, with the fractional part set to zero.
 84    /// </summary>
 85    /// <param name="value">Integer value to convert to </param>
 1584686    public Fixed64(int value) : this((long)value << FixedMath.SHIFT_AMOUNT_I) { }
 87
 88    /// <summary>
 89    /// Constructs a Fixed64 from a double-precision floating-point value.
 90    /// </summary>
 91    /// <remarks>
 92    /// The value is multiplied by the scaling factor (2^SHIFT_AMOUNT) and
 93    /// rounded to the nearest integer to fit into the fixed-point representation.
 94    /// </remarks>
 95    /// <param name="value">Double value to convert to </param>
 213096    public Fixed64(double value) : this((long)Math.Round((double)value * FixedMath.ONE_L)) { }
 97
 98    #endregion
 99
 100    #region Methods (Instance)
 101
 102    /// <summary>
 103    /// Offsets the current Fixed64 by an integer value.
 104    /// </summary>
 105    /// <param name="x">The integer value to add.</param>
 106    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 107    public void Offset(int x)
 1108    {
 1109        m_rawValue += (long)x << FixedMath.SHIFT_AMOUNT_I;
 1110    }
 111
 112    /// <summary>
 113    /// Returns the raw value as a string.
 114    /// </summary>
 115    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 116    public string RawToString()
 1117    {
 1118        return m_rawValue.ToString();
 1119    }
 120
 121    #endregion
 122
 123    #region Fixed64 Operations
 124
 125    /// <summary>
 126    /// Creates a Fixed64 from a fractional number.
 127    /// </summary>
 128    /// <param name="numerator">The numerator of the fraction.</param>
 129    /// <param name="denominator">The denominator of the fraction.</param>
 130    /// <returns>A Fixed64 representing the fraction.</returns>
 131    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 132    public static Fixed64 Fraction(double numerator, double denominator)
 1133    {
 1134        return new Fixed64(numerator / denominator);
 1135    }
 136
 137    /// <summary>
 138    /// x++ (post-increment)
 139    /// </summary>
 140    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 141    public static Fixed64 PostIncrement(ref Fixed64 a)
 1142    {
 1143        Fixed64 originalValue = a;
 1144        a.m_rawValue += One.m_rawValue;
 1145        return originalValue;
 1146    }
 147
 148    /// <summary>
 149    /// x-- (post-decrement)
 150    /// </summary>
 151    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 152    public static Fixed64 PostDecrement(ref Fixed64 a)
 1153    {
 1154        Fixed64 originalValue = a;
 1155        a.m_rawValue -= One.m_rawValue;
 1156        return originalValue;
 1157    }
 158
 159    /// <summary>
 160    /// Counts the leading zeros in a 64-bit unsigned integer.
 161    /// </summary>
 162    /// <param name="x">The number to count leading zeros for.</param>
 163    /// <returns>The number of leading zeros.</returns>
 164    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 165    internal static int CountLeadingZeroes(ulong x)
 4093166    {
 4093167        int result = 0;
 149318168        while ((x & 0xF000000000000000) == 0) { result += 4; x <<= 4; }
 40338169        while ((x & 0x8000000000000000) == 0) { result += 1; x <<= 1; }
 4093170        return result;
 4093171    }
 172
 173    /// <summary>
 174    /// Returns a number indicating the sign of a Fix64 number.
 175    /// Returns 1 if the value is positive, 0 if is 0, and -1 if it is negative.
 176    /// </summary>
 177    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 178    public static int Sign(Fixed64 value)
 18179    {
 180        // Return the sign of the value, optimizing for branchless comparison
 18181        return value.m_rawValue < 0 ? -1 : (value.m_rawValue > 0 ? 1 : 0);
 18182    }
 183
 184    /// <summary>
 185    /// Returns true if the number has no decimal part (i.e., if the number is equivalent to an integer) and False other
 186    /// </summary>
 187    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 188    public static bool IsInteger(Fixed64 value)
 7189    {
 7190        return ((ulong)value.m_rawValue & FixedMath.MAX_SHIFTED_AMOUNT_UI) == 0;
 7191    }
 192
 193    #endregion
 194
 195    #region Explicit and Implicit Conversions
 196
 197    /// <summary>
 198    /// Converts a 64-bit signed integer to a Fixed64 value using explicit casting.
 199    /// </summary>
 200    /// <remarks>
 201    /// The conversion interprets the input value as the integer part of the fixed-point number.
 202    /// Use this operator when an explicit conversion from long to Fixed64 is required.
 203    /// </remarks>
 204    /// <param name="value">The 64-bit signed integer to convert to a Fixed64 value.</param>
 205    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 206    public static explicit operator Fixed64(long value)
 1207    {
 1208        return FromRaw(value << FixedMath.SHIFT_AMOUNT_I);
 1209    }
 210
 211    /// <summary>
 212    /// Converts a Fixed64 value to a 64-bit signed integer by discarding the fractional part.
 213    /// </summary>
 214    /// <remarks>
 215    /// The conversion truncates any fractional component.
 216    /// The result represents the integer portion of the Fixed64 value.</remarks>
 217    /// <param name="value">The Fixed64 value to convert to a long integer.</param>
 218    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 219    public static explicit operator long(Fixed64 value)
 1220    {
 1221        return value.m_rawValue >> FixedMath.SHIFT_AMOUNT_I;
 1222    }
 223
 224    /// <summary>
 225    /// Defines an explicit conversion from a 32-bit signed integer to a Fixed64 value.
 226    /// </summary>
 227    /// <param name="value">The 32-bit signed integer to convert to a Fixed64 value.</param>
 228    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 229    public static explicit operator Fixed64(int value)
 3565230    {
 3565231        return new Fixed64(value);
 3565232    }
 233
 234    /// <summary>
 235    /// Converts a Fixed64 value to a 32-bit signed integer by discarding the fractional part.
 236    /// </summary>
 237    /// <remarks>
 238    /// The conversion truncates any fractional component.
 239    /// The result is equivalent to rounding toward zero.
 240    /// </remarks>
 241    /// <param name="value">The Fixed64 value to convert to an integer.</param>
 242    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 243    public static explicit operator int(Fixed64 value)
 18244    {
 18245        return (int)(value.m_rawValue >> FixedMath.SHIFT_AMOUNT_I);
 18246    }
 247
 248    /// <summary>
 249    /// Defines an explicit conversion from a single-precision floating-point value to a Fixed64 instance.
 250    /// </summary>
 251    /// <param name="value">The single-precision floating-point value to convert to Fixed64.</param>
 252    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 253    public static explicit operator Fixed64(float value)
 3254    {
 3255        return new Fixed64((double)value);
 3256    }
 257
 258    /// <summary>
 259    /// Converts a Fixed64 value to its equivalent single-precision floating-point representation.
 260    /// </summary>
 261    /// <remarks>
 262    /// This conversion may result in a loss of precision if the Fixed64 value cannot be exactly  represented as a float
 263    /// </remarks>
 264    /// <param name="value">The Fixed64 value to convert to a float.</param>
 265    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 266    public static explicit operator float(Fixed64 value)
 1267    {
 1268        return value.m_rawValue * FixedMath.SCALE_FACTOR_F;
 1269    }
 270
 271    /// <summary>
 272    /// Defines an explicit conversion from a double-precision floating-point number to a Fixed64 value.
 273    /// </summary>
 274    /// <remarks>
 275    /// This conversion may result in loss of precision if the double value cannot be exactly represented as a Fixed64.
 276    /// </remarks>
 277    /// <param name="value">The double-precision floating-point number to convert to a Fixed64 value.</param>
 278    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 279    public static explicit operator Fixed64(double value)
 146280    {
 146281        return new Fixed64(value);
 146282    }
 283
 284    /// <summary>
 285    /// Converts a Fixed64 value to its equivalent double-precision floating-point representation.
 286    /// </summary>
 287    /// <remarks>
 288    /// This conversion may result in a loss of precision if the Fixed64 value cannot be exactly represented as a double
 289    /// </remarks>
 290    /// <param name="value">The Fixed64 value to convert to a double.</param>
 291    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 292    public static explicit operator double(Fixed64 value)
 5320293    {
 5320294        return value.m_rawValue * FixedMath.SCALE_FACTOR_D;
 5320295    }
 296
 297    /// <summary>
 298    /// Defines an explicit conversion from a decimal value to a Fixed64 instance.
 299    /// </summary>
 300    /// <param name="value">The decimal value to convert to a Fixed64 instance.</param>
 301    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 302    public static explicit operator Fixed64(decimal value)
 1303    {
 1304        return new Fixed64((double)value);
 1305    }
 306
 307    /// <summary>
 308    /// Converts a Fixed64 value to its decimal representation.
 309    /// </summary>
 310    /// <remarks>
 311    /// This operator provides an explicit conversion from Fixed64 to decimal, preserving the numeric value as closely a
 312    /// Use this conversion when precise decimal arithmetic is required.
 313    /// </remarks>
 314    /// <param name="value">The Fixed64 value to convert to decimal.</param>
 315    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 316    public static explicit operator decimal(Fixed64 value)
 1317    {
 1318        return value.m_rawValue * FixedMath.SCALE_FACTOR_M;
 1319    }
 320
 321    #endregion
 322
 323    #region Arithmetic Operators
 324
 325    /// <summary>
 326    /// Adds two Fixed64 numbers, with saturating behavior in case of overflow.
 327    /// </summary>
 328    public static Fixed64 operator +(Fixed64 x, Fixed64 y)
 19200329    {
 19200330        long xl = x.m_rawValue;
 19200331        long yl = y.m_rawValue;
 19200332        long sum = xl + yl;
 333        // Check for overflow, if signs of operands are equal and signs of sum and x are different
 19200334        if (((~(xl ^ yl) & (xl ^ sum)) & FixedMath.MIN_VALUE_L) != 0)
 3335            sum = xl > 0 ? FixedMath.MAX_VALUE_L : FixedMath.MIN_VALUE_L;
 19200336        return new Fixed64(sum);
 19200337    }
 338
 339    /// <summary>
 340    /// Adds an int to x
 341    /// </summary>
 342    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 343    public static Fixed64 operator +(Fixed64 x, int y)
 20344    {
 20345        return x + new Fixed64((long)y << FixedMath.SHIFT_AMOUNT_I);
 20346    }
 347
 348    /// <summary>
 349    /// Adds an Fixed64 to x
 350    /// </summary>
 351    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 352    public static Fixed64 operator +(int x, Fixed64 y)
 1353    {
 1354        return y + x;
 1355    }
 356
 357    /// <summary>
 358    /// Subtracts one Fixed64 number from another, with saturating behavior in case of overflow.
 359    /// </summary>
 360    public static Fixed64 operator -(Fixed64 x, Fixed64 y)
 11684361    {
 11684362        long xl = x.m_rawValue;
 11684363        long yl = y.m_rawValue;
 11684364        long diff = xl - yl;
 365        // Check for overflow, if signs of operands are different and signs of sum and x are different
 11684366        if ((((xl ^ yl) & (xl ^ diff)) & FixedMath.MIN_VALUE_L) != 0)
 2367            diff = xl < 0 ? FixedMath.MIN_VALUE_L : FixedMath.MAX_VALUE_L;
 11684368        return new Fixed64(diff);
 11684369    }
 370
 371    /// <summary>
 372    /// Subtracts an int from x
 373    /// </summary>
 374    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 375    public static Fixed64 operator -(Fixed64 x, int y)
 10376    {
 10377        return x - new Fixed64((long)y << FixedMath.SHIFT_AMOUNT_I);
 10378    }
 379
 380    /// <summary>
 381    /// Subtracts a Fixed64 from x
 382    /// </summary>
 383    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 384    public static Fixed64 operator -(int x, Fixed64 y)
 10385    {
 10386        return new Fixed64((long)x << FixedMath.SHIFT_AMOUNT_I) - y;
 10387    }
 388
 389    /// <summary>
 390    /// Multiplies two Fixed64 numbers, handling overflow and rounding.
 391    /// </summary>
 392    /// <summary>
 393    /// Multiplies two Fixed64 numbers using full-width 128-bit intermediate precision
 394    /// and round-half-to-even semantics on the discarded fractional bits.
 395    /// </summary>
 396    public static Fixed64 operator *(Fixed64 x, Fixed64 y)
 41689397    {
 41689398        long xl = x.m_rawValue;
 41689399        long yl = y.m_rawValue;
 400
 41689401        int shift = FixedMath.SHIFT_AMOUNT_I;
 402
 41689403        if (shift <= 0 || shift >= 64)
 0404            throw new InvalidOperationException($"SHIFT_AMOUNT_I must be in the range 1..63, but was {shift}.");
 405
 406        // Determine sign of the final result.
 41689407        bool negative = ((xl ^ yl) < 0);
 408
 409        // Convert to unsigned magnitudes safely, including long.MinValue.
 41689410        ulong ax = AbsToUInt64(xl);
 41689411        ulong ay = AbsToUInt64(yl);
 412
 413        // Compute exact 128-bit unsigned product: (hi << 64) | lo
 41689414        Multiply64To128(ax, ay, out ulong hi, out ulong lo);
 415
 416        // Shift-right with round-half-to-even using the FULL discarded remainder.
 41689417        ulong magnitude = ShiftRightRoundedToEven(hi, lo, shift, out bool roundedOverflow);
 418
 419        // If rounding overflowed the shifted magnitude, carry it into saturation handling.
 41689420        if (!negative)
 30571421        {
 30571422            if (roundedOverflow || magnitude > long.MaxValue)
 3423                return new Fixed64(FixedMath.MAX_VALUE_L);
 424
 30568425            return new Fixed64((long)magnitude);
 426        }
 427        else
 11118428        {
 429            // For negative results, magnitude may be exactly 2^63, which maps to long.MinValue.
 430            const ulong minValueMagnitude = 0x8000000000000000UL;
 431
 11118432            if (roundedOverflow || magnitude > minValueMagnitude)
 2433                return new Fixed64(FixedMath.MIN_VALUE_L);
 434
 11116435            if (magnitude == minValueMagnitude)
 1436                return new Fixed64(FixedMath.MIN_VALUE_L);
 437
 11115438            return new Fixed64(-(long)magnitude);
 439        }
 41689440    }
 441
 442    /// <summary>
 443    /// Returns the absolute value of a signed 64-bit integer as an unsigned 64-bit magnitude,
 444    /// safely handling long.MinValue.
 445    /// </summary>
 446    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 447    private static ulong AbsToUInt64(long value)
 83378448    {
 83378449        return value < 0
 83378450            ? unchecked((ulong)(~value + 1))
 83378451            : (ulong)value;
 83378452    }
 453
 454    /// <summary>
 455    /// Computes the exact unsigned 128-bit product of two 64-bit unsigned integers.
 456    /// The result is returned as hi:lo, where product = (hi &lt;&lt; 64) | lo.
 457    /// </summary>
 458    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 459    private static void Multiply64To128(ulong a, ulong b, out ulong hi, out ulong lo)
 41689460    {
 41689461        ulong aLo = (uint)a;
 41689462        ulong aHi = a >> 32;
 41689463        ulong bLo = (uint)b;
 41689464        ulong bHi = b >> 32;
 465
 41689466        ulong p0 = aLo * bLo;
 41689467        ulong p1 = aLo * bHi;
 41689468        ulong p2 = aHi * bLo;
 41689469        ulong p3 = aHi * bHi;
 470
 41689471        ulong middle = (p0 >> 32) + (uint)p1 + (uint)p2;
 472
 41689473        lo = (p0 & 0xFFFFFFFFUL) | (middle << 32);
 41689474        hi = p3 + (p1 >> 32) + (p2 >> 32) + (middle >> 32);
 41689475    }
 476
 477    /// <summary>
 478    /// Shifts the unsigned 128-bit value (hi:lo) right by <paramref name="shift"/> bits,
 479    /// applying round-half-to-even to the discarded bits.
 480    /// </summary>
 481    /// <param name="hi">Upper 64 bits of the 128-bit value.</param>
 482    /// <param name="lo">Lower 64 bits of the 128-bit value.</param>
 483    /// <param name="shift">Number of bits to shift right. Must be in the range 1..63.</param>
 484    /// <param name="overflowed">
 485    /// True if the shifted or rounded result exceeded 64 bits.
 486    /// </param>
 487    /// <returns>
 488    /// The rounded 64-bit result of ((hi:lo) >> shift).
 489    /// </returns>
 490    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 491    private static ulong ShiftRightRoundedToEven(ulong hi, ulong lo, int shift, out bool overflowed)
 41689492    {
 493        // Preconditions: 1 <= shift <= 63
 494
 495        // Bits above the low 64 result bits must be preserved as saturation state
 496        // before the lower projection can wrap back into range.
 41689497        overflowed = (hi >> shift) != 0UL;
 498
 499        // Integer part after shifting right by 'shift':
 500        // result = ((hi << (64 - shift)) | (lo >> shift))
 41689501        ulong result = (hi << (64 - shift)) | (lo >> shift);
 502
 503        // Discarded remainder bits are the low 'shift' bits of lo.
 41689504        ulong remainderMask = (1UL << shift) - 1UL;
 41689505        ulong remainder = lo & remainderMask;
 506
 507        // Halfway value among the discarded bits.
 41689508        ulong half = 1UL << (shift - 1);
 509
 510        // Round-half-to-even:
 511        // - round up if remainder > half
 512        // - if exactly half, round so final result is even
 41689513        bool shouldRoundUp =
 41689514            remainder > half ||
 41689515            (remainder == half && (result & 1UL) != 0);
 516
 41689517        if (shouldRoundUp)
 1291518        {
 1291519            ulong incremented = result + 1UL;
 1291520            overflowed |= incremented < result;
 1291521            result = incremented;
 1291522        }
 523
 41689524        return result;
 41689525    }
 526
 527    /// <summary>
 528    /// Multiplies a Fixed64 by an integer.
 529    /// </summary>
 530    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 531    public static Fixed64 operator *(Fixed64 x, int y)
 137532    {
 137533        return x * new Fixed64((long)y << FixedMath.SHIFT_AMOUNT_I);
 137534    }
 535
 536    /// <summary>
 537    /// Multiplies an integer by a Fixed64.
 538    /// </summary>
 539    public static Fixed64 operator *(int x, Fixed64 y)
 14540    {
 14541        return y * x;
 14542    }
 543
 544    /// <summary>
 545    /// Divides one Fixed64 number by another, handling division by zero and overflow.
 546    /// </summary>
 547    public static Fixed64 operator /(Fixed64 x, Fixed64 y)
 3558548    {
 3558549        long xl = x.m_rawValue;
 3558550        long yl = y.m_rawValue;
 551
 3558552        if (yl == 0)
 2553            throw new DivideByZeroException($"Attempted to divide {x} by zero.");
 554
 3556555        ulong remainder = (ulong)(xl < 0 ? -xl : xl);
 3556556        ulong divider = (ulong)(yl < 0 ? -yl : yl);
 3556557        ulong quotient = 0UL;
 3556558        int bitPos = FixedMath.SHIFT_AMOUNT_I + 1;
 559
 560        // If the divider is divisible by 2^n, take advantage of it.
 21026561        while ((divider & 0xF) == 0 && bitPos >= 4)
 17470562        {
 17470563            divider >>= 4;
 17470564            bitPos -= 4;
 17470565        }
 566
 7644567        while (remainder != 0 && bitPos >= 0)
 4091568        {
 4091569            int shift = CountLeadingZeroes(remainder);
 4091570            if (shift > bitPos)
 2881571                shift = bitPos;
 572
 4091573            remainder <<= shift;
 4091574            bitPos -= shift;
 575
 4091576            ulong div = remainder / divider;
 4091577            remainder %= divider;
 4091578            quotient += div << bitPos;
 579
 580            // Detect overflow
 4091581            if ((div & ~(0xFFFFFFFFFFFFFFFF >> bitPos)) != 0)
 3582                return ((xl ^ yl) & FixedMath.MIN_VALUE_L) == 0
 3583                    ? new Fixed64(FixedMath.MAX_VALUE_L)
 3584                    : new Fixed64(FixedMath.MIN_VALUE_L);
 585
 4088586            remainder <<= 1;
 4088587            --bitPos;
 4088588        }
 589
 590        // Rounding logic: "Round half to even" or "Banker's rounding"
 3553591        if ((quotient & 0x1) != 0)
 746592            quotient += 1;
 593
 3553594        long result = (long)(quotient >> 1);
 3553595        if (((xl ^ yl) & FixedMath.MIN_VALUE_L) != 0)
 569596            result = -result;
 597
 3553598        return new Fixed64(result);
 3556599    }
 600
 601    /// <summary>
 602    /// Divides a Fixed64 by an integer.
 603    /// </summary>
 604    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 605    public static Fixed64 operator /(Fixed64 x, int y)
 107606    {
 107607        return x / new Fixed64((long)y << FixedMath.SHIFT_AMOUNT_I);
 107608    }
 609
 610    /// <summary>
 611    /// Divides an integer by a Fixed64
 612    /// </summary>
 613    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 614    public static Fixed64 operator /(int y, Fixed64 x)
 1615    {
 1616        return new Fixed64((long)y << FixedMath.SHIFT_AMOUNT_I) / x;
 1617    }
 618
 619    /// <summary>
 620    /// Computes the remainder of division of one Fixed64 number by another.
 621    /// </summary>
 622    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 623    public static Fixed64 operator %(Fixed64 x, Fixed64 y)
 195624    {
 195625        if (x.m_rawValue == FixedMath.MIN_VALUE_L && y.m_rawValue == -1)
 1626            return Zero;
 194627        return new Fixed64(x.m_rawValue % y.m_rawValue);
 195628    }
 629
 630    /// <summary>
 631    /// Unary negation operator.
 632    /// </summary>
 633    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 634    public static Fixed64 operator -(Fixed64 x)
 3368635    {
 3368636        return x.m_rawValue == FixedMath.MIN_VALUE_L
 3368637            ? new Fixed64(FixedMath.MAX_VALUE_L)
 3368638            : new Fixed64(-x.m_rawValue);
 3368639    }
 640
 641    /// <summary>
 642    /// Pre-increment operator (++x).
 643    /// </summary>
 644    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 645    public static Fixed64 operator ++(Fixed64 a)
 1646    {
 1647        a.m_rawValue += One.m_rawValue;
 1648        return a;
 1649    }
 650
 651    /// <summary>
 652    /// Pre-decrement operator (--x).
 653    /// </summary>
 654    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 655    public static Fixed64 operator --(Fixed64 a)
 1656    {
 1657        a.m_rawValue -= One.m_rawValue;
 1658        return a;
 1659    }
 660
 661    /// <summary>
 662    /// Bitwise left shift operator.
 663    /// </summary>
 664    /// <param name="a">Operand to shift.</param>
 665    /// <param name="shift">Number of bits to shift.</param>
 666    /// <returns>The shifted value.</returns>
 667    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 668    public static Fixed64 operator <<(Fixed64 a, int shift)
 1669    {
 1670        return new Fixed64(a.m_rawValue << shift);
 1671    }
 672
 673    /// <summary>
 674    /// Bitwise right shift operator.
 675    /// </summary>
 676    /// <param name="a">Operand to shift.</param>
 677    /// <param name="shift">Number of bits to shift.</param>
 678    /// <returns>The shifted value.</returns>
 679    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 680    public static Fixed64 operator >>(Fixed64 a, int shift)
 1681    {
 1682        return new Fixed64(a.m_rawValue >> shift);
 1683    }
 684
 685    #endregion
 686
 687    #region Comparison Operators
 688
 689    /// <summary>
 690    /// Determines whether one Fixed64 is greater than another.
 691    /// </summary>
 692    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 693    public static bool operator >(Fixed64 x, Fixed64 y)
 5696694    {
 5696695        return x.m_rawValue > y.m_rawValue;
 5696696    }
 697
 698    /// <summary>
 699    /// Determines whether one Fixed64 is less than another.
 700    /// </summary>
 701    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 702    public static bool operator <(Fixed64 x, Fixed64 y)
 17019703    {
 17019704        return x.m_rawValue < y.m_rawValue;
 17019705    }
 706
 707    /// <summary>
 708    /// Determines whether one Fixed64 is greater than or equal to another.
 709    /// </summary>
 710    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 711    public static bool operator >=(Fixed64 x, Fixed64 y)
 15140712    {
 15140713        return x.m_rawValue >= y.m_rawValue;
 15140714    }
 715
 716    /// <summary>
 717    /// Determines whether one Fixed64 is less than or equal to another.
 718    /// </summary>
 719    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 720    public static bool operator <=(Fixed64 x, Fixed64 y)
 7514721    {
 7514722        return x.m_rawValue <= y.m_rawValue;
 7514723    }
 724
 725    /// <summary>
 726    /// Determines whether two Fixed64 instances are equal.
 727    /// </summary>
 728    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 729    public static bool operator ==(Fixed64 left, Fixed64 right)
 6748730    {
 6748731        return left.Equals(right);
 6748732    }
 733
 734    /// <summary>
 735    /// Determines whether two Fixed64 instances are not equal.
 736    /// </summary>
 737    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 738    public static bool operator !=(Fixed64 left, Fixed64 right)
 688739    {
 688740        return !left.Equals(right);
 688741    }
 742
 743    #endregion
 744
 745    #region Conversion
 746
 747    /// <summary>
 748    /// Returns the string representation of this Fixed64 instance.
 749    /// </summary>
 750    /// <remarks>
 751    /// Up to 10 decimal places.
 752    /// </remarks>
 753    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 754    public override string ToString()
 2952755    {
 2952756        return ((double)this).ToString(CultureInfo.InvariantCulture);
 2952757    }
 758
 759    /// <summary>
 760    /// Converts the numeric value of the current Fixed64 object to its equivalent string representation.
 761    /// </summary>
 762    /// <param name="format">A format specification that governs how the current Fixed64 object is converted.</param>
 763    /// <returns>The string representation of the value of the current Fixed64 object.</returns>
 764    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 765    public string ToString(string format)
 1766    {
 1767        return ((double)this).ToString(format, CultureInfo.InvariantCulture);
 1768    }
 769
 770    /// <summary>
 771    /// Parses a string to create a Fixed64 instance.
 772    /// </summary>
 773    /// <param name="s">The string representation of the </param>
 774    /// <returns>The parsed Fixed64 value.</returns>
 775    public static Fixed64 Parse(string s)
 5776    {
 7777        if (string.IsNullOrEmpty(s)) throw new ArgumentNullException(nameof(s));
 778
 779        // Check if the value is negative
 3780        bool isNegative = false;
 3781        if (s[0] == '-')
 1782        {
 1783            isNegative = true;
 1784            s = s.Substring(1);
 1785        }
 786
 3787        if (!long.TryParse(s, out long rawValue))
 1788            throw new FormatException($"Invalid format: {s}");
 789
 790        // If the value was negative, negate the result
 2791        if (isNegative)
 1792            rawValue = -rawValue;
 793
 2794        return FromRaw(rawValue);
 2795    }
 796
 797    /// <summary>
 798    /// Tries to parse a string to create a Fixed64 instance.
 799    /// </summary>
 800    /// <param name="s">The string representation of the </param>
 801    /// <param name="result">The parsed Fixed64 value.</param>
 802    /// <returns>True if parsing succeeded; otherwise, false.</returns>
 803    public static bool TryParse(string s, out Fixed64 result)
 4804    {
 4805        result = Zero;
 5806        if (string.IsNullOrEmpty(s)) return false;
 807
 808        // Check if the value is negative
 3809        bool isNegative = false;
 3810        if (s[0] == '-')
 1811        {
 1812            isNegative = true;
 1813            s = s.Substring(1);
 1814        }
 815
 4816        if (!long.TryParse(s, out long rawValue)) return false;
 817
 818        // If the value was negative, negate the result
 2819        if (isNegative)
 1820            rawValue = -rawValue;
 821
 2822        result = FromRaw(rawValue);
 2823        return true;
 4824    }
 825
 826    /// <summary>
 827    /// Creates a Fixed64 from a raw long value.
 828    /// </summary>
 829    /// <param name="rawValue">The raw long value.</param>
 830    /// <returns>A Fixed64 representing the raw value.</returns>
 831    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 832    public static Fixed64 FromRaw(long rawValue)
 17423833    {
 17423834        return new Fixed64(rawValue);
 17423835    }
 836
 837    /// <summary>
 838    /// Converts a Fixed64s RawValue (Int64) into a double
 839    /// </summary>
 840    /// <param name="f1"></param>
 841    /// <returns></returns>
 842    public static double ToDouble(long f1)
 1843    {
 1844        return f1 * FixedMath.SCALE_FACTOR_D;
 1845    }
 846
 847    /// <summary>
 848    /// Converts a Fixed64s RawValue (Int64) into a float
 849    /// </summary>
 850    /// <param name="f1"></param>
 851    /// <returns></returns>
 852    public static float ToFloat(long f1)
 1853    {
 1854        return f1 * FixedMath.SCALE_FACTOR_F;
 1855    }
 856
 857    /// <summary>
 858    /// Converts a Fixed64s RawValue (Int64) into a decimal
 859    /// </summary>
 860    /// <param name="f1"></param>
 861    /// <returns></returns>
 862    public static decimal ToDecimal(long f1)
 1863    {
 1864        return f1 * FixedMath.SCALE_FACTOR_M;
 1865    }
 866
 867    #endregion
 868
 869    #region Equality, HashCode, Comparable Overrides
 870
 871    /// <summary>
 872    /// Determines whether this instance equals another object.
 873    /// </summary>
 874    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 875    public override bool Equals(object? obj)
 20876    {
 20877        return obj is Fixed64 other && Equals(other);
 20878    }
 879
 880    /// <inheritdoc/>
 881    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 882    public bool Equals(Fixed64 other)
 10039883    {
 10039884        return m_rawValue == other.m_rawValue;
 10039885    }
 886
 887    /// <inheritdoc/>
 888    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 889    public bool Equals(Fixed64 x, Fixed64 y)
 5890    {
 5891        return x.Equals(y);
 5892    }
 893
 894    /// <inheritdoc/>
 895    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 896    public override int GetHashCode()
 145897    {
 145898        return m_rawValue.GetHashCode();
 145899    }
 900
 901    /// <inheritdoc/>
 902    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 903    public int GetHashCode(Fixed64 obj)
 1904    {
 1905        return obj.GetHashCode();
 1906    }
 907
 908    /// <summary>
 909    /// Compares this instance to another
 910    /// </summary>
 911    /// <param name="other">The Fixed64 to compare with.</param>
 912    /// <returns>-1 if less than, 0 if equal, 1 if greater than other.</returns>
 913    public int CompareTo(Fixed64 other)
 41914    {
 41915        return m_rawValue.CompareTo(other.m_rawValue);
 41916    }
 917
 918    #endregion
 919}

Methods/Properties

.ctor(System.Int64)
.ctor(System.Int32)
.ctor(System.Double)
Offset(System.Int32)
RawToString()
Fraction(System.Double,System.Double)
PostIncrement(FixedMathSharp.Fixed64&)
PostDecrement(FixedMathSharp.Fixed64&)
CountLeadingZeroes(System.UInt64)
Sign(FixedMathSharp.Fixed64)
IsInteger(FixedMathSharp.Fixed64)
op_Explicit(System.Int64)
op_Explicit(FixedMathSharp.Fixed64)
op_Explicit(System.Int32)
op_Explicit(FixedMathSharp.Fixed64)
op_Explicit(System.Single)
op_Explicit(FixedMathSharp.Fixed64)
op_Explicit(System.Double)
op_Explicit(FixedMathSharp.Fixed64)
op_Explicit(System.Decimal)
op_Explicit(FixedMathSharp.Fixed64)
op_Addition(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
op_Addition(FixedMathSharp.Fixed64,System.Int32)
op_Addition(System.Int32,FixedMathSharp.Fixed64)
op_Subtraction(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
op_Subtraction(FixedMathSharp.Fixed64,System.Int32)
op_Subtraction(System.Int32,FixedMathSharp.Fixed64)
op_Multiply(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
AbsToUInt64(System.Int64)
Multiply64To128(System.UInt64,System.UInt64,System.UInt64&,System.UInt64&)
ShiftRightRoundedToEven(System.UInt64,System.UInt64,System.Int32,System.Boolean&)
op_Multiply(FixedMathSharp.Fixed64,System.Int32)
op_Multiply(System.Int32,FixedMathSharp.Fixed64)
op_Division(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
op_Division(FixedMathSharp.Fixed64,System.Int32)
op_Division(System.Int32,FixedMathSharp.Fixed64)
op_Modulus(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
op_UnaryNegation(FixedMathSharp.Fixed64)
op_Increment(FixedMathSharp.Fixed64)
op_Decrement(FixedMathSharp.Fixed64)
op_LeftShift(FixedMathSharp.Fixed64,System.Int32)
op_RightShift(FixedMathSharp.Fixed64,System.Int32)
op_GreaterThan(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
op_LessThan(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
op_GreaterThanOrEqual(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
op_LessThanOrEqual(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
op_Equality(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
op_Inequality(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
ToString()
ToString(System.String)
Parse(System.String)
TryParse(System.String,FixedMathSharp.Fixed64&)
FromRaw(System.Int64)
ToDouble(System.Int64)
ToFloat(System.Int64)
ToDecimal(System.Int64)
Equals(System.Object)
Equals(FixedMathSharp.Fixed64)
Equals(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
GetHashCode()
GetHashCode(FixedMathSharp.Fixed64)
CompareTo(FixedMathSharp.Fixed64)