From 40fc559a56a623f89f5393bb79bb1d960f0ba0df Mon Sep 17 00:00:00 2001 From: Marcel Koester Date: Thu, 1 Feb 2024 09:34:07 +0100 Subject: [PATCH 1/5] Added new fixed precision computation types based on int and long types. --- .../FixedPrecision/FixedInts.tt | 865 ++++++++++++++++++ 1 file changed, 865 insertions(+) create mode 100644 Src/ILGPU.Algorithms/FixedPrecision/FixedInts.tt diff --git a/Src/ILGPU.Algorithms/FixedPrecision/FixedInts.tt b/Src/ILGPU.Algorithms/FixedPrecision/FixedInts.tt new file mode 100644 index 000000000..0c59dfecb --- /dev/null +++ b/Src/ILGPU.Algorithms/FixedPrecision/FixedInts.tt @@ -0,0 +1,865 @@ +// --------------------------------------------------------------------------------------- +// ILGPU Algorithms +// Copyright (c) 2023-2024 ILGPU Project +// www.ilgpu.net +// +// File: FixedInts.tt/FixedInts.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ include file="../TypeInformation.ttinclude"#> +<#@ include file="FixedIntConfig.ttinclude"#> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +<# +var basicConversionTypes = new string[] + { + "sbyte", + "byte", + "short", + "ushort", + "int", + "uint", + "long", + "ulong", + "System.Int128", + "System.UInt128", + "nint", + "nuint", + "System.Half", + "float", + "double", + }; +#> +using ILGPU.Algorithms.Random; +using ILGPU.Runtime; +using System; +using System.Globalization; +using System.Numerics; +using System.Runtime.CompilerServices; + +#if NET7_0_OR_GREATER + +// disable: max_line_length + +#pragma warning disable IDE0004 // Cast is redundant +#pragma warning disable CA2225 // Friendly operator names + +namespace ILGPU.Algorithms.FixedPrecision +{ +<# foreach (var config in FixedPrecisionIntTypes) { #> +<# foreach (int variant in config.Variants) { #> +<# long resolution = (int)Math.Ceiling(Math.Pow(10, variant)); #> +<# long approximationBits = (int)Math.Ceiling(Math.Log(resolution, 2.0)); #> +<# string formatString = string.Join(string.Empty, Enumerable.Repeat("0", variant)); #> +<# string name = config.GetName(variant); #> + /// + /// A fixed precision integer with <#= config.Bits #>bits using <#= approximationBits #> bits + /// to represent a number with <#= variant #> decimal places. + /// + /// The nested raw integer value. + public readonly record struct <#= name #>(<#= config.TypeName #> RawValue) : + INumber<<#= name #>>, + ISignedNumber<<#= name #>>, + IMinMaxValue<<#= name #>> + { + #region Static + + /// + /// Returns the number of decimal places used. + /// + public const int DecimalPlaces = <#= variant #>; + + /// + /// Returns the number of decimal places used to perform rounding. + /// + private const int RoundingDecimalPlaces = <#= Math.Min(variant, 6) #>; + + /// + /// Returns the integer-based resolution radix. + /// + public const int Resolution = <#= resolution #>; + + /// + /// Returns a float denominator used to convert fixed point values into floats. + /// + public const float FloatDenominator = 1.0f / Resolution; + + /// + /// Returns a double denominator used to convert fixed point values into doubles. + /// + public const double DoubleDenominator = 1.0 / Resolution; + + /// + /// Returns a decimal denominator used to convert fixed point values into decimals. + /// + public const decimal DecimalDenominator = 1m / Resolution; + + /// + public static <#= name #> MinValue => new(<#= config.TypeName #>.MinValue); + + /// + public static <#= name #> MaxValue => new(<#= config.TypeName #>.MaxValue); + + /// + /// Returns the value 1. + /// + public static <#= name #> One => new(Resolution); + + /// + /// Returns the radix 2. + /// + public static int Radix => 2; + + /// + /// Returns the value 0. + /// + public static <#= name #> Zero => new(0); + + /// + /// Returns the value 0. + /// + public static <#= name #> AdditiveIdentity => Zero; + + /// + /// Returns the value -1. + /// + public static <#= name #> NegativeOne => new(-Resolution); + + /// + public static <#= name #> MultiplicativeIdentity => One; + + #endregion + + #region Properties + + /// + /// Returns the main mantissa. + /// + public <#= config.TypeName #> Mantissa => RawValue / Resolution; + + /// + /// Returns all decimal places of this number. + /// + public <#= config.TypeName #> Remainder => RawValue % Resolution; + + #endregion + + #region Operators + + /// + public static <#= name #> operator +(<#= name #> left, <#= name #> right) => + new(left.RawValue + right.RawValue); + + /// + public static <#= name #> operator -(<#= name #> left, <#= name #> right) => + new(left.RawValue - right.RawValue); + + /// + public static bool operator >(<#= name #> left, <#= name #> right) => + left.RawValue > right.RawValue; + /// + public static bool operator >=(<#= name #> left, <#= name #> right) => + left.RawValue >= right.RawValue; + + /// + public static bool operator <(<#= name #> left, <#= name #> right) => + left.RawValue < right.RawValue; + + /// + public static bool operator <=(<#= name #> left, <#= name #> right) => + left.RawValue <= right.RawValue; + + /// + public static <#= name #> operator --(<#= name #> value) => value - One; + /// + public static <#= name #> operator ++(<#= name #> value) => value + One; + + /// + public static <#= name #> operator /(<#= name #> left, <#= name #> right) => + new((<#= config.TypeName #>)(left.RawValue * (<#= config.CalcTypeName #>)Resolution / right.RawValue)); + + /// + public static <#= name #> operator *(<#= name #> left, <#= name #> right) => + new((<#= config.TypeName #>)((<#= config.CalcTypeName #>)left.RawValue * right.RawValue / Resolution)); + + /// + public static <#= name #> operator %(<#= name #> left, <#= name #> right) => + new(left.RawValue % right.RawValue); + + /// + public static <#= name #> operator -(<#= name #> value) => new(-value.RawValue); + + /// + public static <#= name #> operator +(<#= name #> value) => value; + + #endregion + + #region Generic INumberBase Methods + + /// + public static <#= name #> Abs(<#= name #> value) => new(Math.Abs(value.RawValue)); + + /// + public static bool IsCanonical(<#= name #> value) => true; + + /// + public static bool IsComplexNumber(<#= name #> value) => false; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsEvenInteger(<#= name #> value) => + IsInteger(value) & (value.Mantissa & 1) == 0; + + /// + public static bool IsFinite(<#= name #> value) => true; + + /// + public static bool IsImaginaryNumber(<#= name #> value) => false; + + /// + public static bool IsInfinity(<#= name #> value) => false; + + /// + public static bool IsInteger(<#= name #> value) => value.Remainder == 0; + + /// + public static bool IsNaN(<#= name #> value) => false; + + /// + public static bool IsNegative(<#= name #> value) => value.RawValue < 0; + + /// + public static bool IsNegativeInfinity(<#= name #> value) => false; + + /// + public static bool IsNormal(<#= name #> value) => value.RawValue != 0; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsOddInteger(<#= name #> value) => + IsInteger(value) & (value.Mantissa & 1) != 0; + + /// + public static bool IsPositive(<#= name #> value) => value.RawValue >= 0; + + /// + public static bool IsPositiveInfinity(<#= name #> value) => false; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsRealNumber(<#= name #> value) => true; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsSubnormal(<#= name #> value) => false; + + /// + public static bool IsZero(<#= name #> value) => value.RawValue == 0; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static <#= name #> MaxMagnitude(<#= name #> x, <#= name #> y) => + new(<#= config.TypeName #>.MaxMagnitude(x.RawValue, y.RawValue)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static <#= name #> MaxMagnitudeNumber(<#= name #> x, <#= name #> y) => + MaxMagnitude(x, y); + + /// + public static <#= name #> MinMagnitude(<#= name #> x, <#= name #> y) => + new(<#= config.TypeName #>.MinMagnitude(x.RawValue, y.RawValue)); + + /// + public static <#= name #> MinMagnitudeNumber(<#= name #> x, <#= name #> y) => + MinMagnitude(x, y); + + /// + /// Computes the min value of both. + /// + /// The first value. + /// The second value. + /// The min value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static <#= name #> Min(<#= name #> x, <#= name #> y) => + new(Math.Min(x.RawValue, y.RawValue)); + + /// + /// Computes the max value of both. + /// + /// The first value. + /// The second value. + /// The max value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static <#= name #> Max(<#= name #> x, <#= name #> y) => + new(Math.Max(x.RawValue, y.RawValue)); + + #endregion + + #region TryConvert + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryConvertFromChecked(TOther value, out <#= name #> result) + where TOther : INumberBase => + TryConvertFrom(value, out result); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryConvertFromSaturating(TOther value, out <#= name #> result) + where TOther : INumberBase => + TryConvertFrom(value, out result); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryConvertFromTruncating(TOther value, out <#= name #> result) + where TOther : INumberBase => + TryConvertFrom(value, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryConvertFrom(TOther value, out <#= name #> result) + where TOther : INumberBase + { + if (typeof(TOther) == typeof(bool)) + { + result = Unsafe.As(ref value) ? One : Zero; + return true; + } +<# foreach (var conversionType in basicConversionTypes) { #> + if (typeof(TOther) == typeof(<#= conversionType #>)) + { + result = (<#= name #>)Unsafe.As>(ref value); + return true; + } +<# } #> +<# foreach (var otherConfig in FixedPrecisionIntTypes) { #> +<# foreach (int otherVariant in otherConfig.Variants) { #> +<# string otherName = otherConfig.GetName(otherVariant); #> + if (typeof(TOther) == typeof(<#= otherConfig.TypeName #>)) + { + result = (<#= name #>)Unsafe.As>(ref value); + return true; + } +<# } #> +<# } #> + + result = default; + return false; + } + + /// + public static bool TryConvertToChecked(<#= name #> value, out TOther result) + where TOther : INumberBase => + TryConvertTo(value, out result); + + /// + public static bool TryConvertToSaturating(<#= name #> value, out TOther result) + where TOther : INumberBase => + TryConvertTo(value, out result); + + /// + public static bool TryConvertToTruncating(<#= name #> value, out TOther result) + where TOther : INumberBase => + TryConvertTo(value, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryConvertTo(<#= name #> value, out TOther result) + where TOther : INumberBase + { + result = default!; + if (typeof(TOther) == typeof(bool)) + { + Unsafe.As(ref result) = (bool)value; + return true; + } +<# foreach (var conversionType in basicConversionTypes) { #> + if (typeof(TOther) == typeof(<#= conversionType #>)) + { + Unsafe.As>(ref result) = (<#= conversionType #>)value; + return true; + } +<# } #> +<# foreach (var otherConfig in FixedPrecisionIntTypes) { #> +<# foreach (int otherVariant in otherConfig.Variants) { #> +<# string otherName = otherConfig.GetName(otherVariant); #> + if (typeof(TOther) == typeof(<#= otherName #>)) + { + Unsafe.As>(ref result) = (<#= otherName #>)value; + return true; + } +<# } #> +<# } #> + + result = default!; + return false; + } + + #endregion + + #region Parse + + /// + public static bool TryParse(string? s, IFormatProvider? provider, out <#= name #> result) + { + result = default; + if (string.IsNullOrWhiteSpace(s)) + return false; + return TryParse(s.AsSpan(), provider, out result); + } + + /// + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out <#= name #> result) => + TryParse(s, NumberStyles.Integer, provider, out result); + + /// + public static bool TryParse(string? s, NumberStyles style, IFormatProvider? provider, out <#= name #> result) + { + result = default; + if (string.IsNullOrWhiteSpace(s)) + return false; + return TryParse(s.AsSpan(), style, provider, out result); + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out <#= name #> result) + { + result = default; + + var separator = GetDecimalSeparator(provider); + int decimalSeparator = s.IndexOf(separator.AsSpan()); + if (decimalSeparator < 0) + { + // Try parse mantissa part only + if (!<#= config.TypeName #>.TryParse(s, style, provider, out <#= config.TypeName #> mantissaOnly)) + return false; + result = new(mantissaOnly); + return true; + } + + var mantissaPart = s[..decimalSeparator]; + var remainderPart = s[decimalSeparator..]; + + if (!<#= config.TypeName #>.TryParse(mantissaPart, style, provider, out <#= config.TypeName #> mantissa) || + !<#= config.TypeName #>.TryParse(remainderPart, style, provider, out <#= config.TypeName #> remainder)) + { + return false; + } + + result = new(mantissa * Resolution + remainder); + return true; + } + + /// + public static <#= name #> Parse(string s, IFormatProvider? provider) => + Parse(s.AsSpan(), provider); + + /// + public static <#= name #> Parse(string s, NumberStyles style, IFormatProvider? provider) => + Parse(s.AsSpan(), style, provider); + + /// + public static <#= name #> Parse(ReadOnlySpan s, IFormatProvider? provider) => + Parse(s, NumberStyles.Integer, provider); + + /// + public static <#= name #> Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider) + { + if (!TryParse(s, style, provider, out var result)) + throw new FormatException(); + return result; + } + + #endregion + + #region IComparable + + /// + /// Compares the given object to the current instance. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int CompareTo(object? obj) => obj is <#= name #> fixedInt ? CompareTo(fixedInt) : 1; + + /// + /// Compares the given fixed integer to the current instance. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int CompareTo(<#= name #> other) => RawValue.CompareTo(other.RawValue); + + #endregion + + #region ToString and Formats + + /// + /// Returns the default string representation of this fixed point value. + /// + public override string ToString() => ToString(null, null); + + /// + /// Returns the string representation of this value while taking the given separator into account. + /// + /// The decimal separator to use. + private string ToString(string decimalSeparator) => + $"{Mantissa}{decimalSeparator}{Remainder:<#= formatString #>}"; + + /// + /// Helper function to get a number format provider instance. + /// + private static string GetDecimalSeparator(IFormatProvider? formatProvider) => + NumberFormatInfo.GetInstance(formatProvider).NumberDecimalSeparator; + + /// + public string ToString(string? format, IFormatProvider? formatProvider) => + ToString(GetDecimalSeparator(formatProvider)); + + /// + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) + { + if (!Mantissa.TryFormat(destination, out charsWritten, format, provider)) + return false; + + var remainingTarget = destination[charsWritten..]; + var separator = GetDecimalSeparator(provider); + if (separator.Length > remainingTarget.Length) + return false; + + separator.CopyTo(remainingTarget); + charsWritten += separator.Length; + + var decimalPlacesTarget = remainingTarget[separator.Length..]; + bool result = Remainder.TryFormat( + decimalPlacesTarget, + out int remainderCharsWritten, + format, + provider); + charsWritten += remainderCharsWritten; + return result; + } + + #endregion + + #region Conversion Operators + + /// + /// Converts the given fixed-point value into the designated target type. + /// + /// The fixed value to convert. + /// The converted target value. + public static explicit operator bool(<#= name #> fixedInt) => fixedInt.RawValue != 0; + + /// + /// Converts the given fixed-point value into the designated target type. + /// + /// The fixed value to convert. + /// The converted target value. + public static explicit operator char(<#= name #> fixedInt) => (char)fixedInt.Mantissa; + + /// + /// Converts the given fixed-point value into the designated target type. + /// + /// The fixed value to convert. + /// The converted target value. + public static explicit operator sbyte(<#= name #> fixedInt) => (sbyte)fixedInt.Mantissa; + + /// + /// Converts the given fixed-point value into the designated target type. + /// + /// The fixed value to convert. + /// The converted target value. + public static explicit operator byte(<#= name #> fixedInt) => (byte)fixedInt.Mantissa; + + /// + /// Converts the given fixed-point value into the designated target type. + /// + /// The fixed value to convert. + /// The converted target value. + public static explicit operator short(<#= name #> fixedInt) => (short)fixedInt.Mantissa; + + /// + /// Converts the given fixed-point value into the designated target type. + /// + /// The fixed value to convert. + /// The converted target value. + public static explicit operator ushort(<#= name #> fixedInt) => (ushort)fixedInt.Mantissa; + + /// + /// Converts the given fixed-point value into the designated target type. + /// + /// The fixed value to convert. + /// The converted target value. + public static explicit operator int(<#= name #> fixedInt) => (int)fixedInt.Mantissa; + + /// + /// Converts the given fixed-point value into the designated target type. + /// + /// The fixed value to convert. + /// The converted target value. + public static explicit operator uint(<#= name #> fixedInt) => (uint)fixedInt.Mantissa; + + /// + /// Converts the given fixed-point value into the designated target type. + /// + /// The fixed value to convert. + /// The converted target value. + public static explicit operator long(<#= name #> fixedInt) => (long)fixedInt.Mantissa; + + /// + /// Converts the given fixed-point value into the designated target type. + /// + /// The fixed value to convert. + /// The converted target value. + public static explicit operator Int128(<#= name #> fixedInt) => (Int128)fixedInt.Mantissa; + + /// + /// Converts the given fixed-point value into the designated target type. + /// + /// The fixed value to convert. + /// The converted target value. + public static explicit operator ulong(<#= name #> fixedInt) => (ulong)fixedInt.Mantissa; + + /// + /// Converts the given fixed-point value into the designated target type. + /// + /// The fixed value to convert. + /// The converted target value. + public static explicit operator UInt128(<#= name #> fixedInt) => (UInt128)fixedInt.Mantissa; + + /// + /// Converts the given fixed-point value into the designated target type. + /// + /// The fixed value to convert. + /// The converted target value. + public static explicit operator System.Half(<#= name #> fixedInt) => + (System.Half)(float)fixedInt; + + /// + /// Converts the given fixed-point value into the designated target type. + /// + /// The fixed value to convert. + /// The converted target value. + public static explicit operator float(<#= name #> fixedInt) => fixedInt.RawValue * FloatDenominator; + + /// + /// Converts the given fixed-point value into the designated target type. + /// + /// The fixed value to convert. + /// The converted target value. + public static explicit operator double(<#= name #> fixedInt) => + fixedInt.RawValue * DoubleDenominator; + + /// + /// Converts the given fixed-point value into the designated target type. + /// + /// The fixed value to convert. + /// The converted target value. + public static explicit operator decimal(<#= name #> fixedInt) => + fixedInt.RawValue * DecimalDenominator; + + /// + /// Converts the given value into its fixed-point value equivalent. + /// + /// The value to convert. + /// The converted fixed point value. + public static explicit operator <#= name #>(bool value) => value ? One : Zero; + + /// + /// Converts the given value into its fixed-point value equivalent. + /// + /// The value to convert. + /// The converted fixed point value. + public static explicit operator <#= name #>(char value) => new((<#= config.TypeName #>)value); + + /// + /// Converts the given value into its fixed-point value equivalent. + /// + /// The value to convert. + /// The converted fixed point value. + public static explicit operator <#= name #>(sbyte value) => new(value * Resolution); + + /// + /// Converts the given value into its fixed-point value equivalent. + /// + /// The value to convert. + /// The converted fixed point value. + public static explicit operator <#= name #>(byte value) => new(value * Resolution); + + /// + /// Converts the given value into its fixed-point value equivalent. + /// + /// The value to convert. + /// The converted fixed point value. + public static explicit operator <#= name #>(short value) => new(value * Resolution); + + /// + /// Converts the given value into its fixed-point value equivalent. + /// + /// The value to convert. + /// The converted fixed point value. + public static explicit operator <#= name #>(ushort value) => new(value * Resolution); + + /// + /// Converts the given value into its fixed-point value equivalent. + /// + /// The value to convert. + /// The converted fixed point value. + public static explicit operator <#= name #>(int value) => new(value); + + /// + /// Converts the given value into its fixed-point value equivalent. + /// + /// The value to convert. + /// The converted fixed point value. + public static explicit operator <#= name #>(uint value) => new((<#= config.TypeName #>)value); + + /// + /// Converts the given value into its fixed-point value equivalent. + /// + /// The value to convert. + /// The converted fixed point value. + public static explicit operator <#= name #>(long value) => new((<#= config.TypeName #>)value); + + /// + /// Converts the given value into its fixed-point value equivalent. + /// + /// The value to convert. + /// The converted fixed point value. + public static explicit operator <#= name #>(Int128 value) => new((<#= config.TypeName #>)value); + + /// + /// Converts the given value into its fixed-point value equivalent. + /// + /// The value to convert. + /// The converted fixed point value. + public static explicit operator <#= name #>(ulong value) => new((<#= config.TypeName #>)value); + + /// + /// Converts the given value into its fixed-point value equivalent. + /// + /// The value to convert. + /// The converted fixed point value. + public static explicit operator <#= name #>(UInt128 value) => new((<#= config.TypeName #>)value); + + /// + /// Converts the given value into its fixed-point value equivalent. + /// + /// The value to convert. + /// The converted fixed point value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator <#= name #>(System.Half value) => + (<#= name #>)(float)value; + + /// + /// Converts the given value into its fixed-point value equivalent. + /// + /// The value to convert. + /// The converted fixed point value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator <#= name #>(float value) + { + <#= config.TypeName #> mantissa = (<#= config.TypeName #>)value; + <#= config.TypeName #> remainder = (<#= config.TypeName #>)( + MathF.Round(value - MathF.Truncate(value), RoundingDecimalPlaces) * Resolution); + return new(mantissa * Resolution + remainder); + } + + /// + /// Converts the given value into its fixed-point value equivalent. + /// + /// The value to convert. + /// The converted fixed point value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator <#= name #>(double value) + { + <#= config.TypeName #> mantissa = (<#= config.TypeName #>)value; + <#= config.TypeName #> remainder = (<#= config.TypeName #>)( + Math.Round(value - Math.Truncate(value), RoundingDecimalPlaces) * Resolution); + return new(mantissa * Resolution + remainder); + } + + /// + /// Converts the given value into its fixed-point value equivalent. + /// + /// The value to convert. + /// The converted fixed point value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator <#= name #>(decimal value) + { + <#= config.TypeName #> mantissa = (<#= config.TypeName #>)value; + <#= config.TypeName #> remainder = (<#= config.TypeName #>)( + Math.Round(value - Math.Truncate(value), RoundingDecimalPlaces) * Resolution); + return new(mantissa * Resolution + remainder); + } + +<# foreach (var otherConfig in FixedPrecisionIntTypes) { #> +<# foreach (int oVariant in otherConfig.Variants) { #> +<# string oName = otherConfig.GetName(oVariant); #> +<# if (oName == name) continue; #> + /// + /// Converts the given value into its specified fixed-point value equivalent. + /// + /// The value to convert. + /// The converted fixed point value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator <#= oName #>(<#= name #> value) + { +<# if (config.Bits >= otherConfig.Bits) { #> + var computeVal = (<#= config.CalcTypeName #>)value.RawValue; +<# } else { #> + var computeVal = (<#= otherConfig.CalcTypeName #>)value.RawValue; +<# } #> + var newValue = computeVal * <#= oName #>.Resolution / Resolution; + return new((<#= otherConfig.TypeName #>)newValue); + } + +<# } #> +<# } #> + #endregion + } + +<# } #> +<# } #> +} + +namespace ILGPU.Algorithms.Random +{ + using ILGPU.Algorithms.FixedPrecision; + + partial class RandomExtensions + { +<# foreach (var config in FixedPrecisionIntTypes) { #> +<# foreach (int variant in config.Variants) { #> +<# var name = config.GetName(variant); #> + /// + /// Generates a random <#= name #> in [minValue..maxValue). + /// + /// The random provider. + /// The minimum value (inclusive). + /// The maximum values (exclusive). + /// A random <#= name #> in [minValue..maxValue). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static <#= name #> Next( + ref TRandomProvider randomProvider, + <#= name #> minValue, + <#= name #> maxValue) + where TRandomProvider : struct, IRandomProvider + { + <#= config.CalcTypeName #> next = Next( + ref randomProvider, + (<#= config.CalcTypeName #>)minValue.RawValue, + (<#= config.CalcTypeName #>)maxValue.RawValue); + return new((<#= config.TypeName #>)next); + } +<# } #> +<# } #> + } +} + +#pragma warning restore CA2225 +#pragma warning restore IDE0004 + +#endif \ No newline at end of file From 054ae89a21e9ce1240a7b2210b687945b58831c9 Mon Sep 17 00:00:00 2001 From: Marcel Koester Date: Thu, 1 Feb 2024 09:34:07 +0100 Subject: [PATCH 2/5] Added FixedIntConfig.ttinclude to control fixed precision type generation. --- .../FixedPrecision/FixedIntConfig.ttinclude | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Src/ILGPU.Algorithms/FixedPrecision/FixedIntConfig.ttinclude diff --git a/Src/ILGPU.Algorithms/FixedPrecision/FixedIntConfig.ttinclude b/Src/ILGPU.Algorithms/FixedPrecision/FixedIntConfig.ttinclude new file mode 100644 index 000000000..bde3c8058 --- /dev/null +++ b/Src/ILGPU.Algorithms/FixedPrecision/FixedIntConfig.ttinclude @@ -0,0 +1,59 @@ +// --------------------------------------------------------------------------------------- +// ILGPU Algorithms +// Copyright (c) 2023-2024 ILGPU Project +// www.ilgpu.net +// +// File: FixedIntConfig.ttinclude +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +<#+ +private readonly struct FixedIntConfig +{ + public FixedIntConfig( + int bits, + string baseName, + string typeName, + string calcTypeName, + int[] variants) + { + Bits = bits; + BaseName = baseName; + TypeName = typeName; + CalcTypeName = calcTypeName; + Variants = variants; + } + + public int Bits { get; } + public string BaseName { get; } + public string TypeName { get; } + public string CalcTypeName { get; } + public int[] Variants { get; } + + public string GetName(int variant) => $"Fixed{BaseName}{variant}DP"; + + public TypeInformation ToBasicTypeInformation(int variant) => new TypeInformation( + GetName(variant), + GetName(variant), + TypeInformationKind.SignedInt, + prefix: "(int)"); + + public IEnumerable ToBasicTypeInformation() => + Variants.Select(ToBasicTypeInformation); +} + +private static FixedIntConfig[] FixedPrecisionIntTypes = +{ + new FixedIntConfig(32, "Int", "int", "long", new int[] + { + 2, 4, 6 + }), + new FixedIntConfig(64, "Long", "long", "long", new int[] + { + 2, 4, 6, 8 + }) +}; + +#> \ No newline at end of file From 3e6f9d2193e0e6cbc3e93a4a33d00cb5c5a9f4b4 Mon Sep 17 00:00:00 2001 From: Marcel Koester Date: Thu, 1 Feb 2024 09:34:07 +0100 Subject: [PATCH 3/5] Added new generator template to ILGPU.Algorithms.csproj and adjusted ignore file. --- .gitignore | 5 +++-- Src/ILGPU.Algorithms/ILGPU.Algorithms.csproj | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index b0f5b7490..0c6f225dc 100644 --- a/.gitignore +++ b/.gitignore @@ -290,12 +290,12 @@ Src/ILGPU/Util/PrimitiveDataBlocks.cs Src/ILGPU.Algorithms/AlgorithmContextMappings.cs Src/ILGPU.Algorithms/CL/CLContext.Generated.cs Src/ILGPU.Algorithms/ComparisonOperations.cs +Src/ILGPU.Algorithms/FixedPrecision/FixedInts.cs Src/ILGPU.Algorithms/HistogramLaunchers.cs Src/ILGPU.Algorithms/HistogramOperations.cs Src/ILGPU.Algorithms/IL/ILContext.Generated.cs Src/ILGPU.Algorithms/PTX/PTXContext.Generated.cs Src/ILGPU.Algorithms/RadixSortOperations.cs -Src/ILGPU.Algorithms/Vectors/VectorTypes.cs Src/ILGPU.Algorithms/Random/RandomRanges.cs Src/ILGPU.Algorithms/Runtime/Cuda/API/CuBlasNativeMethods.cs Src/ILGPU.Algorithms/Runtime/Cuda/API/CuFFTAPI.Generated.cs @@ -314,10 +314,11 @@ Src/ILGPU.Algorithms/Runtime/Cuda/CuFFTWPlan.cs Src/ILGPU.Algorithms/ScanReduceOperations.cs Src/ILGPU.Algorithms/Sequencers.cs Src/ILGPU.Algorithms/UniqueLaunchers.cs -Src/ILGPU.Algorithms/XMath/Cordic.cs +Src/ILGPU.Algorithms/Vectors/VectorTypes.cs Src/ILGPU.Algorithms/XMath/Cordic.Log.cs Src/ILGPU.Algorithms/XMath/Cordic.Pow.cs Src/ILGPU.Algorithms/XMath/Cordic.Trig.cs +Src/ILGPU.Algorithms/XMath/Cordic.cs Src/ILGPU.Algorithms/XMath/RoundingModes.cs # Generated test source files diff --git a/Src/ILGPU.Algorithms/ILGPU.Algorithms.csproj b/Src/ILGPU.Algorithms/ILGPU.Algorithms.csproj index e50a862c2..e142de5a8 100644 --- a/Src/ILGPU.Algorithms/ILGPU.Algorithms.csproj +++ b/Src/ILGPU.Algorithms/ILGPU.Algorithms.csproj @@ -59,6 +59,10 @@ True CuRandNativeMethods.tt + + TextTemplatingFilePreprocessor + FixedIntConfig.cs + @@ -97,6 +101,10 @@ TextTemplatingFileGenerator ComparisonOperations.cs + + FixedInts.cs + TextTemplatingFileGenerator + TextTemplatingFileGenerator HistogramLaunchers.cs @@ -234,6 +242,11 @@ True ComparisonOperations.tt + + True + True + FixedInts.tt + True True @@ -397,5 +410,6 @@ + From 1a76e57134502e3c917c9186db52265bad490d23 Mon Sep 17 00:00:00 2001 From: Marcel Koester Date: Thu, 1 Feb 2024 09:34:08 +0100 Subject: [PATCH 4/5] Extended VectorTypes.tt to generate vector types for fixed precision int types. --- Src/ILGPU.Algorithms/Vectors/VectorTypes.tt | 43 ++++++++++++++++----- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/Src/ILGPU.Algorithms/Vectors/VectorTypes.tt b/Src/ILGPU.Algorithms/Vectors/VectorTypes.tt index b7d20f264..260f07642 100644 --- a/Src/ILGPU.Algorithms/Vectors/VectorTypes.tt +++ b/Src/ILGPU.Algorithms/Vectors/VectorTypes.tt @@ -1,6 +1,6 @@ // --------------------------------------------------------------------------------------- // ILGPU Algorithms -// Copyright (c) 2023 ILGPU Project +// Copyright (c) 2023-2024 ILGPU Project // www.ilgpu.net // // File: VectorTypes.tt/VectorTypes.cs @@ -11,6 +11,7 @@ <#@ template debug="false" hostspecific="false" language="C#" #> <#@ include file="../TypeInformation.ttinclude"#> +<#@ include file="../FixedPrecision/FixedIntConfig.ttinclude"#> <#@ assembly name="System.Core" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> @@ -19,7 +20,11 @@ // Please note that this code does not support FP16 at the moment because ILGPU.Half does // not support the INumberBase and INumber interfaces and will be considered // obsolete in the future anyway. -var allTypes = IntTypes.Concat(FloatTypes.Skip(1)); +var fixedPrecisionTypes = FixedPrecisionIntTypes + .SelectMany(t => t.ToBasicTypeInformation()).ToHashSet(); +var allTypes = IntTypes + .Concat(FloatTypes.Skip(1)) + .Concat(fixedPrecisionTypes); var typesByRawName = allTypes.ToDictionary(t => t.Type); var accumulationTypes = new Dictionary() { @@ -62,6 +67,9 @@ string GetTypeName(TypeInformation type, int vectorLength) return $"{baseTypeName}{postFix}x{vectorLength}"; } #> +#if NET7_0_OR_GREATER +using ILGPU.Algorithms.FixedPrecision; +#endif using ILGPU.Algorithms.Random; using ILGPU.Runtime; using System; @@ -81,6 +89,7 @@ namespace ILGPU.Algorithms.Vectors <# foreach (var type in allTypes) { #> <# var accumulations = accumulationTypes.TryGetValue(type.Name, out var accTypes) ? accTypes : Array.Empty(); #> +<# var propPostfix = fixedPrecisionTypes.Contains(type) ? ".RawValue" : "" ; #> <# foreach (var vectorLength in vectorLengths) { #> <# var typeName = GetTypeName(type, vectorLength); #> /// @@ -103,7 +112,7 @@ namespace ILGPU.Algorithms.Vectors /// The offset of the <#= vectorItemNames[i] #> field in bytes. /// public static readonly int Offset<#= vectorItemNames[i] #> = - sizeof(<#= type.Type #>) * <#= i #>; + Interop.SizeOf<<#= type.Type #>>() * <#= i #>; /// /// The offset of the <#= vectorItemNames[i] #> field in bytes. @@ -120,7 +129,7 @@ namespace ILGPU.Algorithms.Vectors /// /// Returns the radix of the underlying value. /// - public static int Radix => 10; + public static int Radix => 2; /// /// Returns an invalid value (min [signed types], max value [unsigned] or NaN). @@ -143,7 +152,8 @@ namespace ILGPU.Algorithms.Vectors /// /// Returns the value one. /// - public static <#= typeName #> One => FromScalar(<#= type.FormatNumber("1") #>); + public static <#= typeName #> One => + FromScalar((<#= type.Type #>)<#= type.FormatNumber("1") #>); /// /// Returns the value zero. @@ -161,12 +171,13 @@ namespace ILGPU.Algorithms.Vectors /// The first value. /// The second value. /// The min value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static <#= typeName #> Min( <#= typeName #> first, <#= typeName #> second) => new <#= typeName #>(<#= string.Join(", ", vectorItemNames .Take(vectorLength) - .Select(t => $"({type.Type})Math.Min(first.{t}, second.{t})")) #>); + .Select(t => $"({type.Type})Math.Min(first.{t}{propPostfix}, second.{t}{propPostfix})")) #>); /// /// Computes the max value of both. @@ -174,12 +185,13 @@ namespace ILGPU.Algorithms.Vectors /// The first value. /// The second value. /// The max value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static <#= typeName #> Max( <#= typeName #> first, <#= typeName #> second) => new <#= typeName #>(<#= string.Join(", ", vectorItemNames .Take(vectorLength) - .Select(t => $"({type.Type})Math.Max(first.{t}, second.{t})")) #>); + .Select(t => $"({type.Type})Math.Max(first.{t}{propPostfix}, second.{t}{propPostfix})")) #>); /// /// Clamps the given value. @@ -307,6 +319,7 @@ namespace ILGPU.Algorithms.Vectors /// /// The target memory address. /// The current value to add. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtomicAdd(ref <#= typeName #> target, <#= typeName #> value) { ref var elementRef = ref Unsafe.As<<#= typeName #>, <#= type.Type #>>( @@ -662,13 +675,14 @@ namespace ILGPU.Algorithms.Vectors /// /// Returns the absolute value. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static <#= typeName #> Abs(<#= typeName #> value) => <# if (type.IsUnsignedInt) { #> value; <# } else { #> new <#= typeName #>(<#= string.Join(", ", vectorItemNames .Take(vectorLength) - .Select(t => $"Math.Abs(value.{t})")) #>); + .Select(t => $"({type.Type})Math.Abs(value.{t}{propPostfix})")) #>); <# } #> /// @@ -682,11 +696,13 @@ namespace ILGPU.Algorithms.Vectors /// /// Performs the specified operation. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static <#= typeName #> operator --(<#= typeName #> value) => value - One; /// /// Performs the specified operation. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static <#= typeName #> operator /(<#= typeName #> left, <#= typeName #> right) => new <#= typeName #>(<#= string.Join(", ", vectorItemNames .Take(vectorLength) @@ -695,11 +711,13 @@ namespace ILGPU.Algorithms.Vectors /// /// Performs the specified operation. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static <#= typeName #> operator ++(<#= typeName #> value) => value + One; /// /// Performs the specified operation. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static <#= typeName #> operator *(<#= typeName #> left, <#= typeName #> right) => new <#= typeName #>(<#= string.Join(", ", vectorItemNames .Take(vectorLength) @@ -708,6 +726,7 @@ namespace ILGPU.Algorithms.Vectors /// /// Performs the specified operation. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static <#= typeName #> operator -(<#= typeName #> left, <#= typeName #> right) => new <#= typeName #>(<#= string.Join(", ", vectorItemNames .Take(vectorLength) @@ -717,6 +736,7 @@ namespace ILGPU.Algorithms.Vectors /// /// Not supported operation. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] static <#= typeName #> IUnaryNegationOperators<<#= typeName #>, <#= typeName #>>. operator -(<#= typeName #> value) => value; <# } else { #> @@ -726,17 +746,19 @@ namespace ILGPU.Algorithms.Vectors public static <#= typeName #> operator -(<#= typeName #> value) => new <#= typeName #>(<#= string.Join(", ", vectorItemNames .Take(vectorLength) - .Select(t => $"({type.Type})-value.{t}")) #>); + .Select(t => $"({type.Type})(-value.{t})")) #>); <# } #> /// /// Performs the specified operation. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static <#= typeName #> operator +(<#= typeName #> value) => value; /// /// Performs the specified operation. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static <#= typeName #> MaxMagnitude(<#= typeName #> x, <#= typeName #> y) => <# if (type.IsUnsignedInt) { #> Max(x, y); @@ -749,6 +771,7 @@ namespace ILGPU.Algorithms.Vectors /// /// Performs the specified operation. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static <#= typeName #> MaxMagnitudeNumber(<#= typeName #> x, <#= typeName #> y) => <# if (type.IsInt) { #> MaxMagnitude(x, y); @@ -761,6 +784,7 @@ namespace ILGPU.Algorithms.Vectors /// /// Performs the specified operation. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static <#= typeName #> MinMagnitude(<#= typeName #> x, <#= typeName #> y) => <# if (type.IsUnsignedInt) { #> Min(x, y); @@ -773,6 +797,7 @@ namespace ILGPU.Algorithms.Vectors /// /// Performs the specified operation. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static <#= typeName #> MinMagnitudeNumber(<#= typeName #> x, <#= typeName #> y) => <# if (type.IsInt) { #> MinMagnitude(x, y); From 8d04ccedbf02bcffed8da0dcab0d07d623131f1d Mon Sep 17 00:00:00 2001 From: Marcel Koester Date: Thu, 1 Feb 2024 09:34:08 +0100 Subject: [PATCH 5/5] Integrated fixed precision types with RandomRanges.tt. --- .../Random/RandomExtensions.cs | 28 +++++++++++++++++-- Src/ILGPU.Algorithms/Random/RandomRanges.tt | 10 +++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/Src/ILGPU.Algorithms/Random/RandomExtensions.cs b/Src/ILGPU.Algorithms/Random/RandomExtensions.cs index 9c60c05f4..f5a3e22e7 100644 --- a/Src/ILGPU.Algorithms/Random/RandomExtensions.cs +++ b/Src/ILGPU.Algorithms/Random/RandomExtensions.cs @@ -1,6 +1,6 @@ // --------------------------------------------------------------------------------------- // ILGPU Algorithms -// Copyright (c) 2021-2023 ILGPU Project +// Copyright (c) 2021-2024 ILGPU Project // www.ilgpu.net // // File: RandomExtensions.cs @@ -21,7 +21,7 @@ namespace ILGPU.Algorithms.Random /// /// Represents useful helpers for random generators. /// - public static class RandomExtensions + public static partial class RandomExtensions { /// /// 1.0 / int.MaxValue @@ -176,6 +176,30 @@ public static long Next( return Math.Min(intermediate + minValue, maxValue - 1); } + /// + /// Generates a random long in [minValue..maxValue). + /// + /// The random provider. + /// The minimum value (inclusive). + /// The maximum values (exclusive). + /// A random long in [minValue..maxValue). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Next( + ref TRandomProvider randomProvider, + ulong minValue, + ulong maxValue) + where TRandomProvider : struct, IRandomProvider + { + Debug.Assert(minValue < maxValue, "Values out of range"); + ulong dist = maxValue - minValue; + + // Check whether the bit range matches in theory + ulong intermediate = dist > 1UL << 23 + ? (ulong)(randomProvider.NextFloat() * dist) + : (ulong)(randomProvider.NextDouble() * dist); + return Math.Min(intermediate + minValue, maxValue - 1UL); + } + #if NET7_0_OR_GREATER /// /// Generates a new random vector containing provided RNG-based values. diff --git a/Src/ILGPU.Algorithms/Random/RandomRanges.tt b/Src/ILGPU.Algorithms/Random/RandomRanges.tt index 1282190e4..86b85a06c 100644 --- a/Src/ILGPU.Algorithms/Random/RandomRanges.tt +++ b/Src/ILGPU.Algorithms/Random/RandomRanges.tt @@ -1,6 +1,6 @@ // --------------------------------------------------------------------------------------- // ILGPU Algorithms -// Copyright (c) 2023 ILGPU Project +// Copyright (c) 2023-2024 ILGPU Project // www.ilgpu.net // // File: RandomRanges.tt/RandomRanges.cs @@ -11,12 +11,15 @@ <#@ template debug="false" hostspecific="false" language="C#" #> <#@ include file="../TypeInformation.ttinclude"#> +<#@ include file="../FixedPrecision/FixedIntConfig.ttinclude"#> <#@ assembly name="System.Core" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".cs" #> <# -var rngTypes = SignedIntTypes.Concat(FloatTypes); +var rngTypes = SignedIntTypes + .Concat(FloatTypes) + .Concat(FixedPrecisionIntTypes.SelectMany(t => t.ToBasicTypeInformation())); var functionMapping = new Dictionary() { { "Int8", "(byte)randomProvider.Next(0, byte.MaxValue)" }, @@ -29,6 +32,9 @@ var functionMapping = new Dictionary() { "Double", "randomProvider.NextDouble()" }, }; #> +#if NET7_0_OR_GREATER +using ILGPU.Algorithms.FixedPrecision; +#endif using System; using System.Diagnostics.CodeAnalysis; using System.Numerics;