File: src\runtime\src\libraries\System.Private.CoreLib\src\System\Numerics\IBinaryInteger.cs
Web Access
Project: src\runtime\src\coreclr\nativeaot\System.Private.CoreLib\src\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Numerics
{
    /// <summary>Defines an integer type that is represented in a base-2 format.</summary>
    /// <typeparam name="TSelf">The type that implements the interface.</typeparam>
    public interface IBinaryInteger<TSelf>
        : IBinaryNumber<TSelf>,
          IShiftOperators<TSelf, int, TSelf>
        where TSelf : IBinaryInteger<TSelf>?
    {
        /// <summary>Computes the quotient and remainder of two values.</summary>
        /// <param name="left">The value which <paramref name="right" /> divides.</param>
        /// <param name="right">The value which divides <paramref name="left" />.</param>
        /// <returns>The quotient and remainder of <paramref name="left" /> divided-by <paramref name="right" />.</returns>
        static virtual (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right)
        {
            TSelf quotient = left / right;
            return (quotient, (left - (quotient * right)));
        }

        /// <summary>Computes the quotient and remainder of two values using the specified division rounding mode.</summary>
        /// <param name="left">The value which <paramref name="right" /> divides.</param>
        /// <param name="right">The value which divides <paramref name="left" />.</param>
        /// <param name="mode">The <see cref="DivisionRounding"/> mode.</param>
        /// <returns>The quotient and remainder of <paramref name="left" /> divided-by <paramref name="right" /> with the specified division rounding mode.</returns>
        static virtual (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right, DivisionRounding mode)
        {
            (TSelf quotient, TSelf remainder) = TSelf.DivRem(left, right);

            if (TSelf.IsZero(remainder))
            {
                return (quotient, remainder);
            }

            switch (mode)
            {
                case DivisionRounding.Truncate:
                {
                    break;
                }

                case DivisionRounding.Floor:
                {
                    if (TSelf.IsPositive(left) != TSelf.IsPositive(right))
                    {
                        quotient--;
                        remainder += right;
                    }
                    break;
                }

                case DivisionRounding.Ceiling:
                {
                    if (TSelf.IsPositive(left) == TSelf.IsPositive(right))
                    {
                        quotient++;
                        remainder -= right;
                    }
                    break;
                }

                case DivisionRounding.AwayFromZero:
                {
                    if (TSelf.IsPositive(left) != TSelf.IsPositive(right))
                    {
                        quotient--;
                        remainder += right;
                    }
                    else
                    {
                        quotient++;
                        remainder -= right;
                    }
                    break;
                }

                case DivisionRounding.Euclidean:
                {
                    if (TSelf.IsNegative(left))
                    {
                        if (TSelf.IsPositive(right))
                        {
                            quotient--;
                            remainder += right;
                        }
                        else
                        {
                            quotient++;
                            remainder -= right;
                        }
                    }
                    break;
                }

                default:
                {
                    ThrowHelper.ThrowArgumentException_InvalidEnumValue(mode);
                    break;
                }
            }

            return (quotient, remainder);
        }

        /// <summary>Computes the quotient of two values using the specified division rounding mode.</summary>
        /// <param name="left">The value which <paramref name="right" /> divides.</param>
        /// <param name="right">The value which divides <paramref name="left" />.</param>
        /// <param name="mode">The <see cref="DivisionRounding"/> mode.</param>
        /// <returns>The quotient of <paramref name="left" /> divided-by <paramref name="right" /> with the specified division rounding mode.</returns>
        static virtual TSelf Divide(TSelf left, TSelf right, DivisionRounding mode)
        {
            (TSelf quotient, TSelf remainder) = TSelf.DivRem(left, right);

            if (TSelf.IsZero(remainder))
            {
                return quotient;
            }

            switch (mode)
            {
                case DivisionRounding.Truncate:
                {
                    break;
                }

                case DivisionRounding.Floor:
                {
                    if (TSelf.IsPositive(left) != TSelf.IsPositive(right))
                    {
                        quotient--;
                    }
                    break;
                }

                case DivisionRounding.Ceiling:
                {
                    if (TSelf.IsPositive(left) == TSelf.IsPositive(right))
                    {
                        quotient++;
                    }
                    break;
                }

                case DivisionRounding.AwayFromZero:
                {
                    if (TSelf.IsPositive(left) != TSelf.IsPositive(right))
                    {
                        quotient--;
                    }
                    else
                    {
                        quotient++;
                    }
                    break;
                }

                case DivisionRounding.Euclidean:
                {
                    if (TSelf.IsNegative(left))
                    {
                        if (TSelf.IsPositive(right))
                        {
                            quotient--;
                        }
                        else
                        {
                            quotient++;
                        }
                    }
                    break;
                }

                default:
                {
                    ThrowHelper.ThrowArgumentException_InvalidEnumValue(mode);
                    break;
                }
            }

            return quotient;
        }

        /// <summary>Computes the remainder of two values using the specified division rounding mode.</summary>
        /// <param name="left">The value which <paramref name="right" /> divides.</param>
        /// <param name="right">The value which divides <paramref name="left" />.</param>
        /// <param name="mode">The <see cref="DivisionRounding"/> mode.</param>
        /// <returns>The remainder of <paramref name="left" /> divided-by <paramref name="right" /> with the specified division rounding mode.</returns>
        static virtual TSelf Remainder(TSelf left, TSelf right, DivisionRounding mode)
        {
            TSelf remainder = left % right;

            if (TSelf.IsZero(remainder))
            {
                return remainder;
            }

            switch (mode)
            {
                case DivisionRounding.Truncate:
                {
                    break;
                }

                case DivisionRounding.Floor:
                {
                    if (TSelf.IsPositive(left) != TSelf.IsPositive(right))
                    {
                        remainder += right;
                    }
                    break;
                }

                case DivisionRounding.Ceiling:
                {
                    if (TSelf.IsPositive(left) == TSelf.IsPositive(right))
                    {
                        remainder -= right;
                    }
                    break;
                }

                case DivisionRounding.AwayFromZero:
                {
                    if (TSelf.IsPositive(left) != TSelf.IsPositive(right))
                    {
                        remainder += right;
                    }
                    else
                    {
                        remainder -= right;
                    }
                    break;
                }

                case DivisionRounding.Euclidean:
                {
                    if (TSelf.IsNegative(left))
                    {
                        if (TSelf.IsPositive(right))
                        {
                            remainder += right;
                        }
                        else
                        {
                            remainder -= right;
                        }
                    }
                    break;
                }

                default:
                {
                    ThrowHelper.ThrowArgumentException_InvalidEnumValue(mode);
                    break;
                }
            }

            return remainder;
        }

        /// <summary>Computes the integer logarithm base 10 of a value.</summary>
        /// <param name="value">The value whose integer logarithm base 10 is to be computed.</param>
        /// <returns>The integer logarithm base 10 of <paramref name="value" />.</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="value" /> is negative.</exception>
        /// <remarks>The result of computing the integer logarithm base 10 of zero is zero.</remarks>
        static virtual TSelf Log10(TSelf value)
        {
            if (!typeof(TSelf).IsValueType)
            {
                ArgumentNullException.ThrowIfNull(value);
            }

            if (TSelf.IsNegative(value))
            {
                ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
            }

            TSelf ten = TSelf.CreateChecked(10);
            TSelf result = TSelf.Zero;

            while (value >= ten)
            {
                value /= ten;
                result++;
            }

            return result;
        }

        /// <summary>Computes the number of leading zero bits in a value.</summary>
        /// <param name="value">The value whose leading zero bits are to be counted.</param>
        /// <returns>The number of leading zero bits in <paramref name="value" />.</returns>
        static virtual TSelf LeadingZeroCount(TSelf value)
        {
            if (!typeof(TSelf).IsValueType)
            {
                ArgumentNullException.ThrowIfNull(value);
            }

            TSelf bitCount = TSelf.CreateChecked(value!.GetByteCount() * 8L);

            if (value == TSelf.Zero)
            {
                return TSelf.CreateChecked(bitCount);
            }

            return TSelf.IsNegative(value) ? TSelf.Zero : ((bitCount - TSelf.One) ^ TSelf.Log2(value));
        }

        /// <summary>Computes the number of bits that are set in a value.</summary>
        /// <param name="value">The value whose set bits are to be counted.</param>
        /// <returns>The number of set bits in <paramref name="value" />.</returns>
        static abstract TSelf PopCount(TSelf value);

        /// <summary>Reads a two's complement number from a given array, in big-endian format, and converts it to an instance of the current type.</summary>
        /// <param name="source">The array from which the two's complement number should be read.</param>
        /// <param name="isUnsigned"><c>true</c> if <paramref name="source" /> represents an unsigned two's complement number; otherwise, <c>false</c> to indicate it represents a signed two's complement number.</param>
        /// <returns>The value read from <paramref name="source" />.</returns>
        /// <exception cref="OverflowException"><paramref name="source" /> is not representable by <typeparamref name="TSelf" /></exception>
        static virtual TSelf ReadBigEndian(byte[] source, bool isUnsigned)
        {
            if (!TSelf.TryReadBigEndian(source, isUnsigned, out TSelf value))
            {
                ThrowHelper.ThrowOverflowException();
            }
            return value;
        }

        /// <summary>Reads a two's complement number from a given array, in big-endian format, and converts it to an instance of the current type.</summary>
        /// <param name="source">The array from which the two's complement number should be read.</param>
        /// <param name="startIndex">The starting index from which the value should be read.</param>
        /// <param name="isUnsigned"><c>true</c> if <paramref name="source" /> represents an unsigned two's complement number; otherwise, <c>false</c> to indicate it represents a signed two's complement number.</param>
        /// <returns>The value read from <paramref name="source" /> starting at <paramref name="startIndex" />.</returns>
        /// <exception cref="OverflowException"><paramref name="source" /> is not representable by <typeparamref name="TSelf" /></exception>
        static virtual TSelf ReadBigEndian(byte[] source, int startIndex, bool isUnsigned)
        {
            if (!TSelf.TryReadBigEndian(source.AsSpan(startIndex), isUnsigned, out TSelf value))
            {
                ThrowHelper.ThrowOverflowException();
            }
            return value;
        }

        /// <summary>Reads a two's complement number from a given span, in big-endian format, and converts it to an instance of the current type.</summary>
        /// <param name="source">The array from which the two's complement number should be read.</param>
        /// <param name="isUnsigned"><c>true</c> if <paramref name="source" /> represents an unsigned two's complement number; otherwise, <c>false</c> to indicate it represents a signed two's complement number.</param>
        /// <returns>The value read from <paramref name="source" />.</returns>
        /// <exception cref="OverflowException"><paramref name="source" /> is not representable by <typeparamref name="TSelf" /></exception>
        static virtual TSelf ReadBigEndian(ReadOnlySpan<byte> source, bool isUnsigned)
        {
            if (!TSelf.TryReadBigEndian(source, isUnsigned, out TSelf value))
            {
                ThrowHelper.ThrowOverflowException();
            }
            return value;
        }

        /// <summary>Reads a two's complement number from a given array, in little-endian format, and converts it to an instance of the current type.</summary>
        /// <param name="source">The array from which the two's complement number should be read.</param>
        /// <param name="isUnsigned"><c>true</c> if <paramref name="source" /> represents an unsigned two's complement number; otherwise, <c>false</c> to indicate it represents a signed two's complement number.</param>
        /// <returns>The value read from <paramref name="source" />.</returns>
        /// <exception cref="OverflowException"><paramref name="source" /> is not representable by <typeparamref name="TSelf" /></exception>
        static virtual TSelf ReadLittleEndian(byte[] source, bool isUnsigned)
        {
            if (!TSelf.TryReadLittleEndian(source, isUnsigned, out TSelf value))
            {
                ThrowHelper.ThrowOverflowException();
            }
            return value;
        }

        /// <summary>Reads a two's complement number from a given array, in little-endian format, and converts it to an instance of the current type.</summary>
        /// <param name="source">The array from which the two's complement number should be read.</param>
        /// <param name="startIndex">The starting index from which the value should be read.</param>
        /// <param name="isUnsigned"><c>true</c> if <paramref name="source" /> represents an unsigned two's complement number; otherwise, <c>false</c> to indicate it represents a signed two's complement number.</param>
        /// <returns>The value read from <paramref name="source" /> starting at <paramref name="startIndex" />.</returns>
        /// <exception cref="OverflowException"><paramref name="source" /> is not representable by <typeparamref name="TSelf" /></exception>
        static virtual TSelf ReadLittleEndian(byte[] source, int startIndex, bool isUnsigned)
        {
            if (!TSelf.TryReadLittleEndian(source.AsSpan(startIndex), isUnsigned, out TSelf value))
            {
                ThrowHelper.ThrowOverflowException();
            }
            return value;
        }

        /// <summary>Reads a two's complement number from a given span, in little-endian format, and converts it to an instance of the current type.</summary>
        /// <param name="source">The array from which the two's complement number should be read.</param>
        /// <param name="isUnsigned"><c>true</c> if <paramref name="source" /> represents an unsigned two's complement number; otherwise, <c>false</c> to indicate it represents a signed two's complement number.</param>
        /// <returns>The value read from <paramref name="source" />.</returns>
        /// <exception cref="OverflowException"><paramref name="source" /> is not representable by <typeparamref name="TSelf" /></exception>
        static virtual TSelf ReadLittleEndian(ReadOnlySpan<byte> source, bool isUnsigned)
        {
            if (!TSelf.TryReadLittleEndian(source, isUnsigned, out TSelf value))
            {
                ThrowHelper.ThrowOverflowException();
            }
            return value;
        }

        /// <summary>Rotates a value left by a given amount.</summary>
        /// <param name="value">The value which is rotated left by <paramref name="rotateAmount" />.</param>
        /// <param name="rotateAmount">The amount by which <paramref name="value" /> is rotated left.</param>
        /// <returns>The result of rotating <paramref name="value" /> left by <paramref name="rotateAmount" />.</returns>
        static virtual TSelf RotateLeft(TSelf value, int rotateAmount)
        {
            if (!typeof(TSelf).IsValueType)
            {
                ArgumentNullException.ThrowIfNull(value);
            }

            int bitCount = checked(value!.GetByteCount() * 8);
            return (value << rotateAmount) | (value >>> (bitCount - rotateAmount));
        }

        /// <summary>Rotates a value right by a given amount.</summary>
        /// <param name="value">The value which is rotated right by <paramref name="rotateAmount" />.</param>
        /// <param name="rotateAmount">The amount by which <paramref name="value" /> is rotated right.</param>
        /// <returns>The result of rotating <paramref name="value" /> right by <paramref name="rotateAmount" />.</returns>
        static virtual TSelf RotateRight(TSelf value, int rotateAmount)
        {
            if (!typeof(TSelf).IsValueType)
            {
                ArgumentNullException.ThrowIfNull(value);
            }

            int bitCount = checked(value!.GetByteCount() * 8);
            return (value >>> rotateAmount) | (value << (bitCount - rotateAmount));
        }

        /// <summary>Computes the number of trailing zero bits in a value.</summary>
        /// <param name="value">The value whose trailing zero bits are to be counted.</param>
        /// <returns>The number of trailing zero bits in <paramref name="value" />.</returns>
        static abstract TSelf TrailingZeroCount(TSelf value);

        /// <summary>Tries to read a two's complement number from a span, in big-endian format, and convert it to an instance of the current type.</summary>
        /// <param name="source">The span from which the two's complement number should be read.</param>
        /// <param name="isUnsigned"><c>true</c> if <paramref name="source" /> represents an unsigned two's complement number; otherwise, <c>false</c> to indicate it represents a signed two's complement number.</param>
        /// <param name="value">On return, contains the value read from <paramref name="source" /> or <c>default</c> if a value could not be read.</param>
        /// <returns><c>true</c> if the value was succesfully read from <paramref name="source" />; otherwise, <c>false</c>.</returns>
        static abstract bool TryReadBigEndian(ReadOnlySpan<byte> source, bool isUnsigned, out TSelf value);

        /// <summary>Tries to read a two's complement number from a span, in little-endian format, and convert it to an instance of the current type.</summary>
        /// <param name="source">The span from which the two's complement number should be read.</param>
        /// <param name="isUnsigned"><c>true</c> if <paramref name="source" /> represents an unsigned two's complement number; otherwise, <c>false</c> to indicate it represents a signed two's complement number.</param>
        /// <param name="value">On return, contains the value read from <paramref name="source" /> or <c>default</c> if a value could not be read.</param>
        /// <returns><c>true</c> if the value was succesfully read from <paramref name="source" />; otherwise, <c>false</c>.</returns>
        static abstract bool TryReadLittleEndian(ReadOnlySpan<byte> source, bool isUnsigned, out TSelf value);

        /// <summary>Gets the number of bytes that will be written as part of <see cref="TryWriteLittleEndian(Span{byte}, out int)" />.</summary>
        /// <returns>The number of bytes that will be written as part of <see cref="TryWriteLittleEndian(Span{byte}, out int)" />.</returns>
        int GetByteCount();

        /// <summary>Gets the length, in bits, of the shortest two's complement representation of the current value.</summary>
        /// <returns>The length, in bits, of the shortest two's complement representation of the current value.</returns>
        int GetShortestBitLength();

        /// <summary>Tries to write the current value, in big-endian format, to a given span.</summary>
        /// <param name="destination">The span to which the current value should be written.</param>
        /// <param name="bytesWritten">The number of bytes written to <paramref name="destination" />.</param>
        /// <returns><c>true</c> if the value was successfully written to <paramref name="destination" />; otherwise, <c>false</c>.</returns>
        bool TryWriteBigEndian(Span<byte> destination, out int bytesWritten);

        /// <summary>Tries to write the current value, in little-endian format, to a given span.</summary>
        /// <param name="destination">The span to which the current value should be written.</param>
        /// <param name="bytesWritten">The number of bytes written to <paramref name="destination" />.</param>
        /// <returns><c>true</c> if the value was successfully written to <paramref name="destination" />; otherwise, <c>false</c>.</returns>
        bool TryWriteLittleEndian(Span<byte> destination, out int bytesWritten);

        /// <summary>Writes the current value, in big-endian format, to a given array.</summary>
        /// <param name="destination">The array to which the current value should be written.</param>
        /// <returns>The number of bytes written to <paramref name="destination" />.</returns>
        int WriteBigEndian(byte[] destination)
        {
            if (!TryWriteBigEndian(destination, out int bytesWritten))
            {
                ThrowHelper.ThrowArgumentException_DestinationTooShort();
            }
            return bytesWritten;
        }

        /// <summary>Writes the current value, in big-endian format, to a given array.</summary>
        /// <param name="destination">The array to which the current value should be written.</param>
        /// <param name="startIndex">The starting index at which the value should be written.</param>
        /// <returns>The number of bytes written to <paramref name="destination" /> starting at <paramref name="startIndex" />.</returns>
        int WriteBigEndian(byte[] destination, int startIndex)
        {
            if (!TryWriteBigEndian(destination.AsSpan(startIndex), out int bytesWritten))
            {
                ThrowHelper.ThrowArgumentException_DestinationTooShort();
            }
            return bytesWritten;
        }

        /// <summary>Writes the current value, in big-endian format, to a given span.</summary>
        /// <param name="destination">The span to which the current value should be written.</param>
        /// <returns>The number of bytes written to <paramref name="destination" />.</returns>
        int WriteBigEndian(Span<byte> destination)
        {
            if (!TryWriteBigEndian(destination, out int bytesWritten))
            {
                ThrowHelper.ThrowArgumentException_DestinationTooShort();
            }
            return bytesWritten;
        }

        /// <summary>Writes the current value, in little-endian format, to a given array.</summary>
        /// <param name="destination">The array to which the current value should be written.</param>
        /// <returns>The number of bytes written to <paramref name="destination" />.</returns>
        int WriteLittleEndian(byte[] destination)
        {
            if (!TryWriteLittleEndian(destination, out int bytesWritten))
            {
                ThrowHelper.ThrowArgumentException_DestinationTooShort();
            }
            return bytesWritten;
        }

        /// <summary>Writes the current value, in little-endian format, to a given array.</summary>
        /// <param name="destination">The array to which the current value should be written.</param>
        /// <param name="startIndex">The starting index at which the value should be written.</param>
        /// <returns>The number of bytes written to <paramref name="destination" /> starting at <paramref name="startIndex" />.</returns>
        int WriteLittleEndian(byte[] destination, int startIndex)
        {
            if (!TryWriteLittleEndian(destination.AsSpan(startIndex), out int bytesWritten))
            {
                ThrowHelper.ThrowArgumentException_DestinationTooShort();
            }
            return bytesWritten;
        }

        /// <summary>Writes the current value, in little-endian format, to a given span.</summary>
        /// <param name="destination">The span to which the current value should be written.</param>
        /// <returns>The number of bytes written to <paramref name="destination" />.</returns>
        int WriteLittleEndian(Span<byte> destination)
        {
            if (!TryWriteLittleEndian(destination, out int bytesWritten))
            {
                ThrowHelper.ThrowArgumentException_DestinationTooShort();
            }
            return bytesWritten;
        }
    }
}