Skip to content

New API for single-precision math #14353

@tannergooding

Description

@tannergooding

Rationale

The .NET framework does not currently provide scalar single-precision floating-point support for many of the trigonometric, logarithmic, and other common mathematical functions.

Single-precision floating-point support should be provided for these mathematical functions in order to better interop with high-performance, scientific, and multimedia-based applications where single-precision floating-points are the only implementation required and/or used.

Since adding these methods to the existing System.Math class would break backwards compatibility, they should be provided through a separate class that provides the same and/or similar functionality.

Providing these new APIs would:

  • improve code readability (especially in the non-hardware backed implementations of methods in the System.Numerics.Vector structs)
  • improve code performance in areas where the single-precision implementation is all that is required
  • provide better interoperability with high-performance, scientific, and multimedia-based applications where data structures and existing code may be dependent on single-precision implementations of such methods.
  • provide a more advanced infrastructure for mathematics in the CoreCLR

Proposed API

The proposed API here provides feature-parity with the existing double-precision math functions provided by the framework.

System.BitConverter

public static class BitConverter
{
    public static float Int32BitsToSingle(int);
    public static int SingleToInt32Bits(float);
}

System.Single

public static partial class MathF
{
    public const float E = 2.71828183f;
    public const float PI = 3.14159265f;

    // Trigonometric Functions
    public static float Acos(float);
    public static float Asin(float);
    public static float Atan(float);
    public static float Atan2(float, float);
    public static float Cos(float);
    public static float Sin(float);
    public static float Tan(float);

    // Hyperbolic Functions
    public static float Cosh(float);
    public static float Sinh(float);
    public static float Tanh(float);

    // Exponential Functions
    public static float Exp(float);

    // Logarithmic Functions
    public static float Log(float);
    public static float Log(float, float);
    public static float Log10(float);

    // Power Functions
    public static float Pow(float, float);
    public static float Sqrt(float);

    // Rounding Functions
    public static float Ceiling(float);
    public static float Floor(float);
    public static float Round(float);
    public static float Round(float, int);
    public static float Round(float, int, MidpointRounding);
    public static float Round(float, MidpointRounding);
    public static float Truncate(float);

    // Manipulation Functions
    public static int Sign(float);

    // Minimum, Maximum, and Difference Functions
    public static float Max(float, float);
    public static float Min(float, float);

    // Other Functions
    public static float Abs(float);
    public static float IEEERemainder(float, float);
}

Example Usage

Currently to calculate the Tangent for each member of the System.Numerics.Vector4 struct, you currently have to call the double-precision version of the method and cast to the result to a single-precision value:

public static Vector4 Acos(Vector4 value)
{
    return new Vector4((float)(Math.Acos(value.X)),
                       (float)(Math.Acos(value.Y)),
                       (float)(Math.Acos(value.Z)),
                       (float)(Math.Acos(value.W)));
}

With the proposed changes, this would now be simplified to the following:

public static Vector4 Acos(Vector4 value)
{
    return new Vector4(Mathf.Acos(value.X),
                       Mathf.Acos(value.Y),
                       Mathf.Acos(value.Z),
                       Mathf.Acos(value.W));
}

The System.Numerics library itself is filled with similar examples as are various bits of code in the CoreFX and CoreCLR repositories.

Perf Numbers

All performance tests are implemented as follows:

  • 100,000 iterations are executed
  • The time of all iterations are aggregated to compute the Total Time
  • The time of all iterations are averaged to compute the Average Time
  • A single iteration executes some simple operation, using the function under test, 5000 times

The execution time below is the Total Time for all 100,000 iterations, measured in seconds.

Hardware: Desktop w/ 3.7GHz Quad-Core A10-7850K (AMD) and 16GB RAM

Function Improvment Execution Time - Double Execution Time - Single
Abs 0.199243555% 0.63752649s 0.63625626s
Acos 12.30220910% 11.5265412s 10.1085220s
Asin 18.66801808% 11.9472425s 9.71692911s
Atan 21.10350002% 10.9964683s 8.67582861s
Atan2 20.51327307% 24.3328097s 19.3413540s
Ceiling 12.91487191% 1.87116459s 1.62950608s
Cos 5.026665542% 7.19916547s 6.83728750s
Cosh 16.46166555% 13.5416170s 11.3124413s
Exp 33.67586387% 6.65578424s 4.41439140s
Floor 10.39208688% 1.74655247s 1.56504922s
Log 19.81117664% 6.42244806s 5.15008553s
Log10 18.40605725% 6.75118866s 5.50856101s
Pow 47.85595440% 31.8820155s 16.6245727s
Round 0.976398142% 4.22620632s 4.18494172s
Sin 15.49539339% 5.98022268s 5.05356365s
Sinh 17.96609899% 14.6242270s 11.9968239s
Sqrt 4.676516651% 2.51281945s 2.39530703s
Tan 30.33470555% 9.07290178s 6.32066374s
Tanh 0.108182099% 8.12724112s 8.11844890s

I believe some extra perf will be squeezed out when the intrinsics (such as CORINFO_INTRINSIC_Sqrt) are properly implemented in the VM layer for single-precision values. Without such functionality, it falls back to the double-precision functionality (extra precision, reduced performance) for certain calls.

Pull Request

There is a sample pull request covering these changes available: dotnet/coreclr#5492

Additional Details

This will require several changes in the CoreCLR as well to support the new APIs via FCALLs and Intrinsics.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions