// 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. // =================================================================================================== // Portions of the code implemented below are based on the 'Berkeley SoftFloat Release 3e' algorithms. // =================================================================================================== /*============================================================ ** ** Purpose: Some single-precision floating-point math operations ** ===========================================================*/ //This class contains only static members and doesn't require serialization. using System.Diagnostics; 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 MathF.Round(float), // FloatingPointUtils::round(double), and FloatingPointUtils::round(float) // ************************************************************************************ // This is based on the 'Berkeley SoftFloat Release 3e' algorithm // This only includes the roundToNearestTiesToEven code paths uint bits = (uint)BitConverter.SingleToInt32Bits(x); int exponent = float.ExtractExponentFromBits(bits); if (exponent <= 0x7E) { if ((bits << 1) == 0) { // Exactly +/- zero should return the original value return x; } // Any value less than or equal to 0.5 will always round to exactly zero // and any value greater than 0.5 will always round to exactly one. However, // we need to preserve the original sign for IEEE compliance. float result = ((exponent == 0x7E) && (float.ExtractSignificandFromBits(bits) != 0)) ? 1.0f : 0.0f; return CopySign(result, x); } if (exponent >= 0x96) { // Any value greater than or equal to 2^23 cannot have a fractional part, // So it will always round to exactly itself. return x; } // The absolute value should be greater than or equal to 1.0 and less than 2^23 Debug.Assert((0x7F <= exponent) && (exponent <= 0x95)); // Determine the last bit that represents the integral portion of the value // and the bits representing the fractional portion uint lastBitMask = 1U << (0x96 - exponent); uint roundBitsMask = lastBitMask - 1; // Increment the first fractional bit, which represents the midpoint between // two integral values in the current window. bits += lastBitMask >> 1; if ((bits & roundBitsMask) == 0) { // If that overflowed and the rest of the fractional bits are zero // then we were exactly x.5 and we want to round to the even result bits &= ~lastBitMask; } else { // Otherwise, we just want to strip the fractional bits off, truncating // to the current integer value. bits &= ~roundBitsMask; } return BitConverter.Int32BitsToSingle((int)bits); } [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; } } }