1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
4 // ===================================================================================================
5 // Portions of the code implemented below are based on the 'Berkeley SoftFloat Release 3e' algorithms.
6 // ===================================================================================================
8 /*============================================================
10 ** Purpose: Some single-precision floating-point math operations
12 ===========================================================*/
14 using System.Diagnostics;
15 using System.Numerics;
16 using System.Runtime.CompilerServices;
17 using System.Runtime.Intrinsics;
18 using System.Runtime.Intrinsics.X86;
19 using System.Runtime.Intrinsics.Arm;
23 public static partial class MathF
25 public const float E = 2.71828183f;
27 public const float PI = 3.14159265f;
29 public const float Tau = 6.283185307f;
31 private const int maxRoundingDigits = 6;
33 // This table is required for the Round function which can specify the number of digits to round to
34 private static readonly float[] roundPower10Single = new float[] {
35 1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f, 1e6f
38 private const float singleRoundLimit = 1e8f;
40 private const float SCALEB_C1 = 1.7014118E+38f; // 0x1p127f
42 private const float SCALEB_C2 = 1.1754944E-38f; // 0x1p-126f
44 private const float SCALEB_C3 = 16777216f; // 0x1p24f
46 [MethodImpl(MethodImplOptions.AggressiveInlining)]
47 public static float Abs(float x)
52 public static float BitDecrement(float x)
54 int bits = BitConverter.SingleToInt32Bits(x);
56 if ((bits & 0x7F800000) >= 0x7F800000)
59 // -Infinity returns -Infinity
60 // +Infinity returns float.MaxValue
61 return (bits == 0x7F800000) ? float.MaxValue : x;
64 if (bits == 0x00000000)
66 // +0.0 returns -float.Epsilon
67 return -float.Epsilon;
70 // Negative values need to be incremented
71 // Positive values need to be decremented
73 bits += ((bits < 0) ? +1 : -1);
74 return BitConverter.Int32BitsToSingle(bits);
77 public static float BitIncrement(float x)
79 int bits = BitConverter.SingleToInt32Bits(x);
81 if ((bits & 0x7F800000) >= 0x7F800000)
84 // -Infinity returns float.MinValue
85 // +Infinity returns +Infinity
86 return (bits == unchecked((int)(0xFF800000))) ? float.MinValue : x;
89 if (bits == unchecked((int)(0x80000000)))
91 // -0.0 returns float.Epsilon
95 // Negative values need to be decremented
96 // Positive values need to be incremented
98 bits += ((bits < 0) ? -1 : +1);
99 return BitConverter.Int32BitsToSingle(bits);
102 [MethodImpl(MethodImplOptions.AggressiveInlining)]
103 public static float CopySign(float x, float y)
105 if (Sse.IsSupported || AdvSimd.IsSupported)
107 return VectorMath.ConditionalSelectBitwise(Vector128.CreateScalarUnsafe(-0.0f), Vector128.CreateScalarUnsafe(y), Vector128.CreateScalarUnsafe(x)).ToScalar();
111 return SoftwareFallback(x, y);
114 static float SoftwareFallback(float x, float y)
116 const int signMask = 1 << 31;
118 // This method is required to work for all inputs,
119 // including NaN, so we operate on the raw bits.
120 int xbits = BitConverter.SingleToInt32Bits(x);
121 int ybits = BitConverter.SingleToInt32Bits(y);
123 // Remove the sign from x, and remove everything but the sign from y
127 // Simply OR them to get the correct sign
128 return BitConverter.Int32BitsToSingle(xbits | ybits);
132 public static float IEEERemainder(float x, float y)
136 return x; // IEEE 754-2008: NaN payload must be preserved
141 return y; // IEEE 754-2008: NaN payload must be preserved
144 float regularMod = x % y;
146 if (float.IsNaN(regularMod))
151 if ((regularMod == 0) && float.IsNegative(x))
153 return float.NegativeZero;
156 float alternativeResult = (regularMod - (Abs(y) * Sign(x)));
158 if (Abs(alternativeResult) == Abs(regularMod))
160 float divisionResult = x / y;
161 float roundedResult = Round(divisionResult);
163 if (Abs(roundedResult) > Abs(divisionResult))
165 return alternativeResult;
173 if (Abs(alternativeResult) < Abs(regularMod))
175 return alternativeResult;
183 public static float Log(float x, float y)
187 return x; // IEEE 754-2008: NaN payload must be preserved
192 return y; // IEEE 754-2008: NaN payload must be preserved
200 if ((x != 1) && ((y == 0) || float.IsPositiveInfinity(y)))
205 return Log(x) / Log(y);
208 [MethodImpl(MethodImplOptions.AggressiveInlining)]
209 public static float Max(float x, float y)
211 return Math.Max(x, y);
214 public static float MaxMagnitude(float x, float y)
216 // This matches the IEEE 754:2019 `maximumMagnitude` function
218 // It propagates NaN inputs back to the caller and
219 // otherwise returns the input with a larger magnitude.
220 // It treats +0 as larger than -0 as per the specification.
225 if ((ax > ay) || float.IsNaN(ax))
232 return float.IsNegative(x) ? y : x;
238 [MethodImpl(MethodImplOptions.AggressiveInlining)]
239 public static float Min(float x, float y)
241 return Math.Min(x, y);
244 public static float MinMagnitude(float x, float y)
246 // This matches the IEEE 754:2019 `minimumMagnitude` function
248 // It propagates NaN inputs back to the caller and
249 // otherwise returns the input with a larger magnitude.
250 // It treats +0 as larger than -0 as per the specification.
255 if ((ax < ay) || float.IsNaN(ax))
262 return float.IsNegative(x) ? x : y;
268 /// <summary>Returns an estimate of the reciprocal of a specified number.</summary>
269 /// <param name="x">The number whose reciprocal is to be estimated.</param>
270 /// <returns>An estimate of the reciprocal of <paramref name="x" />.</returns>
272 /// <para>On x86/x64 hardware this may use the <c>RCPSS</c> instruction which has a maximum relative error of <c>1.5 * 2^-12</c>.</para>
273 /// <para>On ARM64 hardware this may use the <c>FRECPE</c> instruction which performs a single Newton-Raphson iteration.</para>
274 /// <para>On hardware without specialized support, this may just return <c>1.0 / x</c>.</para>
276 [MethodImpl(MethodImplOptions.AggressiveInlining)]
277 public static float ReciprocalEstimate(float x)
281 return Sse.ReciprocalScalar(Vector128.CreateScalarUnsafe(x)).ToScalar();
283 else if (AdvSimd.Arm64.IsSupported)
285 return AdvSimd.Arm64.ReciprocalEstimateScalar(Vector64.CreateScalarUnsafe(x)).ToScalar();
293 /// <summary>Returns an estimate of the reciprocal square root of a specified number.</summary>
294 /// <param name="x">The number whose reciprocal square root is to be estimated.</param>
295 /// <returns>An estimate of the reciprocal square root <paramref name="x" />.</returns>
297 /// <para>On x86/x64 hardware this may use the <c>RSQRTSS</c> instruction which has a maximum relative error of <c>1.5 * 2^-12</c>.</para>
298 /// <para>On ARM64 hardware this may use the <c>FRSQRTE</c> instruction which performs a single Newton-Raphson iteration.</para>
299 /// <para>On hardware without specialized support, this may just return <c>1.0 / Sqrt(x)</c>.</para>
301 [MethodImpl(MethodImplOptions.AggressiveInlining)]
302 public static float ReciprocalSqrtEstimate(float x)
306 return Sse.ReciprocalSqrtScalar(Vector128.CreateScalarUnsafe(x)).ToScalar();
308 else if (AdvSimd.Arm64.IsSupported)
310 return AdvSimd.Arm64.ReciprocalSquareRootEstimateScalar(Vector64.CreateScalarUnsafe(x)).ToScalar();
314 return 1.0f / Sqrt(x);
319 public static float Round(float x)
321 // ************************************************************************************
322 // IMPORTANT: Do not change this implementation without also updating MathF.Round(float),
323 // FloatingPointUtils::round(double), and FloatingPointUtils::round(float)
324 // ************************************************************************************
326 // This is based on the 'Berkeley SoftFloat Release 3e' algorithm
328 uint bits = (uint)BitConverter.SingleToInt32Bits(x);
329 int exponent = float.ExtractExponentFromBits(bits);
331 if (exponent <= 0x7E)
333 if ((bits << 1) == 0)
335 // Exactly +/- zero should return the original value
339 // Any value less than or equal to 0.5 will always round to exactly zero
340 // and any value greater than 0.5 will always round to exactly one. However,
341 // we need to preserve the original sign for IEEE compliance.
343 float result = ((exponent == 0x7E) && (float.ExtractSignificandFromBits(bits) != 0)) ? 1.0f : 0.0f;
344 return CopySign(result, x);
347 if (exponent >= 0x96)
349 // Any value greater than or equal to 2^23 cannot have a fractional part,
350 // So it will always round to exactly itself.
355 // The absolute value should be greater than or equal to 1.0 and less than 2^23
356 Debug.Assert((0x7F <= exponent) && (exponent <= 0x95));
358 // Determine the last bit that represents the integral portion of the value
359 // and the bits representing the fractional portion
361 uint lastBitMask = 1U << (0x96 - exponent);
362 uint roundBitsMask = lastBitMask - 1;
364 // Increment the first fractional bit, which represents the midpoint between
365 // two integral values in the current window.
367 bits += lastBitMask >> 1;
369 if ((bits & roundBitsMask) == 0)
371 // If that overflowed and the rest of the fractional bits are zero
372 // then we were exactly x.5 and we want to round to the even result
374 bits &= ~lastBitMask;
378 // Otherwise, we just want to strip the fractional bits off, truncating
379 // to the current integer value.
381 bits &= ~roundBitsMask;
384 return BitConverter.Int32BitsToSingle((int)bits);
387 [MethodImpl(MethodImplOptions.AggressiveInlining)]
388 public static float Round(float x, int digits)
390 return Round(x, digits, MidpointRounding.ToEven);
393 [MethodImpl(MethodImplOptions.AggressiveInlining)]
394 public static float Round(float x, MidpointRounding mode)
396 return Round(x, 0, mode);
399 public static unsafe float Round(float x, int digits, MidpointRounding mode)
401 if ((digits < 0) || (digits > maxRoundingDigits))
403 throw new ArgumentOutOfRangeException(nameof(digits), SR.ArgumentOutOfRange_RoundingDigits_MathF);
406 if (mode < MidpointRounding.ToEven || mode > MidpointRounding.ToPositiveInfinity)
408 throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, mode, nameof(MidpointRounding)), nameof(mode));
411 if (Abs(x) < singleRoundLimit)
413 float power10 = roundPower10Single[digits];
419 // Rounds to the nearest value; if the number falls midway,
420 // it is rounded to the nearest value with an even least significant digit
421 case MidpointRounding.ToEven:
426 // Rounds to the nearest value; if the number falls midway,
427 // it is rounded to the nearest value above (for positive numbers) or below (for negative numbers)
428 case MidpointRounding.AwayFromZero:
430 float fraction = ModF(x, &x);
432 if (Abs(fraction) >= 0.5f)
439 // Directed rounding: Round to the nearest value, toward to zero
440 case MidpointRounding.ToZero:
445 // Directed Rounding: Round down to the next value, toward negative infinity
446 case MidpointRounding.ToNegativeInfinity:
451 // Directed rounding: Round up to the next value, toward positive infinity
452 case MidpointRounding.ToPositiveInfinity:
459 throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, mode, nameof(MidpointRounding)), nameof(mode));
469 [MethodImpl(MethodImplOptions.AggressiveInlining)]
470 public static int Sign(float x)
475 public static unsafe float Truncate(float x)
481 public static float ScaleB(float x, int n)
483 // Implementation based on https://git.musl-libc.org/cgit/musl/tree/src/math/scalblnf.c
485 // Performs the calculation x * 2^n efficiently. It constructs a float from 2^n by building
486 // the correct biased exponent. If n is greater than the maximum exponent (127) or less than
487 // the minimum exponent (-126), adjust x and n to compute correct result.
506 y *= SCALEB_C2 * SCALEB_C3;
510 y *= SCALEB_C2 * SCALEB_C3;
519 float u = BitConverter.Int32BitsToSingle(((int)(0x7f + n) << 23));