// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. /*============================================================ ** ** Purpose: Some single-precision floating-point math operations ** ===========================================================*/ //This class contains only static members and doesn't require serialization. using System.Runtime.CompilerServices; namespace System { public static partial class MathF { public const float E = 2.71828183f; public const float PI = 3.14159265f; private const int maxRoundingDigits = 6; // This table is required for the Round function which can specify the number of digits to round to private static float[] roundPower10Single = new float[] { 1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f, 1e6f }; private const float singleRoundLimit = 1e8f; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Abs(float x) { return Math.Abs(x); } public static float BitDecrement(float x) { var bits = BitConverter.SingleToInt32Bits(x); if ((bits & 0x7F800000) >= 0x7F800000) { // NaN returns NaN // -Infinity returns -Infinity // +Infinity returns float.MaxValue return (bits == 0x7F800000) ? float.MaxValue : x; } if (bits == 0x00000000) { // +0.0 returns -float.Epsilon return -float.Epsilon; } // Negative values need to be incremented // Positive values need to be decremented bits += ((bits < 0) ? +1 : -1); return BitConverter.Int32BitsToSingle(bits); } public static float BitIncrement(float x) { var bits = BitConverter.SingleToInt32Bits(x); if ((bits & 0x7F800000) >= 0x7F800000) { // NaN returns NaN // -Infinity returns float.MinValue // +Infinity returns +Infinity return (bits == unchecked((int)(0xFF800000))) ? float.MinValue : x; } if (bits == unchecked((int)(0x80000000))) { // -0.0 returns float.Epsilon return float.Epsilon; } // Negative values need to be decremented // Positive values need to be incremented bits += ((bits < 0) ? -1 : +1); return BitConverter.Int32BitsToSingle(bits); } public static unsafe float CopySign(float x, float y) { // This method is required to work for all inputs, // including NaN, so we operate on the raw bits. var xbits = BitConverter.SingleToInt32Bits(x); var ybits = BitConverter.SingleToInt32Bits(y); // If the sign bits of x and y are not the same, // flip the sign bit of x and return the new value; // otherwise, just return x if ((xbits ^ ybits) < 0) { return BitConverter.Int32BitsToSingle(xbits ^ int.MinValue); } return x; } public static float IEEERemainder(float x, float y) { if (float.IsNaN(x)) { return x; // IEEE 754-2008: NaN payload must be preserved } if (float.IsNaN(y)) { return y; // IEEE 754-2008: NaN payload must be preserved } var regularMod = x % y; if (float.IsNaN(regularMod)) { return float.NaN; } if ((regularMod == 0) && float.IsNegative(x)) { return float.NegativeZero; } var alternativeResult = (regularMod - (Abs(y) * Sign(x))); if (Abs(alternativeResult) == Abs(regularMod)) { var divisionResult = x / y; var roundedResult = Round(divisionResult); if (Abs(roundedResult) > Abs(divisionResult)) { return alternativeResult; } else { return regularMod; } } if (Abs(alternativeResult) < Abs(regularMod)) { return alternativeResult; } else { return regularMod; } } public static float Log(float x, float y) { if (float.IsNaN(x)) { return x; // IEEE 754-2008: NaN payload must be preserved } if (float.IsNaN(y)) { return y; // IEEE 754-2008: NaN payload must be preserved } if (y == 1) { return float.NaN; } if ((x != 1) && ((y == 0) || float.IsPositiveInfinity(y))) { return float.NaN; } return Log(x) / Log(y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Max(float x, float y) { return Math.Max(x, y); } public static float MaxMagnitude(float x, float y) { // This matches the IEEE 754:2019 `maximumMagnitude` function // // It propagates NaN inputs back to the caller and // otherwise returns the input with a larger magnitude. // It treats +0 as larger than -0 as per the specification. float ax = Abs(x); float ay = Abs(y); if ((ax > ay) || float.IsNaN(ax)) { return x; } if (ax == ay) { return float.IsNegative(x) ? y : x; } return y; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Min(float x, float y) { return Math.Min(x, y); } public static float MinMagnitude(float x, float y) { // This matches the IEEE 754:2019 `minimumMagnitude` function // // It propagates NaN inputs back to the caller and // otherwise returns the input with a larger magnitude. // It treats +0 as larger than -0 as per the specification. float ax = Abs(x); float ay = Abs(y); if ((ax < ay) || float.IsNaN(ax)) { return x; } if (ax == ay) { return float.IsNegative(x) ? x : y; } return y; } [Intrinsic] public static float Round(float x) { // ************************************************************************************ // IMPORTANT: Do not change this implementation without also updating Math.Round(double), // FloatingPointUtils::round(double), and FloatingPointUtils::round(float) // ************************************************************************************ // If the number has no fractional part do nothing // This shortcut is necessary to workaround precision loss in borderline cases on some platforms if (x == (float)((int)x)) { return x; } // We had a number that was equally close to 2 integers. // We need to return the even one. float flrTempVal = Floor(x + 0.5f); if ((x == (Floor(x) + 0.5f)) && (FMod(flrTempVal, 2.0f) != 0)) { flrTempVal -= 1.0f; } return CopySign(flrTempVal, x); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Round(float x, int digits) { return Round(x, digits, MidpointRounding.ToEven); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Round(float x, MidpointRounding mode) { return Round(x, 0, mode); } public static unsafe float Round(float x, int digits, MidpointRounding mode) { if ((digits < 0) || (digits > maxRoundingDigits)) { throw new ArgumentOutOfRangeException(nameof(digits), SR.ArgumentOutOfRange_RoundingDigits); } if (mode < MidpointRounding.ToEven || mode > MidpointRounding.ToPositiveInfinity) { throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, mode, nameof(MidpointRounding)), nameof(mode)); } if (Abs(x) < singleRoundLimit) { var power10 = roundPower10Single[digits]; x *= power10; switch (mode) { // Rounds to the nearest value; if the number falls midway, // it is rounded to the nearest value with an even least significant digit case MidpointRounding.ToEven: { x = Round(x); break; } // Rounds to the nearest value; if the number falls midway, // it is rounded to the nearest value above (for positive numbers) or below (for negative numbers) case MidpointRounding.AwayFromZero: { float fraction = ModF(x, &x); if (Abs(fraction) >= 0.5) { x += Sign(fraction); } break; } // Directed rounding: Round to the nearest value, toward to zero case MidpointRounding.ToZero: { x = Truncate(x); break; } // Directed Rounding: Round down to the next value, toward negative infinity case MidpointRounding.ToNegativeInfinity: { x = Floor(x); break; } // Directed rounding: Round up to the next value, toward positive infinity case MidpointRounding.ToPositiveInfinity: { x = Ceiling(x); break; } default: { throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, mode, nameof(MidpointRounding)), nameof(mode)); } } x /= power10; } return x; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Sign(float x) { return Math.Sign(x); } public static unsafe float Truncate(float x) { ModF(x, &x); return x; } } }