Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Updating Math.Round and MathF.Round to be IEEE compliant so that the intrinsic and managed form are deterministic. #25901

Merged
merged 3 commits into from Aug 5, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
42 changes: 42 additions & 0 deletions THIRD-PARTY-NOTICES.TXT
Expand Up @@ -281,3 +281,45 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

License notice for Berkeley SoftFloat Release 3e
------------------------------------------------

https://github.com/ucb-bar/berkeley-softfloat-3
https://github.com/ucb-bar/berkeley-softfloat-3/blob/master/COPYING.txt

License for Berkeley SoftFloat Release 3e

John R. Hauser
2018 January 20

The following applies to the whole of SoftFloat Release 3e as well as to
each source file individually.

Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the
University of California. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions, and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions, and the following disclaimer in the
documentation and/or other materials provided with the distribution.

3. Neither the name of the University nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS", AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 changes: 29 additions & 0 deletions src/System.Private.CoreLib/shared/System/Double.cs
Expand Up @@ -12,6 +12,7 @@
**
===========================================================*/

using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -44,6 +45,29 @@ namespace System
// We use this explicit definition to avoid the confusion between 0.0 and -0.0.
internal const double NegativeZero = -0.0;

//
// Constants for manipulating the private bit-representation
//

internal const ulong SignMask = 0x8000_0000_0000_0000;
internal const int SignShift = 63;
internal const int ShiftedSignMask = (int)(SignMask >> SignShift);

internal const ulong ExponentMask = 0x7FF0_0000_0000_0000;
internal const int ExponentShift = 52;
internal const int ShiftedExponentMask = (int)(ExponentMask >> ExponentShift);

internal const ulong SignificandMask = 0x000F_FFFF_FFFF_FFFF;

internal const byte MinSign = 0;
internal const byte MaxSign = 1;

internal const ushort MinExponent = 0x0000;
internal const ushort MaxExponent = 0x07FF;

internal const ulong MinSignificand = 0x0000_0000_0000_0000;
internal const ulong MaxSignificand = 0x000F_FFFF_FFFF_FFFF;

/// <summary>Determines whether the specified value is finite (zero, subnormal, or normal).</summary>
[NonVersionable]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down Expand Up @@ -115,6 +139,11 @@ public static unsafe bool IsSubnormal(double d)
return (bits < 0x7FF0000000000000) && (bits != 0) && ((bits & 0x7FF0000000000000) == 0);
}

internal static int ExtractExponentFromBits(ulong bits)
{
return (int)(bits >> ExponentShift) & ShiftedExponentMask;
}

// Compares this object to another object, returning an instance of System.Relation.
// Null is considered less than any instance.
//
Expand Down
57 changes: 46 additions & 11 deletions src/System.Private.CoreLib/shared/System/Math.cs
Expand Up @@ -2,6 +2,10 @@
// 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.
// ===================================================================================================

/*============================================================
**
**
Expand All @@ -15,7 +19,6 @@

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;

Expand Down Expand Up @@ -802,29 +805,61 @@ public static decimal Round(decimal d, int decimals, MidpointRounding mode)
public static double Round(double a)
{
// ************************************************************************************
// IMPORTANT: Do not change this implementation without also updating Math.Round(double),
// IMPORTANT: Do not change this implementation without also updating MathF.Round(float),
// 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
// This is based on the 'Berkeley SoftFloat Release 3e' algorithm
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference: https://github.com/ucb-bar/berkeley-softfloat-3/blob/master/source/f64_roundToInt.c

The irrelevant support for the other rounding modes and error reporting was not included.
Code comments were added for readability, they do not exist in the reference.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth adding that note (i.e. that the other rounding modes from that algorithm are not included).


ulong bits = (ulong)BitConverter.DoubleToInt64Bits(a);
int exponent = double.ExtractExponentFromBits(bits);

if (exponent <= 0x03FE)
{
// Any value less than or equal to 0.5 will always round to exactly zero.
// However, we need to preserve the original sign for IEEE compliance.

return CopySign(0, a);
}

if (a == (double)((long)a))
if (exponent >= 0x0433)
{
// Any value greater than or equal to 2^52 cannot have a fractional part,
// So it will always round to exactly itself.

return a;
}

// We had a number that was equally close to 2 integers.
// We need to return the even one.
// The absolute value should be greater than or equal to 1.0 and less than 2^52
Debug.Assert((0x03FF <= exponent) && (exponent <= 0x0432));

// Determine the last bit that represents the integral portion of the value
// and the bits representing the fractional portion

double flrTempVal = Floor(a + 0.5);
ulong lastBitMask = 1UL << (0x0433 - exponent);
ulong roundBitsMask = lastBitMask - 1;

if ((a == (Floor(a) + 0.5)) && (FMod(flrTempVal, 2.0) != 0))
// Increment the first fractional bit, which represents the midpoint between
// two integral values in the current window.

bits += lastBitMask >> 1;

if ((bits & roundBitsMask) == 0)
{
flrTempVal -= 1.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 CopySign(flrTempVal, a);
return BitConverter.Int64BitsToDouble((long)bits);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
59 changes: 48 additions & 11 deletions src/System.Private.CoreLib/shared/System/MathF.cs
Expand Up @@ -2,6 +2,10 @@
// 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
Expand All @@ -10,6 +14,7 @@

//This class contains only static members and doesn't require serialization.

using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace System
Expand Down Expand Up @@ -245,29 +250,61 @@ public static float MinMagnitude(float x, float y)
public static float Round(float x)
{
// ************************************************************************************
// IMPORTANT: Do not change this implementation without also updating Math.Round(double),
// IMPORTANT: Do not change this implementation without also updating MathF.Round(float),
// 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))
// This is based on the 'Berkeley SoftFloat Release 3e' algorithm
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference: https://github.com/ucb-bar/berkeley-softfloat-3/blob/master/source/f32_roundToInt.c

The irrelevant support for the other rounding modes and error reporting was not included.
Code comments were added for readability, they do not exist in the reference.


uint bits = (uint)BitConverter.SingleToInt32Bits(x);
int exponent = float.ExtractExponentFromBits(bits);

if (exponent <= 0x7E)
{
// Any value less than or equal to 0.5 will always round to exactly zero.
// However, we need to preserve the original sign for IEEE compliance.

return CopySign(0, 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;
}

// We had a number that was equally close to 2 integers.
// We need to return the even one.
// The absolute value should be greater than or equal to 1.0 and less than 2^23
Debug.Assert((0x7F <= exponent) && (exponent <= 0x95));

float flrTempVal = Floor(x + 0.5f);
// Determine the last bit that represents the integral portion of the value
// and the bits representing the fractional portion

if ((x == (Floor(x) + 0.5f)) && (FMod(flrTempVal, 2.0f) != 0))
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
{
flrTempVal -= 1.0f;
// Otherwise, we just want to strip the fractional bits off, truncating
// to the current integer value.

bits &= ~roundBitsMask;
}

return CopySign(flrTempVal, x);
return BitConverter.Int32BitsToSingle((int)bits);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
28 changes: 28 additions & 0 deletions src/System.Private.CoreLib/shared/System/Single.cs
Expand Up @@ -40,6 +40,29 @@ namespace System
// We use this explicit definition to avoid the confusion between 0.0 and -0.0.
internal const float NegativeZero = (float)-0.0;

//
// Constants for manipulating the private bit-representation
//

internal const uint SignMask = 0x8000_0000;
internal const int SignShift = 31;
internal const int ShiftedSignMask = (int)(SignMask >> SignShift);

internal const ulong ExponentMask = 0x7F80_0000;
internal const int ExponentShift = 23;
internal const int ShiftedExponentMask = (int)(ExponentMask >> ExponentShift);

internal const ulong SignificandMask = 0x007F_FFFF;

internal const byte MinSign = 0;
internal const byte MaxSign = 1;

internal const byte MinExponent = 0x00;
internal const byte MaxExponent = 0xFF;

internal const uint MinSignificand = 0x0000_0000;
internal const uint MaxSignificand = 0x007F_FFFF;

/// <summary>Determines whether the specified value is finite (zero, subnormal, or normal).</summary>
[NonVersionable]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down Expand Up @@ -111,6 +134,11 @@ public static unsafe bool IsSubnormal(float f)
return (bits < 0x7F800000) && (bits != 0) && ((bits & 0x7F800000) == 0);
}

internal static int ExtractExponentFromBits(uint bits)
{
return (int)(bits >> ExponentShift) & ShiftedExponentMask;
}

// Compares this object to another object, returning an integer that
// indicates the relationship.
// Returns a value less than zero if this object
Expand Down