diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs index 8c7c95bfdbf1ad..a92c2f5aa4ccd2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -899,6 +899,214 @@ internal static TValue MultiplyDecimalIeee754(TValue left, TVa return NumberToDecimalIeee754BitsFromWide(resultSign, productHigh, productLow, productExponent, sticky: false); } + /// + /// Computes (left × right) + addend for three IEEE 754 decimal values represented by their raw bit + /// patterns, rounds the exact result once (round-to-nearest, ties-to-even), and returns its bit pattern. + /// + /// + /// The exact product coefficient (up to twice the format precision) is computed at double integer width, so the + /// product is never rounded before the addend is combined with it. The product and addend are aligned to a + /// common exponent within a window wide enough to preserve every digit that can influence the result (including + /// the deepest cancellation); digits below the window are folded into a sticky flag and the aligned coefficients + /// are fed into the shared word-level rounding path, producing the same result as a single rounding of the exact + /// value. This mirrors the mathematical behavior of the Intel reference implementation; a faithful port of its + /// reciprocal-multiply rounding is a possible future performance optimization. + /// + internal static TValue FusedMultiplyAddDecimalIeee754(TValue x, TValue y, TValue z) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + // This code is based on `bid32_fma`, `bid64_fma`, and `bid128_fma` from Intel(R) Decimal Floating-Point Math Library + // Copyright (c) 2007-2025, Intel Corp. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + // A NaN operand propagates its (quieted) payload. The Intel reference inspects the operands in the order + // y, then z, then x, so the first NaN in that order supplies the sign and payload of the result. + if (TDecimal.IsNaN(y)) + { + return PropagateNaN(y, y); + } + + if (TDecimal.IsNaN(z)) + { + return PropagateNaN(z, z); + } + + if (TDecimal.IsNaN(x)) + { + return PropagateNaN(x, x); + } + + // The product sign is the exclusive-or of the factor signs, including zeros and infinities. + bool productSign = TDecimal.IsNegative(x) ^ TDecimal.IsNegative(y); + + bool xInfinity = TDecimal.IsInfinity(x); + bool yInfinity = TDecimal.IsInfinity(y); + + if (xInfinity || yInfinity) + { + // Infinity multiplied by zero is invalid (NaN); the Intel reference emits the canonical quiet NaN. + bool otherZero = (xInfinity && !yInfinity && TValue.IsZero(UnpackDecimalIeee754(y).Significand)) + || (yInfinity && !xInfinity && TValue.IsZero(UnpackDecimalIeee754(x).Significand)); + + if (otherZero) + { + return TDecimal.NaNMask; + } + + // The product is an infinity. Adding an infinity of the opposite sign is invalid (NaN); every other + // addend leaves the product's infinity unchanged (canonicalized). + if (TDecimal.IsInfinity(z) && (TDecimal.IsNegative(z) != productSign)) + { + return TDecimal.NaNMask; + } + + return productSign ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + + // The product is finite. A finite product plus an infinite addend is that infinity (canonicalized). + if (TDecimal.IsInfinity(z)) + { + return TDecimal.IsNegative(z) ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + + DecodedDecimalIeee754 dx = UnpackDecimalIeee754(x); + DecodedDecimalIeee754 dy = UnpackDecimalIeee754(y); + DecodedDecimalIeee754 dz = UnpackDecimalIeee754(z); + + int productExponent = dx.UnbiasedExponent + dy.UnbiasedExponent; + bool productZero = TValue.IsZero(dx.Significand) || TValue.IsZero(dy.Significand); + + bool addendSign = dz.Signed; + int addendExponent = dz.UnbiasedExponent; + TValue addendSignificand = dz.Significand; + bool addendZero = TValue.IsZero(addendSignificand); + + if (productZero) + { + // A zero product reduces the result to the addend at the preferred (smaller) exponent, matching the + // zero handling in addition (the product's preferred exponent is the sum of the factor exponents). + if (addendZero) + { + bool bothNegative = productSign == addendSign && productSign; + return DecimalIeee754FiniteNumberBinaryEncoding(bothNegative, TValue.Zero, Math.Min(productExponent, addendExponent)); + } + + if (productExponent >= addendExponent) + { + return z; + } + + int addendDigits = TDecimal.CountDigits(addendSignificand); + int pad = Math.Min(addendExponent - productExponent, TDecimal.Precision - addendDigits); + + // The product's exponent can be far below the minimum quantum, so bound the padding so the + // result stays at or above it; a zero product cannot push the exact addend into subnormal range. + pad = Math.Min(pad, addendExponent - TDecimal.MinAdjustedExponent); + return DecimalIeee754FiniteNumberBinaryEncoding(addendSign, addendSignificand * TDecimal.Power10(pad), addendExponent - pad); + } + + // The exact product coefficient occupies up to twice the format precision and is held at double width. + WideMultiply(dx.Significand, dy.Significand, out TValue productHigh, out TValue productLow); + int productDigits = WideDigitCount(productHigh, productLow); + + if (addendZero) + { + // Adding a zero lowers the preferred exponent toward the addend's exponent, bounded by the product + // exponent; the alignment below realizes it by scaling the product's coefficient up (padding zeros). + addendExponent = Math.Min(addendExponent, productExponent); + } + + int productMsd = productExponent + productDigits - 1; + int addendDigitsCount = addendZero ? 0 : TDecimal.CountDigits(addendSignificand); + int addendMsd = addendZero ? int.MinValue : addendExponent + addendDigitsCount - 1; + + int maxMsd = Math.Max(productMsd, addendMsd); + int minLsd = Math.Min(productExponent, addendExponent); + + // Retain a window wide enough to hold the full product (up to 2*Precision digits) plus guard digits, which + // also covers the deepest possible cancellation. When the operands are far enough apart that the window + // cannot reach the lower one, its digits fall below the retained range and only contribute stickiness. + int retain = (2 * TDecimal.Precision) + 2; + int commonExponent = Math.Max(minLsd, maxMsd - retain + 1); + + bool sticky = false; + + TValue aHigh = productHigh; + TValue aLow = productLow; + AlignWideToCommonExponent(ref aHigh, ref aLow, productExponent, commonExponent, ref sticky); + + TValue bHigh = TValue.Zero; + TValue bLow = addendSignificand; + + if (!addendZero) + { + AlignWideToCommonExponent(ref bHigh, ref bLow, addendExponent, commonExponent, ref sticky); + } + + bool resultSign; + TValue resultHigh; + TValue resultLow; + + if (productSign == addendSign) + { + // Magnitudes add. + resultHigh = aHigh + bHigh; + resultLow = aLow + bLow; + + if (resultLow < aLow) + { + resultHigh += TValue.One; + } + + resultSign = productSign; + } + else + { + int comparison = WideCompare(aHigh, aLow, bHigh, bLow); + + if (comparison == 0) + { + // The retained magnitudes cancel exactly. Capping only drops digits when the operands are far + // enough apart that one strictly dominates, so an exact cancellation here carries no sticky tail + // and yields +0 under round-to-nearest. + return DecimalIeee754FiniteNumberBinaryEncoding(false, TValue.Zero, commonExponent); + } + + if (comparison > 0) + { + WideSubtract(aHigh, aLow, bHigh, bLow, out resultHigh, out resultLow); + resultSign = productSign; + } + else + { + WideSubtract(bHigh, bLow, aHigh, aLow, out resultHigh, out resultLow); + resultSign = addendSign; + } + + if (sticky) + { + // The dropped tail belongs to the smaller (subtrahend) magnitude, so the exact difference is one + // unit smaller with a non-zero fractional remainder retained in the sticky flag for rounding. + if (TValue.IsZero(resultLow)) + { + resultHigh -= TValue.One; + } + + resultLow -= TValue.One; + } + } + + if (TValue.IsZero(resultHigh) && TValue.IsZero(resultLow) && !sticky) + { + return DecimalIeee754FiniteNumberBinaryEncoding(false, TValue.Zero, commonExponent); + } + + return NumberToDecimalIeee754BitsFromWide(resultSign, resultHigh, resultLow, commonExponent, sticky); + } + /// /// Divides two IEEE 754 decimal values represented by their raw bit patterns and returns the /// bit pattern of the correctly rounded (round-to-nearest, ties-to-even) quotient. @@ -1138,6 +1346,204 @@ internal static TValue RemainderDecimalIeee754(TValue left, TV return DecimalIeee754FiniteNumberBinaryEncoding(resultSign, remainder, resultExponent); } + /// + /// Computes the round-to-nearest IEEE 754 remainder of two decimal values represented by their raw bit + /// patterns: x - y * RoundToNearestEven(x / y). Unlike the % operator this rounds the quotient to + /// the nearest integer (ties to even), so the result magnitude is at most |y| / 2 and its sign may differ + /// from the dividend. + /// + /// + /// The truncated remainder and its integer quotient are formed exactly at the preferred exponent + /// min(exp(x), exp(y)) using the same coefficient reduction as the % operator. When twice the + /// truncated remainder exceeds the divisor coefficient, or equals it while the quotient is odd, the divisor is + /// subtracted and the sign flipped to land on the nearest multiple. Only the final reduction step's quotient + /// determines parity: every earlier partial quotient is scaled by a positive power of ten and is therefore even. + /// + internal static TValue Ieee754RemainderDecimalIeee754(TValue left, TValue right) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger, IMinMaxValue + { + // This code is based on `bid32_rem`, `bid64_rem`, and `bid128_rem` from Intel(R) Decimal Floating-Point Math Library + // Copyright (c) 2007-2025, Intel Corp. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + if (TDecimal.IsNaN(left) || TDecimal.IsNaN(right)) + { + return PropagateNaN(left, right); + } + + bool resultSign = TDecimal.IsNegative(left); + + if (TDecimal.IsInfinity(left)) + { + // Infinity has no finite remainder; the operation is invalid and produces the canonical quiet NaN. + return TDecimal.NaNMask; + } + + DecodedDecimalIeee754 a = UnpackDecimalIeee754(left); + + if (TDecimal.IsInfinity(right)) + { + // A finite value has itself as its remainder modulo infinity; re-encode to a canonical form. + return DecimalIeee754FiniteNumberBinaryEncoding(resultSign, a.Significand, a.UnbiasedExponent); + } + + DecodedDecimalIeee754 b = UnpackDecimalIeee754(right); + + if (TValue.IsZero(b.Significand)) + { + // A remainder with a zero divisor is invalid and produces the canonical quiet NaN. + return TDecimal.NaNMask; + } + + // The preferred exponent of the remainder is the smaller of the two operand exponents. + int resultExponent = Math.Min(a.UnbiasedExponent, b.UnbiasedExponent); + + if (TValue.IsZero(a.Significand)) + { + // Zero modulo any non-zero value is a signed zero at the preferred exponent. + return DecimalIeee754FiniteNumberBinaryEncoding(resultSign, TValue.Zero, resultExponent); + } + + // `remainder` is the truncated remainder and `divisor` the divisor coefficient, both at the preferred + // exponent; `quotientIsOdd` carries the parity of the full integer quotient for the ties-to-even rule. + TValue remainder; + TValue divisor; + bool quotientIsOdd; + + if (a.UnbiasedExponent >= b.UnbiasedExponent) + { + // Reduce `a.Significand * 10^(ea - eb)` modulo `b.Significand`, folding as many trailing zeros per step + // as keep the product within the integer width (capped at the largest cached power of ten). Each step + // tracks its quotient; the parity of the last one equals the parity of the full quotient because the + // earlier partial quotients are each scaled by a later positive power of ten. + divisor = b.Significand; + + TValue quotient = a.Significand / b.Significand; + remainder = a.Significand - (quotient * b.Significand); + + int chunk = 1; + TValue chunkLimit = TValue.MaxValue / b.Significand; + + while ((chunk < TDecimal.Precision - 1) && (TDecimal.Power10(chunk + 1) <= chunkLimit)) + { + chunk++; + } + + for (int gap = a.UnbiasedExponent - b.UnbiasedExponent; (gap > 0) && !TValue.IsZero(remainder); gap -= chunk) + { + int step = Math.Min(chunk, gap); + TValue scaled = remainder * TDecimal.Power10(step); + + quotient = scaled / b.Significand; + remainder = scaled - (quotient * b.Significand); + } + + quotientIsOdd = !TValue.IsZero(quotient & TValue.One); + } + else + { + // The divisor is scaled up by `10^(eb - ea)`. Once the scaled divisor has more than one digit beyond the + // format precision it exceeds twice the dividend, so the dividend is already the nearest remainder. + int gap = b.UnbiasedExponent - a.UnbiasedExponent; + + if (TDecimal.CountDigits(b.Significand) + gap > TDecimal.Precision + 1) + { + return DecimalIeee754FiniteNumberBinaryEncoding(resultSign, a.Significand, resultExponent); + } + + // The scaled divisor has at most `Precision + 1` digits. Peel one factor of ten so the cached + // power-of-ten lookup stays within its table, which only spans exponents below `Precision`. + divisor = (b.Significand * TDecimal.Power10(gap - 1)) * TDecimal.Power10(1); + + TValue quotient = a.Significand / divisor; + remainder = a.Significand - (quotient * divisor); + quotientIsOdd = !TValue.IsZero(quotient & TValue.One); + } + + // Round the quotient to nearest, ties to even: move to the nearer multiple of the divisor when the truncated + // remainder is past the halfway point, or exactly halfway with an odd quotient. The subtraction flips the sign. + TValue twiceRemainder = remainder + remainder; + + if ((twiceRemainder > divisor) || ((twiceRemainder == divisor) && quotientIsOdd)) + { + remainder = divisor - remainder; + resultSign = !resultSign; + } + + return DecimalIeee754FiniteNumberBinaryEncoding(resultSign, remainder, resultExponent); + } + + internal static TValue SqrtDecimalIeee754(TValue value) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger, IMinMaxValue + { + // This code is based on `bid32_sqrt`, `bid64_sqrt`, and `bid128_sqrt` from Intel(R) Decimal Floating-Point Math Library + // Copyright (c) 2007-2025, Intel Corp. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + if (TDecimal.IsNaN(value)) + { + return PropagateNaN(value, value); + } + + bool sign = TDecimal.IsNegative(value); + + if (TDecimal.IsInfinity(value)) + { + // sqrt(+Infinity) is +Infinity; sqrt(-Infinity) is invalid and produces the canonical quiet NaN. + return sign ? TDecimal.NaNMask : TDecimal.PositiveInfinity; + } + + DecodedDecimalIeee754 a = UnpackDecimalIeee754(value); + + // The exact result keeps its preferred exponent, floor(e / 2), for both zeros and perfect squares. + int idealExponent = a.UnbiasedExponent >> 1; + + if (TValue.IsZero(a.Significand)) + { + // sqrt(+/-0) is a signed zero, preserving the sign per IEEE 754. + return DecimalIeee754FiniteNumberBinaryEncoding(sign, TValue.Zero, idealExponent); + } + + if (sign) + { + // sqrt of a negative, non-zero value is invalid and produces the canonical quiet NaN. + return TDecimal.NaNMask; + } + + // Factor the value as coefficient * 10^(2 * idealExponent) by folding one power of ten into the + // coefficient when the exponent is odd, so its square root is sqrt(coefficient) * 10^idealExponent. + TValue coefficient = a.Significand; + + if ((a.UnbiasedExponent & 1) != 0) + { + coefficient *= TDecimal.Power10(1); + } + + (TValue exactRoot, bool isPerfectSquare) = WideSqrt(TValue.Zero, coefficient); + + if (isPerfectSquare) + { + return DecimalIeee754FiniteNumberBinaryEncoding(sign, exactRoot, idealExponent); + } + + // Otherwise scale the coefficient up by an even power of ten so its integer square root carries + // Precision + 1 digits: one guard digit past the format precision for a single correct rounding. + int guard = TDecimal.Precision + 1 - ((TDecimal.CountDigits(coefficient) + 1) / 2); + + TValue high = TValue.Zero; + TValue low = coefficient; + WideScaleByPow10(ref high, ref low, guard * 2); + + (TValue root, bool exact) = WideSqrt(high, low); + return NumberToDecimalIeee754BitsFromWide(sign, TValue.Zero, root, idealExponent - guard, !exact); + } + /// /// Rounds a finite value to fractional digits under . /// The value is coefficient * 10^exponent; when its quantum exponent is already at or above @@ -1240,40 +1646,415 @@ internal static TValue RoundDecimalIeee754(TValue bits, int di } /// - /// Computes the scale factor 10^ used to align addition operands. Exponents - /// within the format's Power10 lookup range (0..Precision - 1) come straight from that table, - /// matching the existing parsing/formatting paths. The slightly larger alignment exponents - /// (Precision..Precision + 2, which the table does not cover) reduce the exponent by the largest table - /// entry each iteration and finish with a single lookup for the remainder. The result always fits a single limb - /// for every supported format. + /// Computes the integer base-10 logarithm of a value: the exponent of its most significant digit. The special + /// cases match , reporting for zero and + /// for both NaN and infinity. /// - private static TValue AlignmentScaleFactor(int exponent) + internal static int ILogBDecimalIeee754(TValue bits) where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo where TValue : unmanaged, IBinaryInteger { - int highestTableExponent = TDecimal.Precision - 1; - TValue largest = TDecimal.Power10(highestTableExponent); - TValue result = TValue.One; + if (!TDecimal.IsFinite(bits)) + { + return int.MaxValue; + } - while (exponent > highestTableExponent) + DecodedDecimalIeee754 a = UnpackDecimalIeee754(bits); + + if (TValue.IsZero(a.Significand)) { - result *= largest; - exponent -= highestTableExponent; + return int.MinValue; } - return result * TDecimal.Power10(exponent); + return a.UnbiasedExponent + TDecimal.CountDigits(a.Significand) - 1; } /// - /// Computes the full (double-width) product of two significands as a pair of - /// limbs ( holds the more significant half). The product of two coefficients can - /// require up to twice the format precision, which always fits in two limbs of the underlying integer width. + /// Multiplies a value by 10^. A surplus exponent is absorbed into trailing zeros of + /// the coefficient while it still fits the format precision; anything beyond that overflows to a signed infinity. + /// A deficit exponent rounds the coefficient (to nearest, ties to even) up to the minimum quantum, underflowing + /// gradually to a subnormal or a signed zero. /// - private static void WideMultiply(TValue left, TValue right, out TValue high, out TValue low) + internal static TValue ScaleBDecimalIeee754(TValue bits, int n) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo where TValue : unmanaged, IBinaryInteger { - // For the 32-bit and 64-bit formats the full product fits in a single wider C# integer (ulong and - // UInt128 respectively), so it comes from one native multiply. The 128-bit format has no wider + // This code is based on `bid32_scalbn`, `bid64_scalbn`, and `bid128_scalbn` from Intel(R) Decimal Floating-Point Math Library + // Copyright (c) 2007-2025, Intel Corp. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + if (TDecimal.IsNaN(bits)) + { + return PropagateNaN(bits, bits); + } + + if (TDecimal.IsInfinity(bits)) + { + // Canonicalize so a non-canonical infinity operand scales to the canonical infinity. + return TDecimal.IsNegative(bits) ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + + DecodedDecimalIeee754 a = UnpackDecimalIeee754(bits); + long exponent = (long)a.UnbiasedExponent + n; + + if (TValue.IsZero(a.Significand)) + { + // Zero carries no significant digits, so the quantum simply clamps into the representable range. + int zeroExponent = (int)Math.Clamp(exponent, TDecimal.MinAdjustedExponent, TDecimal.MaxAdjustedExponent); + return DecimalIeee754FiniteNumberBinaryEncoding(a.Signed, TValue.Zero, zeroExponent); + } + + if (exponent > TDecimal.MaxAdjustedExponent) + { + // Absorb the surplus into trailing zeros while the coefficient stays within the format precision. + long surplus = exponent - TDecimal.MaxAdjustedExponent; + + if (surplus <= TDecimal.Precision - TDecimal.CountDigits(a.Significand)) + { + TValue significand = a.Significand * TDecimal.Power10((int)surplus); + return DecimalIeee754FiniteNumberBinaryEncoding(a.Signed, significand, TDecimal.MaxAdjustedExponent); + } + + return a.Signed ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + + if (exponent < TDecimal.MinAdjustedExponent) + { + // Raise the quantum to the minimum by discarding low-order digits, rounding to nearest with ties to even. + long drop = TDecimal.MinAdjustedExponent - exponent; + int coefficientDigits = TDecimal.CountDigits(a.Significand); + + if (drop > coefficientDigits) + { + // Even the most significant digit sits below half the minimum quantum, so the result is a signed zero. + return DecimalIeee754FiniteNumberBinaryEncoding(a.Signed, TValue.Zero, TDecimal.MinAdjustedExponent); + } + + TValue five = TValue.CreateTruncating(5); + TValue quotient; + int discardedComparedToHalf; + + if (drop == coefficientDigits) + { + // The entire coefficient is discarded; compare it against half of the discarded quantum. + quotient = TValue.Zero; + TValue half = five * TDecimal.Power10(coefficientDigits - 1); + discardedComparedToHalf = a.Significand.CompareTo(half); + } + else + { + TValue divisor = TDecimal.Power10((int)drop); + quotient = a.Significand / divisor; + TValue discarded = a.Significand - (quotient * divisor); + TValue half = five * TDecimal.Power10((int)drop - 1); + discardedComparedToHalf = discarded.CompareTo(half); + } + + if ((discardedComparedToHalf > 0) || ((discardedComparedToHalf == 0) && !TValue.IsZero(quotient & TValue.One))) + { + quotient += TValue.One; + } + + return DecimalIeee754FiniteNumberBinaryEncoding(a.Signed, quotient, TDecimal.MinAdjustedExponent); + } + + // The shifted quantum is already representable, so the coefficient is preserved exactly. + return DecimalIeee754FiniteNumberBinaryEncoding(a.Signed, a.Significand, (int)exponent); + } + + /// + /// Re-expresses with the quantum (exponent) of , rounding the + /// coefficient to nearest with ties to even (IEEE 754-2019 §5.3.2 quantize). The numeric value of + /// is otherwise unused. A NaN operand propagates ( before + /// ); one infinite and one finite operand is invalid and yields a quiet NaN, while two + /// infinities keep the sign of . When the requested quantum cannot represent the value + /// within the format precision the result is a quiet NaN. + /// + internal static TValue QuantizeDecimalIeee754(TValue x, TValue y) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + // This code is based on `bid32_quantize`, `bid64_quantize`, and `bid128_quantize` from Intel(R) Decimal Floating-Point Math Library + // Copyright (c) 2007-2025, Intel Corp. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + if (TDecimal.IsNaN(x) || TDecimal.IsNaN(y)) + { + // Propagate the first NaN operand (x before y), canonicalized to a quiet NaN. + return PropagateNaN(x, y); + } + + if (TDecimal.IsInfinity(x) || TDecimal.IsInfinity(y)) + { + if (TDecimal.IsInfinity(x) && TDecimal.IsInfinity(y)) + { + // Two infinities share a quantum; the result keeps the sign of x. + return TDecimal.IsNegative(x) ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + + // One infinite and one finite operand cannot share a quantum, so the result is invalid. + return TDecimal.NaNMask; + } + + DecodedDecimalIeee754 a = UnpackDecimalIeee754(x); + int targetExponent = UnpackDecimalIeee754(y).UnbiasedExponent; + + if (TValue.IsZero(a.Significand)) + { + // Zero carries no significant digits, so it simply adopts the requested quantum. + return DecimalIeee754FiniteNumberBinaryEncoding(a.Signed, TValue.Zero, targetExponent); + } + + int coefficientDigits = TDecimal.CountDigits(a.Significand); + + if (targetExponent <= a.UnbiasedExponent) + { + // A quantum at or below the current one only scales the coefficient up; it is exact unless the + // scaled coefficient would exceed the format precision, in which case the value is unrepresentable. + int shift = a.UnbiasedExponent - targetExponent; + + if (coefficientDigits + shift > TDecimal.Precision) + { + return TDecimal.NaNMask; + } + + TValue significand = a.Significand * TDecimal.Power10(shift); + return DecimalIeee754FiniteNumberBinaryEncoding(a.Signed, significand, targetExponent); + } + + // A coarser quantum discards low-order digits; rounding away never overflows the precision because the + // magnitude only shrinks. + int drop = targetExponent - a.UnbiasedExponent; + TValue five = TValue.CreateTruncating(5); + TValue quotient; + int discardedComparedToHalf; + + if (drop >= coefficientDigits) + { + quotient = TValue.Zero; + + if (drop > coefficientDigits) + { + // The whole coefficient is strictly below half of the discarded quantum. + discardedComparedToHalf = -1; + } + else + { + TValue half = five * TDecimal.Power10(coefficientDigits - 1); + discardedComparedToHalf = a.Significand.CompareTo(half); + } + } + else + { + TValue divisor = TDecimal.Power10(drop); + TValue discarded; + (quotient, discarded) = TValue.DivRem(a.Significand, divisor); + + TValue half = five * TDecimal.Power10(drop - 1); + discardedComparedToHalf = discarded.CompareTo(half); + } + + if ((discardedComparedToHalf > 0) || ((discardedComparedToHalf == 0) && !TValue.IsZero(quotient & TValue.One))) + { + quotient += TValue.One; + } + + return DecimalIeee754FiniteNumberBinaryEncoding(a.Signed, quotient, targetExponent); + } + + /// + /// Returns the quantum of : 1 × 10^exp sharing its exponent (IEEE 754-2019 + /// §5.3.2 quantum). The result is always positive. Infinity yields positive infinity and NaN + /// propagates, canonicalized to a quiet NaN. + /// + internal static TValue QuantumDecimalIeee754(TValue bits) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + // This code is based on `bid32_quantum`, `bid64_quantum`, and `bid128_quantum` from Intel(R) Decimal Floating-Point Math Library + // Copyright (c) 2007-2025, Intel Corp. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + if (TDecimal.IsNaN(bits)) + { + return PropagateNaN(bits, bits); + } + + if (TDecimal.IsInfinity(bits)) + { + return TDecimal.PositiveInfinity; + } + + DecodedDecimalIeee754 a = UnpackDecimalIeee754(bits); + return DecimalIeee754FiniteNumberBinaryEncoding(signed: false, TValue.One, a.UnbiasedExponent); + } + + /// + /// Determines whether and have the same quantum (exponent), following + /// IEEE 754-2019 §5.7.3 sameQuantum. Two NaNs share a quantum and two infinities share a quantum; any + /// other pairing that involves a NaN or infinity does not. No exception is ever signaled. + /// + internal static bool SameQuantumDecimalIeee754(TValue x, TValue y) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + bool xNaN = TDecimal.IsNaN(x); + bool yNaN = TDecimal.IsNaN(y); + + if (xNaN || yNaN) + { + return xNaN && yNaN; + } + + bool xInfinity = TDecimal.IsInfinity(x); + bool yInfinity = TDecimal.IsInfinity(y); + + if (xInfinity || yInfinity) + { + return xInfinity && yInfinity; + } + + return UnpackDecimalIeee754(x).UnbiasedExponent == UnpackDecimalIeee754(y).UnbiasedExponent; + } + + /// + /// Returns the least value that compares greater than (IEEE 754 nextUp). NaN is + /// returned unchanged, positive infinity is its own successor, and negative infinity steps to the most negative + /// finite value. + /// + internal static TValue BitIncrementDecimalIeee754(TValue bits) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + // This code is based on `bid32_nextup`, `bid64_nextup`, and `bid128_nextup` from Intel(R) Decimal Floating-Point Math Library + // Copyright (c) 2007-2025, Intel Corp. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + if (TDecimal.IsNaN(bits)) + { + return PropagateNaN(bits, bits); + } + + if (TDecimal.IsInfinity(bits)) + { + return TDecimal.IsNegative(bits) + ? DecimalIeee754FiniteNumberBinaryEncoding(signed: true, TDecimal.MaxSignificand, TDecimal.MaxAdjustedExponent) + : TDecimal.PositiveInfinity; + } + + DecodedDecimalIeee754 a = UnpackDecimalIeee754(bits); + + if (TValue.IsZero(a.Significand)) + { + // The successor of any zero is the smallest positive subnormal. + return DecimalIeee754FiniteNumberBinaryEncoding(signed: false, TValue.One, TDecimal.MinAdjustedExponent); + } + + int exponent = a.UnbiasedExponent; + + if (!a.Signed && (a.Significand == TDecimal.MaxSignificand) && (exponent == TDecimal.MaxAdjustedExponent)) + { + // The successor of the largest finite value is positive infinity. + return TDecimal.PositiveInfinity; + } + + if (a.Signed && (a.Significand == TValue.One) && (exponent == TDecimal.MinAdjustedExponent)) + { + // The successor of the smallest negative subnormal is negative zero. + return DecimalIeee754FiniteNumberBinaryEncoding(signed: true, TValue.Zero, TDecimal.MinAdjustedExponent); + } + + // Pad the coefficient toward the minimum quantum (bounded by the precision and the minimum exponent) so a + // single ulp is the smallest representable step at this magnitude. + TValue significand = a.Significand; + int padding = Math.Min(TDecimal.Precision - TDecimal.CountDigits(significand), exponent - TDecimal.MinAdjustedExponent); + + if (padding > 0) + { + significand *= TDecimal.Power10(padding); + exponent -= padding; + } + + if (!a.Signed) + { + // Stepping away from zero adds one ulp, carrying into the next exponent at the precision boundary. + significand += TValue.One; + + if (significand > TDecimal.MaxSignificand) + { + significand = TDecimal.Power10(TDecimal.Precision - 1); + exponent++; + } + } + else + { + // Stepping toward zero subtracts one ulp, borrowing from the next exponent at the precision boundary. + significand -= TValue.One; + + if ((significand == (TDecimal.Power10(TDecimal.Precision - 1) - TValue.One)) && (exponent != TDecimal.MinAdjustedExponent)) + { + significand = TDecimal.MaxSignificand; + exponent--; + } + } + + return DecimalIeee754FiniteNumberBinaryEncoding(a.Signed, significand, exponent); + } + + /// + /// Returns the greatest value that compares less than (IEEE 754 nextDown), + /// computed from the successor identity nextDown(x) = -nextUp(-x). + /// + internal static TValue BitDecrementDecimalIeee754(TValue bits) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + return BitIncrementDecimalIeee754(bits ^ TDecimal.SignMask) ^ TDecimal.SignMask; + } + + /// + /// Computes the scale factor 10^ used to align addition operands. Exponents + /// within the format's Power10 lookup range (0..Precision - 1) come straight from that table, + /// matching the existing parsing/formatting paths. The slightly larger alignment exponents + /// (Precision..Precision + 2, which the table does not cover) reduce the exponent by the largest table + /// entry each iteration and finish with a single lookup for the remainder. The result always fits a single limb + /// for every supported format. + /// + private static TValue AlignmentScaleFactor(int exponent) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + int highestTableExponent = TDecimal.Precision - 1; + TValue largest = TDecimal.Power10(highestTableExponent); + TValue result = TValue.One; + + while (exponent > highestTableExponent) + { + result *= largest; + exponent -= highestTableExponent; + } + + return result * TDecimal.Power10(exponent); + } + + /// + /// Computes the full (double-width) product of two significands as a pair of + /// limbs ( holds the more significant half). The product of two coefficients can + /// require up to twice the format precision, which always fits in two limbs of the underlying integer width. + /// + private static void WideMultiply(TValue left, TValue right, out TValue high, out TValue low) + where TValue : unmanaged, IBinaryInteger + { + // For the 32-bit and 64-bit formats the full product fits in a single wider C# integer (ulong and + // UInt128 respectively), so it comes from one native multiply. The 128-bit format has no wider // native type and uses the schoolbook half-limb decomposition below. if (typeof(TValue) == typeof(uint)) { @@ -1310,6 +2091,114 @@ private static void WideMultiply(TValue left, TValue right, out TValue h high = highHigh + (lowHigh >> half) + (highLow >> half) + (cross >> half); } + /// + /// Multiplies the double-width value in (, ) by a single-limb + /// in place. The caller guarantees the scaled result still fits two limbs. + /// + private static void WideMultiplyByLimb(ref TValue high, ref TValue low, TValue factor) + where TValue : unmanaged, IBinaryInteger + { + WideMultiply(low, factor, out TValue lowHigh, out TValue lowLow); + WideMultiply(high, factor, out TValue highHigh, out TValue highLow); + + TValue newHigh = highLow + lowHigh; + + // The result fits two limbs by construction: the high limb never carries out and `high * factor` never + // spills past the second limb. + Debug.Assert(TValue.IsZero(highHigh)); + Debug.Assert(newHigh >= highLow); + + high = newHigh; + low = lowLow; + } + + /// + /// Scales the double-width value in (, ) up by + /// 10^ in place, multiplying by the largest single-limb power of ten each step. + /// The caller guarantees the scaled result still fits two limbs. + /// + private static void WideScaleByPow10(ref TValue high, ref TValue low, int power) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + int highestTableExponent = TDecimal.Precision - 1; + + while (power > 0) + { + int chunk = Math.Min(power, highestTableExponent); + WideMultiplyByLimb(ref high, ref low, TDecimal.Power10(chunk)); + power -= chunk; + } + } + + /// + /// Drops the least-significant decimal digits from the double-width value in + /// (, ) in place, folding every dropped digit into + /// . Used to align an addition operand whose low digits fall below the retained window, + /// where all dropped digits lie below the rounding position and therefore only contribute stickiness. + /// + private static void WideDropLowDigits(ref TValue high, ref TValue low, int dropCount, ref bool sticky) + where TValue : unmanaged, IBinaryInteger + { + for (int i = 0; i < dropCount; i++) + { + if (TValue.IsZero(high) && TValue.IsZero(low)) + { + break; + } + + sticky |= WideDivideByTen(ref high, ref low) != 0; + } + } + + /// + /// Aligns the double-width coefficient in (, ), whose value is + /// coefficient·10^, to in place. A larger + /// exponent scales the coefficient up (exact); a smaller exponent drops the low digits into + /// . + /// + private static void AlignWideToCommonExponent(ref TValue high, ref TValue low, int exponent, int commonExponent, ref bool sticky) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + if (exponent > commonExponent) + { + WideScaleByPow10(ref high, ref low, exponent - commonExponent); + } + else if (exponent < commonExponent) + { + WideDropLowDigits(ref high, ref low, commonExponent - exponent, ref sticky); + } + } + + /// + /// Compares two double-width magnitudes, returning a negative value, zero, or a positive value according to + /// whether the first is less than, equal to, or greater than the second. + /// + private static int WideCompare(TValue leftHigh, TValue leftLow, TValue rightHigh, TValue rightLow) + where TValue : unmanaged, IBinaryInteger + { + int highComparison = leftHigh.CompareTo(rightHigh); + return highComparison != 0 ? highComparison : leftLow.CompareTo(rightLow); + } + + /// + /// Subtracts the double-width magnitude (, ) from + /// (, ), which the caller guarantees is the larger. + /// + private static void WideSubtract(TValue leftHigh, TValue leftLow, TValue rightHigh, TValue rightLow, out TValue high, out TValue low) + where TValue : unmanaged, IBinaryInteger + { + high = leftHigh - rightHigh; + + if (leftLow < rightLow) + { + high -= TValue.One; + } + + low = leftLow - rightLow; + } + /// /// Divides the double-width value in (, ) by ten in place, /// returning the discarded least-significant decimal digit. Used to strip low-order digits during rounding. @@ -1386,6 +2275,45 @@ private static int WideDigitCount(TValue high, TValue low) return count + TDecimal.CountDigits(low); } + /// + /// Computes the integer square root of the double-width value (, ), + /// returning floor(sqrt(value)) and whether the value is a perfect square. The root fits a single limb + /// because the input spans at most twice the limb width, so its square root spans at most one. + /// + private static (TValue Root, bool IsExact) WideSqrt(TValue high, TValue low) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + // Binary long-division square root: consume the input two bits at a time from the most-significant end, + // appending a bit to the root when the trial subtrahend `2 * root + 1` fits the running remainder. The + // remainder stays below `2 * root`, and the coefficients here never exceed Precision + 1 digits, so the + // root, remainder, and trial subtrahend all fit a single limb even for the 128-bit format. + int limbBits = TValue.Zero.GetByteCount() * 8; + TValue three = TValue.CreateTruncating(3); + + TValue root = TValue.Zero; + TValue remainder = TValue.Zero; + + for (int pair = limbBits - 1; pair >= 0; pair--) + { + int shift = pair * 2; + TValue twoBits = (shift >= limbBits) ? ((high >> (shift - limbBits)) & three) : ((low >> shift) & three); + + remainder = (remainder << 2) | twoBits; + root <<= 1; + + TValue trial = (root << 1) | TValue.One; + + if (remainder >= trial) + { + remainder -= trial; + root += TValue.One; + } + } + + return (root, TValue.IsZero(remainder)); + } + /// /// Removes the least-significant decimal digits from the double-width value, /// returning the retained significand (which fits in a single limb). The most-significant removed digit is diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs index 2bfa96b4ce84af..cab2d6884edb65 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -852,6 +852,48 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span desti return false; } + // + // IFloatingPointIeee754 + // + + /// + public static Decimal128 BitDecrement(Decimal128 x) => new Decimal128(Number.BitDecrementDecimalIeee754(new UInt128(x._upper, x._lower))); + + /// + public static Decimal128 BitIncrement(Decimal128 x) => new Decimal128(Number.BitIncrementDecimalIeee754(new UInt128(x._upper, x._lower))); + + /// + public static Decimal128 FusedMultiplyAdd(Decimal128 left, Decimal128 right, Decimal128 addend) => new Decimal128(Number.FusedMultiplyAddDecimalIeee754(new UInt128(left._upper, left._lower), new UInt128(right._upper, right._lower), new UInt128(addend._upper, addend._lower))); + + /// + public static Decimal128 Ieee754Remainder(Decimal128 left, Decimal128 right) => new Decimal128(Number.Ieee754RemainderDecimalIeee754(new UInt128(left._upper, left._lower), new UInt128(right._upper, right._lower))); + + /// + public static int ILogB(Decimal128 x) => Number.ILogBDecimalIeee754(new UInt128(x._upper, x._lower)); + + /// + public static Decimal128 ScaleB(Decimal128 x, int n) => new Decimal128(Number.ScaleBDecimalIeee754(new UInt128(x._upper, x._lower), n)); + + /// + public static Decimal128 Sqrt(Decimal128 x) => new Decimal128(Number.SqrtDecimalIeee754(new UInt128(x._upper, x._lower))); + + /// Adjusts a value to the quantum (exponent) of another value, rounding to nearest with ties to even. + /// The value whose quantum is adjusted. + /// The value that provides the target quantum. + /// expressed with the quantum of , or NaN when the value cannot be represented at that quantum. + public static Decimal128 Quantize(Decimal128 x, Decimal128 y) => new Decimal128(Number.QuantizeDecimalIeee754(new UInt128(x._upper, x._lower), new UInt128(y._upper, y._lower))); + + /// Computes the quantum of a value: one unit in the last place sharing its exponent. + /// The value whose quantum is returned. + /// The quantum of . + public static Decimal128 Quantum(Decimal128 x) => new Decimal128(Number.QuantumDecimalIeee754(new UInt128(x._upper, x._lower))); + + /// Determines whether two values have the same quantum (exponent). + /// The first value to compare. + /// The second value to compare. + /// true if and have the same quantum; otherwise, false. + public static bool SameQuantum(Decimal128 x, Decimal128 y) => Number.SameQuantumDecimalIeee754(new UInt128(x._upper, x._lower), new UInt128(y._upper, y._lower)); + /// Computes the absolute of a value. /// The value for which to get its absolute. /// The absolute of . diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs index 8000356c12d92b..64b7f787800a61 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -875,6 +875,48 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destin return false; } + // + // IFloatingPointIeee754 + // + + /// + public static Decimal32 BitDecrement(Decimal32 x) => new Decimal32(Number.BitDecrementDecimalIeee754(x._value)); + + /// + public static Decimal32 BitIncrement(Decimal32 x) => new Decimal32(Number.BitIncrementDecimalIeee754(x._value)); + + /// + public static Decimal32 FusedMultiplyAdd(Decimal32 left, Decimal32 right, Decimal32 addend) => new Decimal32(Number.FusedMultiplyAddDecimalIeee754(left._value, right._value, addend._value)); + + /// + public static Decimal32 Ieee754Remainder(Decimal32 left, Decimal32 right) => new Decimal32(Number.Ieee754RemainderDecimalIeee754(left._value, right._value)); + + /// + public static int ILogB(Decimal32 x) => Number.ILogBDecimalIeee754(x._value); + + /// + public static Decimal32 ScaleB(Decimal32 x, int n) => new Decimal32(Number.ScaleBDecimalIeee754(x._value, n)); + + /// + public static Decimal32 Sqrt(Decimal32 x) => new Decimal32(Number.SqrtDecimalIeee754(x._value)); + + /// Adjusts a value to the quantum (exponent) of another value, rounding to nearest with ties to even. + /// The value whose quantum is adjusted. + /// The value that provides the target quantum. + /// expressed with the quantum of , or NaN when the value cannot be represented at that quantum. + public static Decimal32 Quantize(Decimal32 x, Decimal32 y) => new Decimal32(Number.QuantizeDecimalIeee754(x._value, y._value)); + + /// Computes the quantum of a value: one unit in the last place sharing its exponent. + /// The value whose quantum is returned. + /// The quantum of . + public static Decimal32 Quantum(Decimal32 x) => new Decimal32(Number.QuantumDecimalIeee754(x._value)); + + /// Determines whether two values have the same quantum (exponent). + /// The first value to compare. + /// The second value to compare. + /// true if and have the same quantum; otherwise, false. + public static bool SameQuantum(Decimal32 x, Decimal32 y) => Number.SameQuantumDecimalIeee754(x._value, y._value); + /// Computes the absolute of a value. /// The value for which to get its absolute. /// The absolute of . diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs index 1b08a963722056..f00c426c3743ad 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -866,6 +866,48 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destin return false; } + // + // IFloatingPointIeee754 + // + + /// + public static Decimal64 BitDecrement(Decimal64 x) => new Decimal64(Number.BitDecrementDecimalIeee754(x._value)); + + /// + public static Decimal64 BitIncrement(Decimal64 x) => new Decimal64(Number.BitIncrementDecimalIeee754(x._value)); + + /// + public static Decimal64 FusedMultiplyAdd(Decimal64 left, Decimal64 right, Decimal64 addend) => new Decimal64(Number.FusedMultiplyAddDecimalIeee754(left._value, right._value, addend._value)); + + /// + public static Decimal64 Ieee754Remainder(Decimal64 left, Decimal64 right) => new Decimal64(Number.Ieee754RemainderDecimalIeee754(left._value, right._value)); + + /// + public static int ILogB(Decimal64 x) => Number.ILogBDecimalIeee754(x._value); + + /// + public static Decimal64 ScaleB(Decimal64 x, int n) => new Decimal64(Number.ScaleBDecimalIeee754(x._value, n)); + + /// + public static Decimal64 Sqrt(Decimal64 x) => new Decimal64(Number.SqrtDecimalIeee754(x._value)); + + /// Adjusts a value to the quantum (exponent) of another value, rounding to nearest with ties to even. + /// The value whose quantum is adjusted. + /// The value that provides the target quantum. + /// expressed with the quantum of , or NaN when the value cannot be represented at that quantum. + public static Decimal64 Quantize(Decimal64 x, Decimal64 y) => new Decimal64(Number.QuantizeDecimalIeee754(x._value, y._value)); + + /// Computes the quantum of a value: one unit in the last place sharing its exponent. + /// The value whose quantum is returned. + /// The quantum of . + public static Decimal64 Quantum(Decimal64 x) => new Decimal64(Number.QuantumDecimalIeee754(x._value)); + + /// Determines whether two values have the same quantum (exponent). + /// The first value to compare. + /// The second value to compare. + /// true if and have the same quantum; otherwise, false. + public static bool SameQuantum(Decimal64 x, Decimal64 y) => Number.SameQuantumDecimalIeee754(x._value, y._value); + /// Computes the absolute of a value. /// The value for which to get its absolute. /// The absolute of . diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 21f8d460f3f0fb..90d9559878c4de 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11449,6 +11449,8 @@ namespace System.Numerics public static System.Numerics.Decimal128 Tau { get { throw null; } } public static System.Numerics.Decimal128 Zero { get { throw null; } } public static System.Numerics.Decimal128 Abs(System.Numerics.Decimal128 value) { throw null; } + public static System.Numerics.Decimal128 BitDecrement(System.Numerics.Decimal128 x) { throw null; } + public static System.Numerics.Decimal128 BitIncrement(System.Numerics.Decimal128 x) { throw null; } public static System.Numerics.Decimal128 Ceiling(System.Numerics.Decimal128 x) { throw null; } public static System.Numerics.Decimal128 Clamp(System.Numerics.Decimal128 value, System.Numerics.Decimal128 min, System.Numerics.Decimal128 max) { throw null; } public static System.Numerics.Decimal128 ClampNative(System.Numerics.Decimal128 value, System.Numerics.Decimal128 min, System.Numerics.Decimal128 max) { throw null; } @@ -11463,7 +11465,10 @@ namespace System.Numerics public bool Equals(System.Numerics.Decimal128 other) { throw null; } public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public static System.Numerics.Decimal128 Floor(System.Numerics.Decimal128 x) { throw null; } + public static System.Numerics.Decimal128 FusedMultiplyAdd(System.Numerics.Decimal128 left, System.Numerics.Decimal128 right, System.Numerics.Decimal128 addend) { throw null; } public override int GetHashCode() { throw null; } + public static int ILogB(System.Numerics.Decimal128 x) { throw null; } + public static System.Numerics.Decimal128 Ieee754Remainder(System.Numerics.Decimal128 left, System.Numerics.Decimal128 right) { throw null; } public static bool IsEvenInteger(System.Numerics.Decimal128 value) { throw null; } public static bool IsFinite(System.Numerics.Decimal128 value) { throw null; } public static bool IsInfinity(System.Numerics.Decimal128 value) { throw null; } @@ -11576,11 +11581,16 @@ namespace System.Numerics public static System.Numerics.Decimal128 Parse(string s, System.Globalization.NumberStyles style) { throw null; } public static System.Numerics.Decimal128 Parse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; } public static System.Numerics.Decimal128 Parse(string s, System.IFormatProvider? provider) { throw null; } + public static System.Numerics.Decimal128 Quantize(System.Numerics.Decimal128 x, System.Numerics.Decimal128 y) { throw null; } + public static System.Numerics.Decimal128 Quantum(System.Numerics.Decimal128 x) { throw null; } public static System.Numerics.Decimal128 Round(System.Numerics.Decimal128 x) { throw null; } public static System.Numerics.Decimal128 Round(System.Numerics.Decimal128 x, int digits) { throw null; } public static System.Numerics.Decimal128 Round(System.Numerics.Decimal128 x, int digits, System.MidpointRounding mode) { throw null; } public static System.Numerics.Decimal128 Round(System.Numerics.Decimal128 x, System.MidpointRounding mode) { throw null; } + public static bool SameQuantum(System.Numerics.Decimal128 x, System.Numerics.Decimal128 y) { throw null; } + public static System.Numerics.Decimal128 ScaleB(System.Numerics.Decimal128 x, int n) { throw null; } public static int Sign(System.Numerics.Decimal128 value) { throw null; } + public static System.Numerics.Decimal128 Sqrt(System.Numerics.Decimal128 x) { throw null; } int System.Numerics.IFloatingPoint.GetExponentByteCount() { throw null; } int System.Numerics.IFloatingPoint.GetExponentShortestBitLength() { throw null; } int System.Numerics.IFloatingPoint.GetSignificandBitLength() { throw null; } @@ -11639,6 +11649,8 @@ namespace System.Numerics public static System.Numerics.Decimal32 Tau { get { throw null; } } public static System.Numerics.Decimal32 Zero { get { throw null; } } public static System.Numerics.Decimal32 Abs(System.Numerics.Decimal32 value) { throw null; } + public static System.Numerics.Decimal32 BitDecrement(System.Numerics.Decimal32 x) { throw null; } + public static System.Numerics.Decimal32 BitIncrement(System.Numerics.Decimal32 x) { throw null; } public static System.Numerics.Decimal32 Ceiling(System.Numerics.Decimal32 x) { throw null; } public static System.Numerics.Decimal32 Clamp(System.Numerics.Decimal32 value, System.Numerics.Decimal32 min, System.Numerics.Decimal32 max) { throw null; } public static System.Numerics.Decimal32 ClampNative(System.Numerics.Decimal32 value, System.Numerics.Decimal32 min, System.Numerics.Decimal32 max) { throw null; } @@ -11653,7 +11665,10 @@ namespace System.Numerics public bool Equals(System.Numerics.Decimal32 other) { throw null; } public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public static System.Numerics.Decimal32 Floor(System.Numerics.Decimal32 x) { throw null; } + public static System.Numerics.Decimal32 FusedMultiplyAdd(System.Numerics.Decimal32 left, System.Numerics.Decimal32 right, System.Numerics.Decimal32 addend) { throw null; } public override int GetHashCode() { throw null; } + public static int ILogB(System.Numerics.Decimal32 x) { throw null; } + public static System.Numerics.Decimal32 Ieee754Remainder(System.Numerics.Decimal32 left, System.Numerics.Decimal32 right) { throw null; } public static bool IsEvenInteger(System.Numerics.Decimal32 value) { throw null; } public static bool IsFinite(System.Numerics.Decimal32 value) { throw null; } public static bool IsInfinity(System.Numerics.Decimal32 value) { throw null; } @@ -11770,11 +11785,16 @@ namespace System.Numerics public static System.Numerics.Decimal32 Parse(string s, System.Globalization.NumberStyles style) { throw null; } public static System.Numerics.Decimal32 Parse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; } public static System.Numerics.Decimal32 Parse(string s, System.IFormatProvider? provider) { throw null; } + public static System.Numerics.Decimal32 Quantize(System.Numerics.Decimal32 x, System.Numerics.Decimal32 y) { throw null; } + public static System.Numerics.Decimal32 Quantum(System.Numerics.Decimal32 x) { throw null; } public static System.Numerics.Decimal32 Round(System.Numerics.Decimal32 x) { throw null; } public static System.Numerics.Decimal32 Round(System.Numerics.Decimal32 x, int digits) { throw null; } public static System.Numerics.Decimal32 Round(System.Numerics.Decimal32 x, int digits, System.MidpointRounding mode) { throw null; } public static System.Numerics.Decimal32 Round(System.Numerics.Decimal32 x, System.MidpointRounding mode) { throw null; } + public static bool SameQuantum(System.Numerics.Decimal32 x, System.Numerics.Decimal32 y) { throw null; } + public static System.Numerics.Decimal32 ScaleB(System.Numerics.Decimal32 x, int n) { throw null; } public static int Sign(System.Numerics.Decimal32 value) { throw null; } + public static System.Numerics.Decimal32 Sqrt(System.Numerics.Decimal32 x) { throw null; } int System.Numerics.IFloatingPoint.GetExponentByteCount() { throw null; } int System.Numerics.IFloatingPoint.GetExponentShortestBitLength() { throw null; } int System.Numerics.IFloatingPoint.GetSignificandBitLength() { throw null; } @@ -11833,6 +11853,8 @@ namespace System.Numerics public static System.Numerics.Decimal64 Tau { get { throw null; } } public static System.Numerics.Decimal64 Zero { get { throw null; } } public static System.Numerics.Decimal64 Abs(System.Numerics.Decimal64 value) { throw null; } + public static System.Numerics.Decimal64 BitDecrement(System.Numerics.Decimal64 x) { throw null; } + public static System.Numerics.Decimal64 BitIncrement(System.Numerics.Decimal64 x) { throw null; } public static System.Numerics.Decimal64 Ceiling(System.Numerics.Decimal64 x) { throw null; } public static System.Numerics.Decimal64 Clamp(System.Numerics.Decimal64 value, System.Numerics.Decimal64 min, System.Numerics.Decimal64 max) { throw null; } public static System.Numerics.Decimal64 ClampNative(System.Numerics.Decimal64 value, System.Numerics.Decimal64 min, System.Numerics.Decimal64 max) { throw null; } @@ -11847,7 +11869,10 @@ namespace System.Numerics public bool Equals(System.Numerics.Decimal64 other) { throw null; } public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public static System.Numerics.Decimal64 Floor(System.Numerics.Decimal64 x) { throw null; } + public static System.Numerics.Decimal64 FusedMultiplyAdd(System.Numerics.Decimal64 left, System.Numerics.Decimal64 right, System.Numerics.Decimal64 addend) { throw null; } public override int GetHashCode() { throw null; } + public static int ILogB(System.Numerics.Decimal64 x) { throw null; } + public static System.Numerics.Decimal64 Ieee754Remainder(System.Numerics.Decimal64 left, System.Numerics.Decimal64 right) { throw null; } public static bool IsEvenInteger(System.Numerics.Decimal64 value) { throw null; } public static bool IsFinite(System.Numerics.Decimal64 value) { throw null; } public static bool IsInfinity(System.Numerics.Decimal64 value) { throw null; } @@ -11962,11 +11987,16 @@ namespace System.Numerics public static System.Numerics.Decimal64 Parse(string s, System.Globalization.NumberStyles style) { throw null; } public static System.Numerics.Decimal64 Parse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; } public static System.Numerics.Decimal64 Parse(string s, System.IFormatProvider? provider) { throw null; } + public static System.Numerics.Decimal64 Quantize(System.Numerics.Decimal64 x, System.Numerics.Decimal64 y) { throw null; } + public static System.Numerics.Decimal64 Quantum(System.Numerics.Decimal64 x) { throw null; } public static System.Numerics.Decimal64 Round(System.Numerics.Decimal64 x) { throw null; } public static System.Numerics.Decimal64 Round(System.Numerics.Decimal64 x, int digits) { throw null; } public static System.Numerics.Decimal64 Round(System.Numerics.Decimal64 x, int digits, System.MidpointRounding mode) { throw null; } public static System.Numerics.Decimal64 Round(System.Numerics.Decimal64 x, System.MidpointRounding mode) { throw null; } + public static bool SameQuantum(System.Numerics.Decimal64 x, System.Numerics.Decimal64 y) { throw null; } + public static System.Numerics.Decimal64 ScaleB(System.Numerics.Decimal64 x, int n) { throw null; } public static int Sign(System.Numerics.Decimal64 value) { throw null; } + public static System.Numerics.Decimal64 Sqrt(System.Numerics.Decimal64 x) { throw null; } int System.Numerics.IFloatingPoint.GetExponentByteCount() { throw null; } int System.Numerics.IFloatingPoint.GetExponentShortestBitLength() { throw null; } int System.Numerics.IFloatingPoint.GetSignificandBitLength() { throw null; } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs index 0f20a8b5eb1960..ddceb1771fe174 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs @@ -1530,6 +1530,281 @@ public static void TryParsePartial_Invalid(string value, NumberStyles style, IFo Assert.Equal(0, bytesConsumed); } + [Theory] + [InlineData(0x3040000000000000UL, 0x0000000000000000UL, 0x0000000000000000UL, 0x0000000000000001UL)] // +0 -> +MINFP + [InlineData(0xB040000000000000UL, 0x0000000000000000UL, 0x0000000000000000UL, 0x0000000000000001UL)] // -0 -> +MINFP + [InlineData(0x3040000000000000UL, 0x0000000000000001UL, 0x2FFE314DC6448D93UL, 0x38C15B0A00000001UL)] // 1 -> 1.000...001 + [InlineData(0xB040000000000000UL, 0x0000000000000001UL, 0xAFFDED09BEAD87C0UL, 0x378D8E63FFFFFFFFUL)] // -1 -> -0.999...9 + [InlineData(0x5FFFED09BEAD87C0UL, 0x378D8E63FFFFFFFFUL, 0x7800000000000000UL, 0x0000000000000000UL)] // +MAXFP -> +Infinity + [InlineData(0xDFFFED09BEAD87C0UL, 0x378D8E63FFFFFFFFUL, 0xDFFFED09BEAD87C0UL, 0x378D8E63FFFFFFFEUL)] // -MAXFP steps toward zero + [InlineData(0x0000000000000000UL, 0x0000000000000001UL, 0x0000000000000000UL, 0x0000000000000002UL)] // +MINFP + [InlineData(0x8000000000000000UL, 0x0000000000000001UL, 0x8000000000000000UL, 0x0000000000000000UL)] // -MINFP -> -0 + [InlineData(0x2FFFED09BEAD87C0UL, 0x378D8E63FFFFFFFFUL, 0x3000314DC6448D93UL, 0x38C15B0A00000000UL)] // coefficient carry + [InlineData(0x7800000000000000UL, 0x0000000000000000UL, 0x7800000000000000UL, 0x0000000000000000UL)] // +Infinity + [InlineData(0xF800000000000000UL, 0x0000000000000000UL, 0xDFFFED09BEAD87C0UL, 0x378D8E63FFFFFFFFUL)] // -Infinity -> -MAXFP + [InlineData(0xFC00000000000000UL, 0x0000000000000000UL, 0xFC00000000000000UL, 0x0000000000000000UL)] // NaN + [InlineData(0x7E00000000000000UL, 0x0000000000001234UL, 0x7C00000000000000UL, 0x0000000000001234UL)] // signaling NaN canonicalized + [InlineData(0x7C003FFFFFFFFFFFUL, 0xFFFFFFFFFFFFFFFFUL, 0x7C00000000000000UL, 0x0000000000000000UL)] // out-of-range NaN payload canonicalized + public static void BitIncrementTest(ulong upper, ulong lower, ulong expectedUpper, ulong expectedLower) + { + Decimal128 result = Decimal128.BitIncrement(Unsafe.BitCast(new UInt128(upper, lower))); + Assert.Equal(new UInt128(expectedUpper, expectedLower), Unsafe.BitCast(result)); + } + + [Theory] + [InlineData(0x3040000000000000UL, 0x0000000000000000UL, 0x8000000000000000UL, 0x0000000000000001UL)] // +0 -> -MINFP + [InlineData(0xB040000000000000UL, 0x0000000000000000UL, 0x8000000000000000UL, 0x0000000000000001UL)] // -0 -> -MINFP + [InlineData(0x3040000000000000UL, 0x0000000000000001UL, 0x2FFDED09BEAD87C0UL, 0x378D8E63FFFFFFFFUL)] // 1 -> 0.999...9 + [InlineData(0xB040000000000000UL, 0x0000000000000001UL, 0xAFFE314DC6448D93UL, 0x38C15B0A00000001UL)] // -1 -> -1.000...001 + [InlineData(0x5FFFED09BEAD87C0UL, 0x378D8E63FFFFFFFFUL, 0x5FFFED09BEAD87C0UL, 0x378D8E63FFFFFFFEUL)] // +MAXFP steps toward zero + [InlineData(0xDFFFED09BEAD87C0UL, 0x378D8E63FFFFFFFFUL, 0xF800000000000000UL, 0x0000000000000000UL)] // -MAXFP -> -Infinity + [InlineData(0x0000000000000000UL, 0x0000000000000001UL, 0x0000000000000000UL, 0x0000000000000000UL)] // +MINFP -> +0 + [InlineData(0x8000000000000000UL, 0x0000000000000001UL, 0x8000000000000000UL, 0x0000000000000002UL)] // -MINFP + [InlineData(0x2FFFED09BEAD87C0UL, 0x378D8E63FFFFFFFFUL, 0x2FFFED09BEAD87C0UL, 0x378D8E63FFFFFFFEUL)] // normal step + [InlineData(0x7800000000000000UL, 0x0000000000000000UL, 0x5FFFED09BEAD87C0UL, 0x378D8E63FFFFFFFFUL)] // +Infinity -> +MAXFP + [InlineData(0xF800000000000000UL, 0x0000000000000000UL, 0xF800000000000000UL, 0x0000000000000000UL)] // -Infinity + [InlineData(0xFC00000000000000UL, 0x0000000000000000UL, 0xFC00000000000000UL, 0x0000000000000000UL)] // NaN + [InlineData(0x7E00000000000000UL, 0x0000000000001234UL, 0x7C00000000000000UL, 0x0000000000001234UL)] // signaling NaN canonicalized + [InlineData(0x7C003FFFFFFFFFFFUL, 0xFFFFFFFFFFFFFFFFUL, 0x7C00000000000000UL, 0x0000000000000000UL)] // out-of-range NaN payload canonicalized + public static void BitDecrementTest(ulong upper, ulong lower, ulong expectedUpper, ulong expectedLower) + { + Decimal128 result = Decimal128.BitDecrement(Unsafe.BitCast(new UInt128(upper, lower))); + Assert.Equal(new UInt128(expectedUpper, expectedLower), Unsafe.BitCast(result)); + } + + [Theory] + [InlineData(0x3040000000000000UL, 0x0000000000000001UL, 0)] // 1 + [InlineData(0x304A000000000000UL, 0x0000000000000001UL, 5)] // 1e5 + [InlineData(0x3040000000000000UL, 0x000000000012D687UL, 6)] // 1234567 + [InlineData(0x3036000000000000UL, 0x0000000000000389UL, -3)] // 9.05e-3 + [InlineData(0x3040000000000000UL, 0x000000000000002AUL, 1)] // 42 + [InlineData(0x2F82000000000000UL, 0x0000000000000001UL, -95)] // 1e-95 + [InlineData(0x304C000000000000UL, 0x0000000000000001UL, 6)] // 1e6 + [InlineData(0x3040000000000000UL, 0x0000000000000000UL, int.MinValue)] // +0 + [InlineData(0xB040000000000000UL, 0x0000000000000000UL, int.MinValue)] // -0 + [InlineData(0xFC00000000000000UL, 0x0000000000000000UL, int.MaxValue)] // NaN + [InlineData(0x7800000000000000UL, 0x0000000000000000UL, int.MaxValue)] // +Infinity + [InlineData(0xF800000000000000UL, 0x0000000000000000UL, int.MaxValue)] // -Infinity + public static void ILogBTest(ulong upper, ulong lower, int expected) + { + Assert.Equal(expected, Decimal128.ILogB(Unsafe.BitCast(new UInt128(upper, lower)))); + } + + [Theory] + [InlineData(0x3040000000000000UL, 0x000000000000007BUL, 0, 0x3040000000000000UL, 0x000000000000007BUL)] // 123 scaleB 0 + [InlineData(0x3040000000000000UL, 0x000000000000007BUL, 2, 0x3044000000000000UL, 0x000000000000007BUL)] // 123 scaleB 2 + [InlineData(0x3040000000000000UL, 0x000000000000007BUL, -2, 0x303C000000000000UL, 0x000000000000007BUL)] // 123 scaleB -2 + [InlineData(0x3040000000000000UL, 0x0000000000000001UL, 34, 0x3084000000000000UL, 0x0000000000000001UL)] // absorb into coefficient + [InlineData(0x3040000000000000UL, 0x0000000000000009UL, 6111, 0x5FFE000000000000UL, 0x0000000000000009UL)] // absorb at max quantum + [InlineData(0x3040000000000000UL, 0x0000000000000001UL, 6154, 0x7800000000000000UL, 0x0000000000000000UL)] // overflow -> +Infinity + [InlineData(0xB040000000000000UL, 0x0000000000000001UL, 6154, 0xF800000000000000UL, 0x0000000000000000UL)] // overflow -> -Infinity + [InlineData(0x0042000000000000UL, 0x0000000000000001UL, -50, 0x0000000000000000UL, 0x0000000000000000UL)] // deep underflow -> +0 + [InlineData(0x0000000000000000UL, 0x000000000000000FUL, -1, 0x0000000000000000UL, 0x0000000000000002UL)] // gradual underflow, tie -> even (up) + [InlineData(0x0000000000000000UL, 0x0000000000000019UL, -1, 0x0000000000000000UL, 0x0000000000000002UL)] // gradual underflow, tie -> even (stay) + [InlineData(0x0000000000000000UL, 0x000000000000000EUL, -1, 0x0000000000000000UL, 0x0000000000000001UL)] // gradual underflow, rounds down + [InlineData(0x3040000000000000UL, 0x0000000000000000UL, 5, 0x304A000000000000UL, 0x0000000000000000UL)] // +0 + [InlineData(0xB040000000000000UL, 0x0000000000000000UL, 5, 0xB04A000000000000UL, 0x0000000000000000UL)] // -0 + [InlineData(0xFC00000000000000UL, 0x0000000000000000UL, 5, 0xFC00000000000000UL, 0x0000000000000000UL)] // NaN + [InlineData(0x7800000000000000UL, 0x0000000000000000UL, 5, 0x7800000000000000UL, 0x0000000000000000UL)] // +Infinity + [InlineData(0xF800000000000000UL, 0x0000000000000000UL, 5, 0xF800000000000000UL, 0x0000000000000000UL)] // -Infinity + [InlineData(0x7E00000000000000UL, 0x0000000000001234UL, 5, 0x7C00000000000000UL, 0x0000000000001234UL)] // signaling NaN canonicalized + [InlineData(0x7C003FFFFFFFFFFFUL, 0xFFFFFFFFFFFFFFFFUL, 5, 0x7C00000000000000UL, 0x0000000000000000UL)] // out-of-range NaN payload canonicalized + public static void ScaleBTest(ulong upper, ulong lower, int n, ulong expectedUpper, ulong expectedLower) + { + Decimal128 result = Decimal128.ScaleB(Unsafe.BitCast(new UInt128(upper, lower)), n); + Assert.Equal(new UInt128(expectedUpper, expectedLower), Unsafe.BitCast(result)); + } + + [Theory] + [InlineData(0x3040000000000000UL, 0x0000000000000003UL, 0x3040000000000000UL, 0x0000000000000002UL, 0xB040000000000000UL, 0x0000000000000001UL)] // 3 rem 2 = -1 (quotient rounds up to even 2) + [InlineData(0x3040000000000000UL, 0x0000000000000005UL, 0x3040000000000000UL, 0x0000000000000002UL, 0x3040000000000000UL, 0x0000000000000001UL)] // 5 rem 2 = 1 (quotient 2, exact half stays) + [InlineData(0x3040000000000000UL, 0x0000000000000007UL, 0x3040000000000000UL, 0x0000000000000002UL, 0xB040000000000000UL, 0x0000000000000001UL)] // 7 rem 2 = -1 (quotient rounds up to even 4) + [InlineData(0x3040000000000000UL, 0x000000000000000BUL, 0x3040000000000000UL, 0x0000000000000004UL, 0xB040000000000000UL, 0x0000000000000001UL)] // 11 rem 4 = -1 (nearest multiple 12) + [InlineData(0xB040000000000000UL, 0x000000000000000BUL, 0x3040000000000000UL, 0x0000000000000004UL, 0x3040000000000000UL, 0x0000000000000001UL)] // -11 rem 4 = 1 (sign flips) + [InlineData(0x3040000000000000UL, 0x000000000000000AUL, 0x3040000000000000UL, 0x0000000000000003UL, 0x3040000000000000UL, 0x0000000000000001UL)] // 10 rem 3 = 1 + [InlineData(0x3040000000000000UL, 0x0000000000000009UL, 0x3040000000000000UL, 0x0000000000000003UL, 0x3040000000000000UL, 0x0000000000000000UL)] // 9 rem 3 = +0 (exact multiple) + [InlineData(0xB040000000000000UL, 0x0000000000000009UL, 0x3040000000000000UL, 0x0000000000000003UL, 0xB040000000000000UL, 0x0000000000000000UL)] // -9 rem 3 = -0 + [InlineData(0x3040000000000000UL, 0x000000000000002AUL, 0x7800000000000000UL, 0x0000000000000000UL, 0x3040000000000000UL, 0x000000000000002AUL)] // finite rem +Infinity = finite + [InlineData(0xB040000000000000UL, 0x000000000000002AUL, 0xF800000000000000UL, 0x0000000000000000UL, 0xB040000000000000UL, 0x000000000000002AUL)] // -finite rem -Infinity = -finite + [InlineData(0x3040000000000000UL, 0x0000000000000005UL, 0x3040000000000000UL, 0x0000000000000000UL, 0x7C00000000000000UL, 0x0000000000000000UL)] // x rem 0 = NaN + [InlineData(0x7800000000000000UL, 0x0000000000000000UL, 0x3040000000000000UL, 0x0000000000000005UL, 0x7C00000000000000UL, 0x0000000000000000UL)] // +Infinity rem finite = NaN + [InlineData(0xFC00000000000000UL, 0x0000000000000000UL, 0x3040000000000000UL, 0x0000000000000005UL, 0xFC00000000000000UL, 0x0000000000000000UL)] // NaN rem finite = NaN + [InlineData(0x3040000000000000UL, 0x0000000000000005UL, 0xFC00000000000000UL, 0x0000000000000000UL, 0xFC00000000000000UL, 0x0000000000000000UL)] // finite rem NaN = NaN + [InlineData(0x7E00000000000000UL, 0x0000000000001234UL, 0x3040000000000000UL, 0x0000000000000005UL, 0x7C00000000000000UL, 0x0000000000001234UL)] // signaling NaN operand quieted + [InlineData(0x3040000000000000UL, 0x0000000000000005UL, 0x7C003FFFFFFFFFFFUL, 0xFFFFFFFFFFFFFFFFUL, 0x7C00000000000000UL, 0x0000000000000000UL)] // out-of-range NaN payload cleared + [InlineData(0x9DD8000000000000UL, 0x00005AE240791CB6UL, 0xCDE6000000000F49UL, 0x1F2A73FEBB2E87E4UL, 0x9DD8000000000000UL, 0x00005AE240791CB6UL)] + [InlineData(0x3492000000000000UL, 0x00000000000288ECUL, 0xCDC0000000000000UL, 0x0002B570149A7EA3UL, 0x3492000000000000UL, 0x00000000000288ECUL)] + [InlineData(0xB91C00000E10E981UL, 0x07C99D05E9BA12D6UL, 0xA174000000000000UL, 0x00000000000047C2UL, 0xA174000000000000UL, 0x0000000000001A54UL)] + [InlineData(0x1D2E000000000000UL, 0x0000000000000000UL, 0x127C000000000000UL, 0x7BE69A35492A0CB3UL, 0x127C000000000000UL, 0x0000000000000000UL)] + [InlineData(0x226A000000000000UL, 0x0000000000000025UL, 0x9542000000000000UL, 0x00000000000001EDUL, 0x9542000000000000UL, 0x00000000000000F3UL)] + [InlineData(0xFC00000000000000UL, 0x0000000000000000UL, 0xCA42000000000000UL, 0x0000000000000000UL, 0xFC00000000000000UL, 0x0000000000000000UL)] + [InlineData(0xD9F2000000002C69UL, 0xCB28F65CC738478CUL, 0xA6082E9DA99387A2UL, 0x32F5E22081376C85UL, 0xA608151AF546EF83UL, 0xD0E20672239D5124UL)] + [InlineData(0x428C000000000000UL, 0x0000000000002582UL, 0xBC3C0208AE9D60D1UL, 0x5F293AAFE0F4E2C3UL, 0xBC3C00CAFAA4688BUL, 0xC0AF20E24D17BCE7UL)] + [InlineData(0x2A2600000000002DUL, 0x59B9AA35DE0A76F8UL, 0xB69A000000FE255DUL, 0x9813B929D4CAB39EUL, 0x2A2600000000002DUL, 0x59B9AA35DE0A76F8UL)] + [InlineData(0xB19C000000000000UL, 0x0000000000000000UL, 0x3A72000000000000UL, 0x00000002354B2F73UL, 0xB19C000000000000UL, 0x0000000000000000UL)] + [InlineData(0x28920004CFEB720EUL, 0x24AE01A62DBA18B3UL, 0xF800000000000000UL, 0x0000000000000000UL, 0x28920004CFEB720EUL, 0x24AE01A62DBA18B3UL)] + [InlineData(0xB94400000DDF9083UL, 0xB3C1E0CA40E97365UL, 0x7800000000000000UL, 0x0000000000000000UL, 0xB94400000DDF9083UL, 0xB3C1E0CA40E97365UL)] + [InlineData(0xFC00000000000000UL, 0x0000000000000000UL, 0x3970000000000000UL, 0x000000000000252BUL, 0xFC00000000000000UL, 0x0000000000000000UL)] + [InlineData(0x9340000000065682UL, 0xC7C831F6DEC2DC76UL, 0x113E000000000000UL, 0x00000015259A9347UL, 0x113E000000000000UL, 0x00000007E60B3298UL)] + [InlineData(0x3D20000000000004UL, 0xF399C4B35E6951FEUL, 0xA65E000000000000UL, 0x00000453EA66AE8FUL, 0x265E000000000000UL, 0x0000017C2BDE3FA2UL)] + [InlineData(0x356A000000000000UL, 0x0000000000000000UL, 0x0C90000000000000UL, 0x602EED97BB4D4597UL, 0x0C90000000000000UL, 0x0000000000000000UL)] + [InlineData(0xD71A000000000000UL, 0x000000721CC91608UL, 0x016AA9EFD4B0AC76UL, 0x62E4F61692CE4106UL, 0x016A44B7D3E5343BUL, 0x5794CD94FD5D69EAUL)] + [InlineData(0xAF1400001734A1A0UL, 0x9F0C3C44080487E5UL, 0x1F8E000000000000UL, 0x09F6E0D6E8C1F02CUL, 0x9F8E000000000000UL, 0x0410EDE0BD429090UL)] + [InlineData(0x1E9E00012996900AUL, 0x2DD4810C4F3A7B0DUL, 0x5A92000000000000UL, 0x0000005E4F90A79AUL, 0x1E9E00012996900AUL, 0x2DD4810C4F3A7B0DUL)] + [InlineData(0x49E0000000000000UL, 0x00002A9775133A63UL, 0xBFBA000000000117UL, 0x5FA6AE4B2078E0E9UL, 0x3FBA00000000006AUL, 0xCF0A3BF721C3D55EUL)] + [InlineData(0x9C6E000000000000UL, 0x0000000002F8A9ACUL, 0x80CE000000000000UL, 0x00000000000002DBUL, 0x80CE000000000000UL, 0x000000000000010CUL)] + [InlineData(0x13AE000000000000UL, 0x0000000000000000UL, 0x80E0000000000000UL, 0x0000000000000000UL, 0x7C00000000000000UL, 0x0000000000000000UL)] + [InlineData(0x25AA000000000000UL, 0x0000000000000000UL, 0xF800000000000000UL, 0x0000000000000000UL, 0x25AA000000000000UL, 0x0000000000000000UL)] + [InlineData(0x3A32031DC5CD65DCUL, 0x0C477B601BC9BDCDUL, 0xC350000000000000UL, 0x00000245D961F6B4UL, 0x3A32031DC5CD65DCUL, 0x0C477B601BC9BDCDUL)] + [InlineData(0x05D6000000000000UL, 0x0000000000000006UL, 0x33E4000000000000UL, 0x0000000000000077UL, 0x05D6000000000000UL, 0x0000000000000006UL)] + [InlineData(0x55C40000028DC19EUL, 0xCDA4CD2EA355D0E8UL, 0x0A40000000000000UL, 0x0000000000000000UL, 0x7C00000000000000UL, 0x0000000000000000UL)] + [InlineData(0x57C600000002BD63UL, 0xF456BE60FE8E2C69UL, 0x4892000000000000UL, 0x00000001279255F0UL, 0xC892000000000000UL, 0x000000008F041E80UL)] + [InlineData(0xF800000000000000UL, 0x0000000000000000UL, 0x9046000000000000UL, 0x0000000000000000UL, 0x7C00000000000000UL, 0x0000000000000000UL)] + [InlineData(0x1ABE0000027A1BA1UL, 0x211F3C3E661AC81EUL, 0xD17C000000000001UL, 0xCBDCD5265DC9C066UL, 0x1ABE0000027A1BA1UL, 0x211F3C3E661AC81EUL)] + [InlineData(0xCFEC000000074D73UL, 0x87D4951999998747UL, 0x0ABA000000000984UL, 0xE2BC16EEB77D738CUL, 0x0ABA0000000000D6UL, 0x4C32F67CB2AD5CF4UL)] + [InlineData(0xB0A4000000000000UL, 0x0073185BC8829F34UL, 0x0CE60002C9DC01CAUL, 0xCC8CE2871759CB5BUL, 0x8CE60001426B9523UL, 0x95C4B6BEE860F29BUL)] + [InlineData(0x9312000000000000UL, 0x0002C127DF400535UL, 0xD02A000000000000UL, 0x0000000000000001UL, 0x9312000000000000UL, 0x0002C127DF400535UL)] + [InlineData(0xFC00000000000000UL, 0x0000000000000000UL, 0xA970000000000000UL, 0x000000002C31CA2BUL, 0xFC00000000000000UL, 0x0000000000000000UL)] + [InlineData(0x4FFA000000000000UL, 0x000000000000166CUL, 0x16A4000000000000UL, 0x00000000362B69E9UL, 0x96A4000000000000UL, 0x000000001251435EUL)] + [InlineData(0xAE9A000000000000UL, 0x0000000000000000UL, 0x10FA000000000000UL, 0x000000000D42601AUL, 0x90FA000000000000UL, 0x0000000000000000UL)] + [InlineData(0x1BBA000000000000UL, 0x0000000000000386UL, 0x2E5A04231F197D99UL, 0x6F56D55DDDA3AD76UL, 0x1BBA000000000000UL, 0x0000000000000386UL)] + public static void Ieee754RemainderTest(ulong leftUpper, ulong leftLower, ulong rightUpper, ulong rightLower, ulong expectedUpper, ulong expectedLower) + { + Decimal128 result = Decimal128.Ieee754Remainder(Unsafe.BitCast(new UInt128(leftUpper, leftLower)), Unsafe.BitCast(new UInt128(rightUpper, rightLower))); + Assert.Equal(new UInt128(expectedUpper, expectedLower), Unsafe.BitCast(result)); + } + + [Theory] + [InlineData(0x3040000000000000UL, 0x0000000000000004UL, 0x3040000000000000UL, 0x0000000000000002UL)] // sqrt(4) = 2 + [InlineData(0x3040000000000000UL, 0x0000000000000009UL, 0x3040000000000000UL, 0x0000000000000003UL)] // sqrt(9) = 3 + [InlineData(0x3040000000000000UL, 0x0000000000000064UL, 0x3040000000000000UL, 0x000000000000000AUL)] // sqrt(100) = 10 + [InlineData(0x3044000000000000UL, 0x0000000000000004UL, 0x3042000000000000UL, 0x0000000000000002UL)] // sqrt(4E2) = 2E1 (even exponent halves) + [InlineData(0x3038000000000000UL, 0x0000000000000009UL, 0x303C000000000000UL, 0x0000000000000003UL)] // sqrt(9E-4) = 3E-2 + [InlineData(0x3040000000000000UL, 0x0000000000000001UL, 0x3040000000000000UL, 0x0000000000000001UL)] // sqrt(1) = 1 + [InlineData(0x3040000000000000UL, 0x0000000000000002UL, 0x2FFE45B9E278CDF8UL, 0xB43E0F0F10148022UL)] // sqrt(2) inexact, rounded to 34 digits + [InlineData(0x3040000000000000UL, 0x0000000000000000UL, 0x3040000000000000UL, 0x0000000000000000UL)] // sqrt(+0) = +0 + [InlineData(0xB040000000000000UL, 0x0000000000000000UL, 0xB040000000000000UL, 0x0000000000000000UL)] // sqrt(-0) = -0 (sign preserved) + [InlineData(0x304A000000000000UL, 0x0000000000000000UL, 0x3044000000000000UL, 0x0000000000000000UL)] // sqrt(0E5) = 0E2 (preferred exponent floor(5/2)) + [InlineData(0xB040000000000000UL, 0x0000000000000004UL, 0x7C00000000000000UL, 0x0000000000000000UL)] // sqrt(-4) = NaN (invalid) + [InlineData(0x7800000000000000UL, 0x0000000000000000UL, 0x7800000000000000UL, 0x0000000000000000UL)] // sqrt(+Infinity) = +Infinity + [InlineData(0xF800000000000000UL, 0x0000000000000000UL, 0x7C00000000000000UL, 0x0000000000000000UL)] // sqrt(-Infinity) = NaN (invalid) + [InlineData(0xFC00000000000000UL, 0x0000000000000000UL, 0xFC00000000000000UL, 0x0000000000000000UL)] // sqrt(NaN) = NaN + [InlineData(0xFC00000000000000UL, 0x0000000000001234UL, 0xFC00000000000000UL, 0x0000000000001234UL)] // NaN payload preserved + [InlineData(0xFC00400000000000UL, 0x0000000000000000UL, 0xFC00000000000000UL, 0x0000000000000000UL)] // out-of-range NaN payload cleared + public static void SqrtTest(ulong valueUpper, ulong valueLower, ulong expectedUpper, ulong expectedLower) + { + Decimal128 result = Decimal128.Sqrt(Unsafe.BitCast(new UInt128(valueUpper, valueLower))); + Assert.Equal(new UInt128(expectedUpper, expectedLower), Unsafe.BitCast(result)); + } + + [Theory] + [InlineData(0x3040000000000000UL, 0x0000000000000001UL, 0x303C000000000000UL, 0x0000000000000001UL, 0x303C000000000000UL, 0x0000000000000064UL)] // quantize(1, 1E-2) = 1.00 (exact scale up) + [InlineData(0x303E000000000000UL, 0x0000000000000019UL, 0x3040000000000000UL, 0x0000000000000001UL, 0x3040000000000000UL, 0x0000000000000002UL)] // quantize(2.5, 1E0) = 2 (ties to even) + [InlineData(0x303E000000000000UL, 0x0000000000000023UL, 0x3040000000000000UL, 0x0000000000000001UL, 0x3040000000000000UL, 0x0000000000000004UL)] // quantize(3.5, 1E0) = 4 (ties to even) + [InlineData(0x303A000000000000UL, 0x00000000000004D2UL, 0x303C000000000000UL, 0x0000000000000001UL, 0x303C000000000000UL, 0x000000000000007BUL)] // quantize(1.234, 1E-2) = 1.23 + [InlineData(0x3040000000000000UL, 0x000000000012D687UL, 0x303E000000000000UL, 0x0000000000000001UL, 0x303E000000000000UL, 0x0000000000BC6146UL)] // quantize(1234567, 1E-1) = 1234567.0 + [InlineData(0xB04A000000000000UL, 0x0000000000000000UL, 0x303C000000000000UL, 0x0000000000000001UL, 0xB03C000000000000UL, 0x0000000000000000UL)] // quantize(-0E5, 1E-2) = -0E-2 (target quantum) + [InlineData(0x3040000000000000UL, 0x0000000000000004UL, 0x3044000000000000UL, 0x0000000000000001UL, 0x3044000000000000UL, 0x0000000000000000UL)] // quantize(4, 1E2) = 0E2 (rounds to zero) + [InlineData(0x3040000000000000UL, 0x000000000000003CUL, 0x3044000000000000UL, 0x0000000000000001UL, 0x3044000000000000UL, 0x0000000000000001UL)] // quantize(60, 1E2) = 1E2 (rounds up) + [InlineData(0x7800000000000000UL, 0x0000000000000000UL, 0x7800000000000000UL, 0x0000000000000000UL, 0x7800000000000000UL, 0x0000000000000000UL)] // quantize(+Inf, +Inf) = +Inf + [InlineData(0xF800000000000000UL, 0x0000000000000000UL, 0x7800000000000000UL, 0x0000000000000000UL, 0xF800000000000000UL, 0x0000000000000000UL)] // quantize(-Inf, +Inf) = -Inf (sign of x) + [InlineData(0x7800000000000000UL, 0x0000000000000000UL, 0x3040000000000000UL, 0x0000000000000001UL, 0x7C00000000000000UL, 0x0000000000000000UL)] // quantize(+Inf, finite) = NaN + [InlineData(0x3040000000000000UL, 0x0000000000000001UL, 0x7800000000000000UL, 0x0000000000000000UL, 0x7C00000000000000UL, 0x0000000000000000UL)] // quantize(finite, +Inf) = NaN + [InlineData(0x7C00000000000000UL, 0x0000000000001234UL, 0x3040000000000000UL, 0x0000000000000001UL, 0x7C00000000000000UL, 0x0000000000001234UL)] // quantize(qNaN, finite) = qNaN (payload preserved) + [InlineData(0x3040000000000000UL, 0x0000000000000001UL, 0x7C00000000000000UL, 0x0000000000002222UL, 0x7C00000000000000UL, 0x0000000000002222UL)] // quantize(finite, qNaN) = qNaN (payload preserved) + public static void QuantizeTest(ulong valueUpper, ulong valueLower, ulong quantumUpper, ulong quantumLower, ulong expectedUpper, ulong expectedLower) + { + Decimal128 result = Decimal128.Quantize(Unsafe.BitCast(new UInt128(valueUpper, valueLower)), Unsafe.BitCast(new UInt128(quantumUpper, quantumLower))); + Assert.Equal(new UInt128(expectedUpper, expectedLower), Unsafe.BitCast(result)); + } + + [Theory] + [InlineData(0x303C000000000000UL, 0x0000000000003039UL, 0x303C000000000000UL, 0x0000000000000001UL)] // quantum(123.45) = 1E-2 + [InlineData(0xB040000000000000UL, 0x0000000000000007UL, 0x3040000000000000UL, 0x0000000000000001UL)] // quantum(-7) = 1E0 (always positive) + [InlineData(0x304A000000000000UL, 0x0000000000000000UL, 0x304A000000000000UL, 0x0000000000000001UL)] // quantum(0E5) = 1E5 + [InlineData(0x7800000000000000UL, 0x0000000000000000UL, 0x7800000000000000UL, 0x0000000000000000UL)] // quantum(+Inf) = +Inf + [InlineData(0xF800000000000000UL, 0x0000000000000000UL, 0x7800000000000000UL, 0x0000000000000000UL)] // quantum(-Inf) = +Inf (sign cleared) + [InlineData(0x7C00000000000000UL, 0x0000000000001234UL, 0x7C00000000000000UL, 0x0000000000001234UL)] // quantum(qNaN) = qNaN (payload preserved) + [InlineData(0xFC00000000000000UL, 0x0000000000000000UL, 0xFC00000000000000UL, 0x0000000000000000UL)] // quantum(-NaN) = -NaN (propagated) + public static void QuantumTest(ulong valueUpper, ulong valueLower, ulong expectedUpper, ulong expectedLower) + { + Decimal128 result = Decimal128.Quantum(Unsafe.BitCast(new UInt128(valueUpper, valueLower))); + Assert.Equal(new UInt128(expectedUpper, expectedLower), Unsafe.BitCast(result)); + } + + [Theory] + [InlineData(0x3040000000000000UL, 0x0000000000000001UL, 0x3040000000000000UL, 0x00000000000003E7UL, true)] // same exponent + [InlineData(0x3040000000000000UL, 0x0000000000000001UL, 0x303E000000000000UL, 0x0000000000000001UL, false)] // different exponent + [InlineData(0x7C00000000000000UL, 0x0000000000000000UL, 0x7C00000000000000UL, 0x0000000000000000UL, true)] // both NaN + [InlineData(0x7C00000000000000UL, 0x0000000000000000UL, 0x3040000000000000UL, 0x0000000000000001UL, false)] // NaN vs finite + [InlineData(0x7800000000000000UL, 0x0000000000000000UL, 0xF800000000000000UL, 0x0000000000000000UL, true)] // both Infinity + [InlineData(0x7800000000000000UL, 0x0000000000000000UL, 0x7C00000000000000UL, 0x0000000000000000UL, false)] // Infinity vs NaN + [InlineData(0x7800000000000000UL, 0x0000000000000000UL, 0x3040000000000000UL, 0x0000000000000001UL, false)] // Infinity vs finite + public static void SameQuantumTest(ulong xUpper, ulong xLower, ulong yUpper, ulong yLower, bool expected) + { + Decimal128 x = Unsafe.BitCast(new UInt128(xUpper, xLower)); + Decimal128 y = Unsafe.BitCast(new UInt128(yUpper, yLower)); + Assert.Equal(expected, Decimal128.SameQuantum(x, y)); + } + + + [Theory] + [InlineData(0x3040000000000000UL, 0x0000000000000002UL, 0x3040000000000000UL, 0x0000000000000003UL, 0x3040000000000000UL, 0x0000000000000004UL, 0x3040000000000000UL, 0x000000000000000AUL)] // 2 * 3 + 4 = 10 + [InlineData(0x3034000000000000UL, 0x00000000000F4241UL, 0x3034000000000000UL, 0x00000000000F4241UL, 0xB034000000000000UL, 0x00000000000F4242UL, 0x3028000000000000UL, 0x0000000000000001UL)] // 1.000001 * 1.000001 - 1.000002 = 1E-12 (fused) + [InlineData(0x304A000000000000UL, 0x0000000000000000UL, 0x3044000000000000UL, 0x0000000000000003UL, 0x303A000000000000UL, 0x0000000000000007UL, 0x303A000000000000UL, 0x0000000000000007UL)] // 0E5 * 3E2 + 7E-3 + [InlineData(0x3040000000000000UL, 0x0000000000000003UL, 0x3040000000000000UL, 0x0000000000000004UL, 0x3040000000000000UL, 0x0000000000000000UL, 0x3040000000000000UL, 0x000000000000000CUL)] // 3 * 4 + 0 = 12 + [InlineData(0x3040000000000000UL, 0x0000000000000002UL, 0x3040000000000000UL, 0x0000000000000003UL, 0xB040000000000000UL, 0x0000000000000006UL, 0x3040000000000000UL, 0x0000000000000000UL)] // 2 * 3 + (-6) = +0 + [InlineData(0xB040000000000000UL, 0x0000000000000002UL, 0x3040000000000000UL, 0x0000000000000003UL, 0x3040000000000000UL, 0x0000000000000006UL, 0x3040000000000000UL, 0x0000000000000000UL)] // -2 * 3 + 6 = +0 + [InlineData(0x3040000000000000UL, 0x0000000000000003UL, 0x7C00000000000000UL, 0x0000000000001234UL, 0x3040000000000000UL, 0x0000000000000004UL, 0x7C00000000000000UL, 0x0000000000001234UL)] // 3 * qNaN(0x1234) + 4 -> qNaN + [InlineData(0x7C00000000000000UL, 0x0000000000000011UL, 0x3040000000000000UL, 0x0000000000000002UL, 0x7C00000000000000UL, 0x0000000000000022UL, 0x7C00000000000000UL, 0x0000000000000022UL)] // qNaN(x) * 2 + qNaN(z) -> z payload + [InlineData(0x3040000000000000UL, 0x0000000000000002UL, 0x7800000000000000UL, 0x0000000000000000UL, 0x3040000000000000UL, 0x0000000000000003UL, 0x7800000000000000UL, 0x0000000000000000UL)] // 2 * +Inf + 3 = +Inf + [InlineData(0xB040000000000000UL, 0x0000000000000002UL, 0x7800000000000000UL, 0x0000000000000000UL, 0x3040000000000000UL, 0x0000000000000003UL, 0xF800000000000000UL, 0x0000000000000000UL)] // -2 * +Inf + 3 = -Inf + [InlineData(0x3040000000000000UL, 0x0000000000000000UL, 0x7800000000000000UL, 0x0000000000000000UL, 0x3040000000000000UL, 0x0000000000000005UL, 0x7C00000000000000UL, 0x0000000000000000UL)] // 0 * +Inf + 5 -> qNaN + [InlineData(0x3040000000000000UL, 0x0000000000000002UL, 0x7800000000000000UL, 0x0000000000000000UL, 0xF800000000000000UL, 0x0000000000000000UL, 0x7C00000000000000UL, 0x0000000000000000UL)] // 2 * +Inf + (-Inf) -> qNaN + public static void FusedMultiplyAddTest(ulong xUpper, ulong xLower, ulong yUpper, ulong yLower, ulong zUpper, ulong zLower, ulong expectedUpper, ulong expectedLower) + { + Decimal128 x = Unsafe.BitCast(new UInt128(xUpper, xLower)); + Decimal128 y = Unsafe.BitCast(new UInt128(yUpper, yLower)); + Decimal128 z = Unsafe.BitCast(new UInt128(zUpper, zLower)); + Assert.Equal(new UInt128(expectedUpper, expectedLower), Unsafe.BitCast(Decimal128.FusedMultiplyAdd(x, y, z))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal128BitDecrement), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void BitDecrement_IntelReferenceVectors(UInt128 value, UInt128 expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal128.BitDecrement(Unsafe.BitCast(value)))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal128BitIncrement), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void BitIncrement_IntelReferenceVectors(UInt128 value, UInt128 expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal128.BitIncrement(Unsafe.BitCast(value)))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal128ILogB), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void ILogB_IntelReferenceVectors(UInt128 value, int expected) + { + Assert.Equal(expected, Decimal128.ILogB(Unsafe.BitCast(value))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal128ScaleB), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void ScaleB_IntelReferenceVectors(UInt128 value, int n, UInt128 expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal128.ScaleB(Unsafe.BitCast(value), n))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal128FusedMultiplyAdd), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void FusedMultiplyAdd_IntelReferenceVectors(UInt128 x, UInt128 y, UInt128 z, UInt128 expected) + { + Decimal128 result = Decimal128.FusedMultiplyAdd(Unsafe.BitCast(x), Unsafe.BitCast(y), Unsafe.BitCast(z)); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] [MemberData(nameof(DecimalIeee754IntelTestData.Decimal128Arithmetic), MemberType = typeof(DecimalIeee754IntelTestData))] public static void op_Arithmetic_IntelReferenceVectors(string operation, UInt128 left, UInt128 right, UInt128 expected) @@ -1559,6 +1834,38 @@ public static void op_Modulus_IntelReferenceVectors(UInt128 left, UInt128 right, Assert.Equal(expected, Unsafe.BitCast(l % r)); } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal128Remainder), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void Ieee754Remainder_IntelReferenceVectors(UInt128 left, UInt128 right, UInt128 expected) + { + Decimal128 l = Unsafe.BitCast(left); + Decimal128 r = Unsafe.BitCast(right); + + Assert.Equal(expected, Unsafe.BitCast(Decimal128.Ieee754Remainder(l, r))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal128Sqrt), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void Sqrt_IntelReferenceVectors(UInt128 value, UInt128 expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal128.Sqrt(Unsafe.BitCast(value)))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal128Quantize), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void Quantize_IntelReferenceVectors(UInt128 value, UInt128 quantum, UInt128 expected) + { + Decimal128 result = Decimal128.Quantize(Unsafe.BitCast(value), Unsafe.BitCast(quantum)); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal128Quantum), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void Quantum_IntelReferenceVectors(UInt128 value, UInt128 expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal128.Quantum(Unsafe.BitCast(value)))); + } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] [MemberData(nameof(DecimalIeee754IntelTestData.Decimal128Comparison), MemberType = typeof(DecimalIeee754IntelTestData))] public static void op_Comparison_IntelReferenceVectors(string operation, UInt128 left, UInt128 right, bool expected) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs index 0598858a3ddf17..1d608a7293dfcd 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs @@ -1530,6 +1530,272 @@ public static void TryParsePartial_Invalid(string value, NumberStyles style, IFo Assert.Equal(0, bytesConsumed); } + [Theory] + [InlineData(0x32800000U, 0x00000001U)] // +0 -> +MINFP + [InlineData(0xB2800000U, 0x00000001U)] // -0 -> +MINFP + [InlineData(0x32800001U, 0x2F8F4241U)] // 1 -> 1.000001 + [InlineData(0xB2800001U, 0xEBD8967FU)] // -1 -> -0.9999999 + [InlineData(0x77F8967FU, 0x78000000U)] // +MAXFP -> +Infinity + [InlineData(0xF7F8967FU, 0xF7F8967EU)] // -MAXFP steps toward zero + [InlineData(0x00000001U, 0x00000002U)] // +MINFP + [InlineData(0x80000001U, 0x80000000U)] // -MINFP -> -0 + [InlineData(0x6BF8967FU, 0x300F4240U)] // coefficient carry + [InlineData(0x78000000U, 0x78000000U)] // +Infinity + [InlineData(0xF8000000U, 0xF7F8967FU)] // -Infinity -> -MAXFP + [InlineData(0xFC000000U, 0xFC000000U)] // NaN + [InlineData(0x7E001234U, 0x7C001234U)] // signaling NaN canonicalized + [InlineData(0x7C0FFFFFU, 0x7C000000U)] // out-of-range NaN payload canonicalized + public static void BitIncrementTest(uint bits, uint expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal32.BitIncrement(Unsafe.BitCast(bits)))); + } + + [Theory] + [InlineData(0x32800000U, 0x80000001U)] // +0 -> -MINFP + [InlineData(0xB2800000U, 0x80000001U)] // -0 -> -MINFP + [InlineData(0x32800001U, 0x6BD8967FU)] // 1 -> 0.9999999 + [InlineData(0xB2800001U, 0xAF8F4241U)] // -1 -> -1.000001 + [InlineData(0x77F8967FU, 0x77F8967EU)] // +MAXFP steps toward zero + [InlineData(0xF7F8967FU, 0xF8000000U)] // -MAXFP -> -Infinity + [InlineData(0x00000001U, 0x00000000U)] // +MINFP -> +0 + [InlineData(0x80000001U, 0x80000002U)] // -MINFP + [InlineData(0x6BF8967FU, 0x6BF8967EU)] // normal step + [InlineData(0x78000000U, 0x77F8967FU)] // +Infinity -> +MAXFP + [InlineData(0xF8000000U, 0xF8000000U)] // -Infinity + [InlineData(0xFC000000U, 0xFC000000U)] // NaN + [InlineData(0x7E001234U, 0x7C001234U)] // signaling NaN canonicalized + [InlineData(0x7C0FFFFFU, 0x7C000000U)] // out-of-range NaN payload canonicalized + public static void BitDecrementTest(uint bits, uint expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal32.BitDecrement(Unsafe.BitCast(bits)))); + } + + [Theory] + [InlineData(0x32800001U, 0)] // 1 + [InlineData(0x35000001U, 5)] // 1e5 + [InlineData(0x3292D687U, 6)] // 1234567 + [InlineData(0x30000389U, -3)] // 9.05e-3 + [InlineData(0x3280002AU, 1)] // 42 + [InlineData(0x03000001U, -95)] // 1e-95 + [InlineData(0x35800001U, 6)] // 1e6 + [InlineData(0x32800000U, int.MinValue)] // +0 + [InlineData(0xB2800000U, int.MinValue)] // -0 + [InlineData(0xFC000000U, int.MaxValue)] // NaN + [InlineData(0x78000000U, int.MaxValue)] // +Infinity + [InlineData(0xF8000000U, int.MaxValue)] // -Infinity + public static void ILogBTest(uint bits, int expected) + { + Assert.Equal(expected, Decimal32.ILogB(Unsafe.BitCast(bits))); + } + + [Theory] + [InlineData(0x3280007BU, 0, 0x3280007BU)] // 123 scaleB 0 + [InlineData(0x3280007BU, 2, 0x3380007BU)] // 123 scaleB 2 + [InlineData(0x3280007BU, -2, 0x3180007BU)] // 123 scaleB -2 + [InlineData(0x32800001U, 7, 0x36000001U)] // absorb into coefficient + [InlineData(0x32800009U, 90, 0x5F800009U)] // absorb at max quantum + [InlineData(0x32800001U, 106, 0x78000000U)] // overflow -> +Infinity + [InlineData(0xB2800001U, 106, 0xF8000000U)] // overflow -> -Infinity + [InlineData(0x00000000U, -50, 0x00000000U)] // deep underflow -> +0 + [InlineData(0x0000000FU, -1, 0x00000002U)] // gradual underflow, tie -> even (up) + [InlineData(0x00000019U, -1, 0x00000002U)] // gradual underflow, tie -> even (stay) + [InlineData(0x0000000EU, -1, 0x00000001U)] // gradual underflow, rounds down + [InlineData(0x32800000U, 5, 0x35000000U)] // +0 + [InlineData(0xB2800000U, 5, 0xB5000000U)] // -0 + [InlineData(0xFC000000U, 5, 0xFC000000U)] // NaN + [InlineData(0x78000000U, 5, 0x78000000U)] // +Infinity + [InlineData(0xF8000000U, 5, 0xF8000000U)] // -Infinity + [InlineData(0x7E001234U, 5, 0x7C001234U)] // signaling NaN canonicalized + [InlineData(0x7C0FFFFFU, 5, 0x7C000000U)] // out-of-range NaN payload canonicalized + public static void ScaleBTest(uint bits, int n, uint expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal32.ScaleB(Unsafe.BitCast(bits), n))); + } + + [Theory] + [InlineData(0x32800003U, 0x32800002U, 0xB2800001U)] // 3 rem 2 = -1 (quotient rounds up to even 2) + [InlineData(0x32800005U, 0x32800002U, 0x32800001U)] // 5 rem 2 = 1 (quotient 2, exact half stays) + [InlineData(0x32800007U, 0x32800002U, 0xB2800001U)] // 7 rem 2 = -1 (quotient rounds up to even 4) + [InlineData(0x3280000BU, 0x32800004U, 0xB2800001U)] // 11 rem 4 = -1 (nearest multiple 12) + [InlineData(0xB280000BU, 0x32800004U, 0x32800001U)] // -11 rem 4 = 1 (sign flips) + [InlineData(0x3280000AU, 0x32800003U, 0x32800001U)] // 10 rem 3 = 1 + [InlineData(0x32800009U, 0x32800003U, 0x32800000U)] // 9 rem 3 = +0 (exact multiple) + [InlineData(0xB2800009U, 0x32800003U, 0xB2800000U)] // -9 rem 3 = -0 + [InlineData(0x3280002AU, 0x78000000U, 0x3280002AU)] // finite rem +Infinity = finite + [InlineData(0xB280002AU, 0xF8000000U, 0xB280002AU)] // -finite rem -Infinity = -finite + [InlineData(0x32800005U, 0x32800000U, 0x7C000000U)] // x rem 0 = NaN + [InlineData(0x78000000U, 0x32800005U, 0x7C000000U)] // +Infinity rem finite = NaN + [InlineData(0xFC000000U, 0x32800005U, 0xFC000000U)] // NaN rem finite = NaN + [InlineData(0x32800005U, 0xFC000000U, 0xFC000000U)] // finite rem NaN = NaN + [InlineData(0x7E001234U, 0x32800005U, 0x7C001234U)] // signaling NaN operand quieted + [InlineData(0x32800005U, 0x7C0FFFFFU, 0x7C000000U)] // out-of-range NaN payload cleared + [InlineData(0x2A800000U, 0x78000000U, 0x2A800000U)] + [InlineData(0xF8000000U, 0x28800184U, 0x7C000000U)] + [InlineData(0x19804DB0U, 0x953FA8A2U, 0x1503D67EU)] + [InlineData(0x8C015A31U, 0x3F000001U, 0x8C015A31U)] + [InlineData(0xFC000000U, 0x18014D75U, 0xFC000000U)] + [InlineData(0x07000185U, 0xF8000000U, 0x07000185U)] + [InlineData(0xC2801E5DU, 0x9B801D8DU, 0x1B800A91U)] + [InlineData(0x28910D0DU, 0xFC000000U, 0xFC000000U)] + [InlineData(0x2F847E9CU, 0x95800000U, 0x7C000000U)] + [InlineData(0x8D000008U, 0xC7000286U, 0x8D000008U)] + [InlineData(0xB85EB066U, 0xFC000000U, 0xFC000000U)] + [InlineData(0xB6800330U, 0x2B800003U, 0xAB800000U)] + [InlineData(0x5307ABF5U, 0x2287FB3AU, 0xA2825F88U)] + [InlineData(0x5A800002U, 0x2D800004U, 0x2D800000U)] + [InlineData(0xFC000000U, 0xC200219DU, 0xFC000000U)] + [InlineData(0x8200772EU, 0x4F000000U, 0x7C000000U)] + [InlineData(0x36807027U, 0x90802417U, 0x108000FDU)] + [InlineData(0xA680AC17U, 0x14000000U, 0x7C000000U)] + [InlineData(0x3E800000U, 0x9380B36AU, 0x13800000U)] + [InlineData(0xFC000000U, 0x91811563U, 0xFC000000U)] + [InlineData(0xFC000000U, 0x5D6828A0U, 0xFC000000U)] + [InlineData(0xFC000000U, 0xBD800004U, 0xFC000000U)] + [InlineData(0x0E000000U, 0x9A00A259U, 0x0E000000U)] + [InlineData(0x2D800000U, 0xF8000000U, 0x2D800000U)] + [InlineData(0x98000248U, 0x59000000U, 0x7C000000U)] + [InlineData(0x4A000005U, 0xA5000000U, 0x7C000000U)] + [InlineData(0xC80114EEU, 0x88000000U, 0x7C000000U)] + [InlineData(0xBC00BDADU, 0x8E000427U, 0x8E000024U)] + [InlineData(0x3380395AU, 0x540129BFU, 0x3380395AU)] + [InlineData(0xFC000000U, 0x81000C50U, 0xFC000000U)] + [InlineData(0xDB0767EFU, 0x86817D0EU, 0x06808DFEU)] + [InlineData(0x9E000000U, 0x5B083365U, 0x9E000000U)] + [InlineData(0x3C000008U, 0x3D9DCC91U, 0x3C000008U)] + [InlineData(0x140005A9U, 0xB8800000U, 0x7C000000U)] + [InlineData(0x5D00003DU, 0x2E80001CU, 0xAE80000CU)] + [InlineData(0x04005659U, 0xC66EF60AU, 0x04005659U)] + public static void Ieee754RemainderTest(uint left, uint right, uint expected) + { + Decimal32 result = Decimal32.Ieee754Remainder(Unsafe.BitCast(left), Unsafe.BitCast(right)); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [Theory] + [InlineData(0x32800004U, 0x32800002U)] // sqrt(4) = 2 + [InlineData(0x32800009U, 0x32800003U)] // sqrt(9) = 3 + [InlineData(0x32800064U, 0x3280000AU)] // sqrt(100) = 10 + [InlineData(0x33800004U, 0x33000002U)] // sqrt(4E2) = 2E1 (even exponent halves) + [InlineData(0x30800009U, 0x31800003U)] // sqrt(9E-4) = 3E-2 + [InlineData(0x32800001U, 0x32800001U)] // sqrt(1) = 1 + [InlineData(0x32800002U, 0x2F959446U)] // sqrt(2) = 1.414214 (inexact, rounded) + [InlineData(0x32800000U, 0x32800000U)] // sqrt(+0) = +0 + [InlineData(0xB2800000U, 0xB2800000U)] // sqrt(-0) = -0 (sign preserved) + [InlineData(0x35000000U, 0x33800000U)] // sqrt(0E5) = 0E2 (preferred exponent floor(5/2)) + [InlineData(0xB2800004U, 0x7C000000U)] // sqrt(-4) = NaN (invalid) + [InlineData(0x78000000U, 0x78000000U)] // sqrt(+Infinity) = +Infinity + [InlineData(0xF8000000U, 0x7C000000U)] // sqrt(-Infinity) = NaN (invalid) + [InlineData(0xFC000000U, 0xFC000000U)] // sqrt(NaN) = NaN + [InlineData(0xFC001234U, 0xFC001234U)] // NaN payload preserved + [InlineData(0xFC100000U, 0xFC000000U)] // out-of-range NaN payload cleared + public static void SqrtTest(uint value, uint expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal32.Sqrt(Unsafe.BitCast(value)))); + } + + [Theory] + [InlineData(0x32800001U, 0x31800001U, 0x31800064U)] // quantize(1, 1E-2) = 1.00 (exact scale up) + [InlineData(0x32000019U, 0x32800001U, 0x32800002U)] // quantize(2.5, 1E0) = 2 (ties to even) + [InlineData(0x32000023U, 0x32800001U, 0x32800004U)] // quantize(3.5, 1E0) = 4 (ties to even) + [InlineData(0x310004D2U, 0x31800001U, 0x3180007BU)] // quantize(1.234, 1E-2) = 1.23 + [InlineData(0x3292D687U, 0x32000001U, 0x7C000000U)] // quantize(1234567, 1E-1) needs 8 digits -> NaN + [InlineData(0xB5000000U, 0x31800001U, 0xB1800000U)] // quantize(-0E5, 1E-2) = -0E-2 (target quantum) + [InlineData(0x32800004U, 0x33800001U, 0x33800000U)] // quantize(4, 1E2) = 0E2 (rounds to zero) + [InlineData(0x3280003CU, 0x33800001U, 0x33800001U)] // quantize(60, 1E2) = 1E2 (rounds up) + [InlineData(0x78000000U, 0x78000000U, 0x78000000U)] // quantize(+Inf, +Inf) = +Inf + [InlineData(0xF8000000U, 0x78000000U, 0xF8000000U)] // quantize(-Inf, +Inf) = -Inf (sign of x) + [InlineData(0x78000000U, 0x32800001U, 0x7C000000U)] // quantize(+Inf, finite) = NaN + [InlineData(0x32800001U, 0x78000000U, 0x7C000000U)] // quantize(finite, +Inf) = NaN + [InlineData(0x7C001234U, 0x32800001U, 0x7C001234U)] // quantize(qNaN, finite) = qNaN (payload preserved) + [InlineData(0x32800001U, 0x7C002222U, 0x7C002222U)] // quantize(finite, qNaN) = qNaN (payload preserved) + public static void QuantizeTest(uint value, uint quantum, uint expected) + { + Decimal32 result = Decimal32.Quantize(Unsafe.BitCast(value), Unsafe.BitCast(quantum)); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [Theory] + [InlineData(0x31803039U, 0x31800001U)] // quantum(123.45) = 1E-2 + [InlineData(0xB2800007U, 0x32800001U)] // quantum(-7) = 1E0 (always positive) + [InlineData(0x35000000U, 0x35000001U)] // quantum(0E5) = 1E5 + [InlineData(0x78000000U, 0x78000000U)] // quantum(+Inf) = +Inf + [InlineData(0xF8000000U, 0x78000000U)] // quantum(-Inf) = +Inf (sign cleared) + [InlineData(0x7C001234U, 0x7C001234U)] // quantum(qNaN) = qNaN (payload preserved) + [InlineData(0xFC000000U, 0xFC000000U)] // quantum(-NaN) = -NaN (propagated) + public static void QuantumTest(uint value, uint expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal32.Quantum(Unsafe.BitCast(value)))); + } + + [Theory] + [InlineData(0x32800001U, 0x328003E7U, true)] // same exponent + [InlineData(0x32800001U, 0x32000001U, false)] // different exponent + [InlineData(0x7C000000U, 0x7C000000U, true)] // both NaN + [InlineData(0x7C000000U, 0x32800001U, false)] // NaN vs finite + [InlineData(0x78000000U, 0xF8000000U, true)] // both Infinity + [InlineData(0x78000000U, 0x7C000000U, false)] // Infinity vs NaN + [InlineData(0x78000000U, 0x32800001U, false)] // Infinity vs finite + public static void SameQuantumTest(uint x, uint y, bool expected) + { + Assert.Equal(expected, Decimal32.SameQuantum(Unsafe.BitCast(x), Unsafe.BitCast(y))); + } + + + [Theory] + [InlineData(0x32800002U, 0x32800003U, 0x32800004U, 0x3280000AU)] // 2 * 3 + 4 = 10 + [InlineData(0x2F8F4241U, 0x2F8F4241U, 0xAF8F4242U, 0x2C800001U)] // 1.000001 * 1.000001 - 1.000002 = 1E-12 (fused) + [InlineData(0x35000000U, 0x33800003U, 0x31000007U, 0x31000007U)] // 0E5 * 3E2 + 7E-3 + [InlineData(0x32800003U, 0x32800004U, 0x32800000U, 0x3280000CU)] // 3 * 4 + 0 = 12 + [InlineData(0x32800002U, 0x32800003U, 0xB2800006U, 0x32800000U)] // 2 * 3 + (-6) = +0 + [InlineData(0xB2800002U, 0x32800003U, 0x32800006U, 0x32800000U)] // -2 * 3 + 6 = +0 + [InlineData(0x32800003U, 0x7C001234U, 0x32800004U, 0x7C001234U)] // 3 * qNaN(0x1234) + 4 -> qNaN + [InlineData(0x7C000011U, 0x32800002U, 0x7C000022U, 0x7C000022U)] // qNaN(x) * 2 + qNaN(z) -> z payload + [InlineData(0x32800002U, 0x78000000U, 0x32800003U, 0x78000000U)] // 2 * +Inf + 3 = +Inf + [InlineData(0xB2800002U, 0x78000000U, 0x32800003U, 0xF8000000U)] // -2 * +Inf + 3 = -Inf + [InlineData(0x32800000U, 0x78000000U, 0x32800005U, 0x7C000000U)] // 0 * +Inf + 5 -> qNaN + [InlineData(0x32800002U, 0x78000000U, 0xF8000000U, 0x7C000000U)] // 2 * +Inf + (-Inf) -> qNaN + public static void FusedMultiplyAddTest(uint x, uint y, uint z, uint expected) + { + Decimal32 result = Decimal32.FusedMultiplyAdd(Unsafe.BitCast(x), Unsafe.BitCast(y), Unsafe.BitCast(z)); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal32BitDecrement), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void BitDecrement_IntelReferenceVectors(uint value, uint expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal32.BitDecrement(Unsafe.BitCast(value)))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal32BitIncrement), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void BitIncrement_IntelReferenceVectors(uint value, uint expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal32.BitIncrement(Unsafe.BitCast(value)))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal32ILogB), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void ILogB_IntelReferenceVectors(uint value, int expected) + { + Assert.Equal(expected, Decimal32.ILogB(Unsafe.BitCast(value))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal32ScaleB), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void ScaleB_IntelReferenceVectors(uint value, int n, uint expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal32.ScaleB(Unsafe.BitCast(value), n))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal32FusedMultiplyAdd), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void FusedMultiplyAdd_IntelReferenceVectors(uint x, uint y, uint z, uint expected) + { + Decimal32 result = Decimal32.FusedMultiplyAdd(Unsafe.BitCast(x), Unsafe.BitCast(y), Unsafe.BitCast(z)); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] [MemberData(nameof(DecimalIeee754IntelTestData.Decimal32Arithmetic), MemberType = typeof(DecimalIeee754IntelTestData))] public static void op_Arithmetic_IntelReferenceVectors(string operation, uint left, uint right, uint expected) @@ -1559,6 +1825,38 @@ public static void op_Modulus_IntelReferenceVectors(uint left, uint right, uint Assert.Equal(expected, Unsafe.BitCast(l % r)); } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal32Remainder), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void Ieee754Remainder_IntelReferenceVectors(uint left, uint right, uint expected) + { + Decimal32 l = Unsafe.BitCast(left); + Decimal32 r = Unsafe.BitCast(right); + + Assert.Equal(expected, Unsafe.BitCast(Decimal32.Ieee754Remainder(l, r))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal32Sqrt), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void Sqrt_IntelReferenceVectors(uint value, uint expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal32.Sqrt(Unsafe.BitCast(value)))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal32Quantize), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void Quantize_IntelReferenceVectors(uint value, uint quantum, uint expected) + { + Decimal32 result = Decimal32.Quantize(Unsafe.BitCast(value), Unsafe.BitCast(quantum)); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal32Quantum), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void Quantum_IntelReferenceVectors(uint value, uint expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal32.Quantum(Unsafe.BitCast(value)))); + } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] [MemberData(nameof(DecimalIeee754IntelTestData.Decimal32Comparison), MemberType = typeof(DecimalIeee754IntelTestData))] public static void op_Comparison_IntelReferenceVectors(string operation, uint left, uint right, bool expected) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs index d24dc9e51dc78d..5dfb2f30fbf2d1 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs @@ -1536,6 +1536,272 @@ public static void TryParsePartial_Invalid(string value, NumberStyles style, IFo Assert.Equal(0, bytesConsumed); } + [Theory] + [InlineData(0x31C0000000000000UL, 0x0000000000000001UL)] // +0 -> +MINFP + [InlineData(0xB1C0000000000000UL, 0x0000000000000001UL)] // -0 -> +MINFP + [InlineData(0x31C0000000000001UL, 0x2FE38D7EA4C68001UL)] // 1 -> 1.000000000000001 + [InlineData(0xB1C0000000000001UL, 0xEBF386F26FC0FFFFUL)] // -1 -> -0.9999999999999999 + [InlineData(0x77FB86F26FC0FFFFUL, 0x7800000000000000UL)] // +MAXFP -> +Infinity + [InlineData(0xF7FB86F26FC0FFFFUL, 0xF7FB86F26FC0FFFEUL)] // -MAXFP steps toward zero + [InlineData(0x0000000000000001UL, 0x0000000000000002UL)] // +MINFP + [InlineData(0x8000000000000001UL, 0x8000000000000000UL)] // -MINFP -> -0 + [InlineData(0x6BFB86F26FC0FFFFUL, 0x30038D7EA4C68000UL)] // coefficient carry + [InlineData(0x7800000000000000UL, 0x7800000000000000UL)] // +Infinity + [InlineData(0xF800000000000000UL, 0xF7FB86F26FC0FFFFUL)] // -Infinity -> -MAXFP + [InlineData(0xFC00000000000000UL, 0xFC00000000000000UL)] // NaN + [InlineData(0x7E00000000001234UL, 0x7C00000000001234UL)] // signaling NaN canonicalized + [InlineData(0x7C03FFFFFFFFFFFFUL, 0x7C00000000000000UL)] // out-of-range NaN payload canonicalized + public static void BitIncrementTest(ulong bits, ulong expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal64.BitIncrement(Unsafe.BitCast(bits)))); + } + + [Theory] + [InlineData(0x31C0000000000000UL, 0x8000000000000001UL)] // +0 -> -MINFP + [InlineData(0xB1C0000000000000UL, 0x8000000000000001UL)] // -0 -> -MINFP + [InlineData(0x31C0000000000001UL, 0x6BF386F26FC0FFFFUL)] // 1 -> 0.9999999999999999 + [InlineData(0xB1C0000000000001UL, 0xAFE38D7EA4C68001UL)] // -1 -> -1.000000000000001 + [InlineData(0x77FB86F26FC0FFFFUL, 0x77FB86F26FC0FFFEUL)] // +MAXFP steps toward zero + [InlineData(0xF7FB86F26FC0FFFFUL, 0xF800000000000000UL)] // -MAXFP -> -Infinity + [InlineData(0x0000000000000001UL, 0x0000000000000000UL)] // +MINFP -> +0 + [InlineData(0x8000000000000001UL, 0x8000000000000002UL)] // -MINFP + [InlineData(0x6BFB86F26FC0FFFFUL, 0x6BFB86F26FC0FFFEUL)] // normal step + [InlineData(0x7800000000000000UL, 0x77FB86F26FC0FFFFUL)] // +Infinity -> +MAXFP + [InlineData(0xF800000000000000UL, 0xF800000000000000UL)] // -Infinity + [InlineData(0xFC00000000000000UL, 0xFC00000000000000UL)] // NaN + [InlineData(0x7E00000000001234UL, 0x7C00000000001234UL)] // signaling NaN canonicalized + [InlineData(0x7C03FFFFFFFFFFFFUL, 0x7C00000000000000UL)] // out-of-range NaN payload canonicalized + public static void BitDecrementTest(ulong bits, ulong expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal64.BitDecrement(Unsafe.BitCast(bits)))); + } + + [Theory] + [InlineData(0x31C0000000000001UL, 0)] // 1 + [InlineData(0x3260000000000001UL, 5)] // 1e5 + [InlineData(0x31C000000012D687UL, 6)] // 1234567 + [InlineData(0x3120000000000389UL, -3)] // 9.05e-3 + [InlineData(0x31C000000000002AUL, 1)] // 42 + [InlineData(0x25E0000000000001UL, -95)] // 1e-95 + [InlineData(0x3280000000000001UL, 6)] // 1e6 + [InlineData(0x31C0000000000000UL, int.MinValue)] // +0 + [InlineData(0xB1C0000000000000UL, int.MinValue)] // -0 + [InlineData(0xFC00000000000000UL, int.MaxValue)] // NaN + [InlineData(0x7800000000000000UL, int.MaxValue)] // +Infinity + [InlineData(0xF800000000000000UL, int.MaxValue)] // -Infinity + public static void ILogBTest(ulong bits, int expected) + { + Assert.Equal(expected, Decimal64.ILogB(Unsafe.BitCast(bits))); + } + + [Theory] + [InlineData(0x31C000000000007BUL, 0, 0x31C000000000007BUL)] // 123 scaleB 0 + [InlineData(0x31C000000000007BUL, 2, 0x320000000000007BUL)] // 123 scaleB 2 + [InlineData(0x31C000000000007BUL, -2, 0x318000000000007BUL)] // 123 scaleB -2 + [InlineData(0x31C0000000000001UL, 16, 0x33C0000000000001UL)] // absorb into coefficient + [InlineData(0x31C0000000000009UL, 369, 0x5FE0000000000009UL)] // absorb at max quantum + [InlineData(0x31C0000000000001UL, 394, 0x7800000000000000UL)] // overflow -> +Infinity + [InlineData(0xB1C0000000000001UL, 394, 0xF800000000000000UL)] // overflow -> -Infinity + [InlineData(0x01E0000000000001UL, -50, 0x0000000000000000UL)] // deep underflow -> +0 + [InlineData(0x000000000000000FUL, -1, 0x0000000000000002UL)] // gradual underflow, tie -> even (up) + [InlineData(0x0000000000000019UL, -1, 0x0000000000000002UL)] // gradual underflow, tie -> even (stay) + [InlineData(0x000000000000000EUL, -1, 0x0000000000000001UL)] // gradual underflow, rounds down + [InlineData(0x31C0000000000000UL, 5, 0x3260000000000000UL)] // +0 + [InlineData(0xB1C0000000000000UL, 5, 0xB260000000000000UL)] // -0 + [InlineData(0xFC00000000000000UL, 5, 0xFC00000000000000UL)] // NaN + [InlineData(0x7800000000000000UL, 5, 0x7800000000000000UL)] // +Infinity + [InlineData(0xF800000000000000UL, 5, 0xF800000000000000UL)] // -Infinity + [InlineData(0x7E00000000001234UL, 5, 0x7C00000000001234UL)] // signaling NaN canonicalized + [InlineData(0x7C03FFFFFFFFFFFFUL, 5, 0x7C00000000000000UL)] // out-of-range NaN payload canonicalized + public static void ScaleBTest(ulong bits, int n, ulong expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal64.ScaleB(Unsafe.BitCast(bits), n))); + } + + [Theory] + [InlineData(0x31C0000000000003UL, 0x31C0000000000002UL, 0xB1C0000000000001UL)] // 3 rem 2 = -1 (quotient rounds up to even 2) + [InlineData(0x31C0000000000005UL, 0x31C0000000000002UL, 0x31C0000000000001UL)] // 5 rem 2 = 1 (quotient 2, exact half stays) + [InlineData(0x31C0000000000007UL, 0x31C0000000000002UL, 0xB1C0000000000001UL)] // 7 rem 2 = -1 (quotient rounds up to even 4) + [InlineData(0x31C000000000000BUL, 0x31C0000000000004UL, 0xB1C0000000000001UL)] // 11 rem 4 = -1 (nearest multiple 12) + [InlineData(0xB1C000000000000BUL, 0x31C0000000000004UL, 0x31C0000000000001UL)] // -11 rem 4 = 1 (sign flips) + [InlineData(0x31C000000000000AUL, 0x31C0000000000003UL, 0x31C0000000000001UL)] // 10 rem 3 = 1 + [InlineData(0x31C0000000000009UL, 0x31C0000000000003UL, 0x31C0000000000000UL)] // 9 rem 3 = +0 (exact multiple) + [InlineData(0xB1C0000000000009UL, 0x31C0000000000003UL, 0xB1C0000000000000UL)] // -9 rem 3 = -0 + [InlineData(0x31C000000000002AUL, 0x7800000000000000UL, 0x31C000000000002AUL)] // finite rem +Infinity = finite + [InlineData(0xB1C000000000002AUL, 0xF800000000000000UL, 0xB1C000000000002AUL)] // -finite rem -Infinity = -finite + [InlineData(0x31C0000000000005UL, 0x31C0000000000000UL, 0x7C00000000000000UL)] // x rem 0 = NaN + [InlineData(0x7800000000000000UL, 0x31C0000000000005UL, 0x7C00000000000000UL)] // +Infinity rem finite = NaN + [InlineData(0xFC00000000000000UL, 0x31C0000000000005UL, 0xFC00000000000000UL)] // NaN rem finite = NaN + [InlineData(0x31C0000000000005UL, 0xFC00000000000000UL, 0xFC00000000000000UL)] // finite rem NaN = NaN + [InlineData(0x7E00000000001234UL, 0x31C0000000000005UL, 0x7C00000000001234UL)] // signaling NaN operand quieted + [InlineData(0x31C0000000000005UL, 0x7C03FFFFFFFFFFFFUL, 0x7C00000000000000UL)] // out-of-range NaN payload cleared + [InlineData(0x4A8000000009AD67UL, 0x0E800000050B4C29UL, 0x8E800000019BDA7EUL)] + [InlineData(0xB5E0000000626F94UL, 0x1D40000000000000UL, 0x7C00000000000000UL)] + [InlineData(0xB4200FE72B9FDBC0UL, 0x17400000000A5185UL, 0x9740000000036ACEUL)] + [InlineData(0x7800000000000000UL, 0x9C8000000252A74EUL, 0x7C00000000000000UL)] + [InlineData(0xD3E00001CB6E5D69UL, 0x2180000000000038UL, 0xA180000000000018UL)] + [InlineData(0x5A1F1A273FA44E96UL, 0x7800000000000000UL, 0x5A1F1A273FA44E96UL)] + [InlineData(0xFC00000000000000UL, 0x3520000000000000UL, 0xFC00000000000000UL)] + [InlineData(0x93005977BAC563A4UL, 0xFC00000000000000UL, 0xFC00000000000000UL)] + [InlineData(0x7800000000000000UL, 0x3A00000000000124UL, 0x7C00000000000000UL)] + [InlineData(0x1F2000013A188328UL, 0x0760000000000053UL, 0x8760000000000005UL)] + [InlineData(0x7800000000000000UL, 0xAB820EB1DDB4A407UL, 0x7C00000000000000UL)] + [InlineData(0x0C20000000017A49UL, 0x34E0000000000000UL, 0x7C00000000000000UL)] + [InlineData(0x1B60000016CD3873UL, 0x3A40004A2EB474ADUL, 0x1B60000016CD3873UL)] + [InlineData(0x9FE7AEF8FDA9509FUL, 0xADC0000000000000UL, 0x7C00000000000000UL)] + [InlineData(0x2B00000000038CEDUL, 0x02C00000E7C762E2UL, 0x82C000002855EE6EUL)] + [InlineData(0xF800000000000000UL, 0x14E0000000001EEFUL, 0x7C00000000000000UL)] + [InlineData(0x2200000000000000UL, 0xDC60000000811427UL, 0x2200000000000000UL)] + [InlineData(0x99E0004DB83C1E4CUL, 0xF800000000000000UL, 0x99E0004DB83C1E4CUL)] + [InlineData(0x9D210DF530D52797UL, 0x3D804E70291AD54DUL, 0x9D210DF530D52797UL)] + [InlineData(0xAF6000000000027EUL, 0x8D80000000000000UL, 0x7C00000000000000UL)] + [InlineData(0xB00000002A3460C2UL, 0x8C4000021DBBFBECUL, 0x8C400000D82162D0UL)] + [InlineData(0xB04000173B5D4163UL, 0x9F80000000000000UL, 0x7C00000000000000UL)] + [InlineData(0x8E85EF219683E203UL, 0xD4A00BA4519CCA7AUL, 0x8E85EF219683E203UL)] + [InlineData(0xCAA0000000000007UL, 0x272FECB8E7CAAA29UL, 0xA721863C708B7A09UL)] + [InlineData(0x1920000000255B64UL, 0xCE60585A1B021D1DUL, 0x1920000000255B64UL)] + [InlineData(0x5980000000000000UL, 0x3A80000000017568UL, 0x3A80000000000000UL)] + [InlineData(0xFC00000000000000UL, 0xC540000000000000UL, 0xFC00000000000000UL)] + [InlineData(0xFC00000000000000UL, 0xFC00000000000000UL, 0xFC00000000000000UL)] + [InlineData(0x35204F0E3A71F3FEUL, 0x3D00000000000000UL, 0x7C00000000000000UL)] + [InlineData(0xCC80000000000000UL, 0x7800000000000000UL, 0xCC80000000000000UL)] + [InlineData(0xA9E0000000C23EA3UL, 0xD1400001A656CD97UL, 0xA9E0000000C23EA3UL)] + [InlineData(0x9DA00008B4CFF7C9UL, 0x242000328AE0FDF4UL, 0x9DA00008B4CFF7C9UL)] + [InlineData(0x488000000000002BUL, 0xC1600000053AAE73UL, 0xC16000000117B014UL)] + [InlineData(0x2BE0000002AAAC79UL, 0xC6200000A117F30CUL, 0x2BE0000002AAAC79UL)] + [InlineData(0x02E0000000001A97UL, 0x2800000000000005UL, 0x02E0000000001A97UL)] + [InlineData(0xA240000000000000UL, 0x2A4000427E8EF016UL, 0xA240000000000000UL)] + public static void Ieee754RemainderTest(ulong left, ulong right, ulong expected) + { + Decimal64 result = Decimal64.Ieee754Remainder(Unsafe.BitCast(left), Unsafe.BitCast(right)); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [Theory] + [InlineData(0x31C0000000000004UL, 0x31C0000000000002UL)] // sqrt(4) = 2 + [InlineData(0x31C0000000000009UL, 0x31C0000000000003UL)] // sqrt(9) = 3 + [InlineData(0x31C0000000000064UL, 0x31C000000000000AUL)] // sqrt(100) = 10 + [InlineData(0x3200000000000004UL, 0x31E0000000000002UL)] // sqrt(4E2) = 2E1 (even exponent halves) + [InlineData(0x3140000000000009UL, 0x3180000000000003UL)] // sqrt(9E-4) = 3E-2 + [InlineData(0x31C0000000000001UL, 0x31C0000000000001UL)] // sqrt(1) = 1 + [InlineData(0x31C0000000000002UL, 0x2FE50638410593E7UL)] // sqrt(2) = 1.414213562373095 (inexact) + [InlineData(0x31C0000000000000UL, 0x31C0000000000000UL)] // sqrt(+0) = +0 + [InlineData(0xB1C0000000000000UL, 0xB1C0000000000000UL)] // sqrt(-0) = -0 (sign preserved) + [InlineData(0x3260000000000000UL, 0x3200000000000000UL)] // sqrt(0E5) = 0E2 (preferred exponent floor(5/2)) + [InlineData(0xB1C0000000000004UL, 0x7C00000000000000UL)] // sqrt(-4) = NaN (invalid) + [InlineData(0x7800000000000000UL, 0x7800000000000000UL)] // sqrt(+Infinity) = +Infinity + [InlineData(0xF800000000000000UL, 0x7C00000000000000UL)] // sqrt(-Infinity) = NaN (invalid) + [InlineData(0xFC00000000000000UL, 0xFC00000000000000UL)] // sqrt(NaN) = NaN + [InlineData(0xFC00000000001234UL, 0xFC00000000001234UL)] // NaN payload preserved + [InlineData(0xFC04000000000000UL, 0xFC00000000000000UL)] // out-of-range NaN payload cleared + public static void SqrtTest(ulong value, ulong expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal64.Sqrt(Unsafe.BitCast(value)))); + } + + [Theory] + [InlineData(0x31C0000000000001UL, 0x3180000000000001UL, 0x3180000000000064UL)] // quantize(1, 1E-2) = 1.00 (exact scale up) + [InlineData(0x31A0000000000019UL, 0x31C0000000000001UL, 0x31C0000000000002UL)] // quantize(2.5, 1E0) = 2 (ties to even) + [InlineData(0x31A0000000000023UL, 0x31C0000000000001UL, 0x31C0000000000004UL)] // quantize(3.5, 1E0) = 4 (ties to even) + [InlineData(0x31600000000004D2UL, 0x3180000000000001UL, 0x318000000000007BUL)] // quantize(1.234, 1E-2) = 1.23 + [InlineData(0x31C000000012D687UL, 0x31A0000000000001UL, 0x31A0000000BC6146UL)] // quantize(1234567, 1E-1) = 1234567.0 + [InlineData(0xB260000000000000UL, 0x3180000000000001UL, 0xB180000000000000UL)] // quantize(-0E5, 1E-2) = -0E-2 (target quantum) + [InlineData(0x31C0000000000004UL, 0x3200000000000001UL, 0x3200000000000000UL)] // quantize(4, 1E2) = 0E2 (rounds to zero) + [InlineData(0x31C000000000003CUL, 0x3200000000000001UL, 0x3200000000000001UL)] // quantize(60, 1E2) = 1E2 (rounds up) + [InlineData(0x7800000000000000UL, 0x7800000000000000UL, 0x7800000000000000UL)] // quantize(+Inf, +Inf) = +Inf + [InlineData(0xF800000000000000UL, 0x7800000000000000UL, 0xF800000000000000UL)] // quantize(-Inf, +Inf) = -Inf (sign of x) + [InlineData(0x7800000000000000UL, 0x31C0000000000001UL, 0x7C00000000000000UL)] // quantize(+Inf, finite) = NaN + [InlineData(0x31C0000000000001UL, 0x7800000000000000UL, 0x7C00000000000000UL)] // quantize(finite, +Inf) = NaN + [InlineData(0x7C00000000001234UL, 0x31C0000000000001UL, 0x7C00000000001234UL)] // quantize(qNaN, finite) = qNaN (payload preserved) + [InlineData(0x31C0000000000001UL, 0x7C00000000002222UL, 0x7C00000000002222UL)] // quantize(finite, qNaN) = qNaN (payload preserved) + public static void QuantizeTest(ulong value, ulong quantum, ulong expected) + { + Decimal64 result = Decimal64.Quantize(Unsafe.BitCast(value), Unsafe.BitCast(quantum)); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [Theory] + [InlineData(0x3180000000003039UL, 0x3180000000000001UL)] // quantum(123.45) = 1E-2 + [InlineData(0xB1C0000000000007UL, 0x31C0000000000001UL)] // quantum(-7) = 1E0 (always positive) + [InlineData(0x3260000000000000UL, 0x3260000000000001UL)] // quantum(0E5) = 1E5 + [InlineData(0x7800000000000000UL, 0x7800000000000000UL)] // quantum(+Inf) = +Inf + [InlineData(0xF800000000000000UL, 0x7800000000000000UL)] // quantum(-Inf) = +Inf (sign cleared) + [InlineData(0x7C00000000001234UL, 0x7C00000000001234UL)] // quantum(qNaN) = qNaN (payload preserved) + [InlineData(0xFC00000000000000UL, 0xFC00000000000000UL)] // quantum(-NaN) = -NaN (propagated) + public static void QuantumTest(ulong value, ulong expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal64.Quantum(Unsafe.BitCast(value)))); + } + + [Theory] + [InlineData(0x31C0000000000001UL, 0x31C00000000003E7UL, true)] // same exponent + [InlineData(0x31C0000000000001UL, 0x31A0000000000001UL, false)] // different exponent + [InlineData(0x7C00000000000000UL, 0x7C00000000000000UL, true)] // both NaN + [InlineData(0x7C00000000000000UL, 0x31C0000000000001UL, false)] // NaN vs finite + [InlineData(0x7800000000000000UL, 0xF800000000000000UL, true)] // both Infinity + [InlineData(0x7800000000000000UL, 0x7C00000000000000UL, false)] // Infinity vs NaN + [InlineData(0x7800000000000000UL, 0x31C0000000000001UL, false)] // Infinity vs finite + public static void SameQuantumTest(ulong x, ulong y, bool expected) + { + Assert.Equal(expected, Decimal64.SameQuantum(Unsafe.BitCast(x), Unsafe.BitCast(y))); + } + + + [Theory] + [InlineData(0x31C0000000000002UL, 0x31C0000000000003UL, 0x31C0000000000004UL, 0x31C000000000000AUL)] // 2 * 3 + 4 = 10 + [InlineData(0x31000000000F4241UL, 0x31000000000F4241UL, 0xB1000000000F4242UL, 0x3040000000000001UL)] // 1.000001 * 1.000001 - 1.000002 = 1E-12 (fused) + [InlineData(0x3260000000000000UL, 0x3200000000000003UL, 0x3160000000000007UL, 0x3160000000000007UL)] // 0E5 * 3E2 + 7E-3 + [InlineData(0x31C0000000000003UL, 0x31C0000000000004UL, 0x31C0000000000000UL, 0x31C000000000000CUL)] // 3 * 4 + 0 = 12 + [InlineData(0x31C0000000000002UL, 0x31C0000000000003UL, 0xB1C0000000000006UL, 0x31C0000000000000UL)] // 2 * 3 + (-6) = +0 + [InlineData(0xB1C0000000000002UL, 0x31C0000000000003UL, 0x31C0000000000006UL, 0x31C0000000000000UL)] // -2 * 3 + 6 = +0 + [InlineData(0x31C0000000000003UL, 0x7C00000000001234UL, 0x31C0000000000004UL, 0x7C00000000001234UL)] // 3 * qNaN(0x1234) + 4 -> qNaN + [InlineData(0x7C00000000000011UL, 0x31C0000000000002UL, 0x7C00000000000022UL, 0x7C00000000000022UL)] // qNaN(x) * 2 + qNaN(z) -> z payload + [InlineData(0x31C0000000000002UL, 0x7800000000000000UL, 0x31C0000000000003UL, 0x7800000000000000UL)] // 2 * +Inf + 3 = +Inf + [InlineData(0xB1C0000000000002UL, 0x7800000000000000UL, 0x31C0000000000003UL, 0xF800000000000000UL)] // -2 * +Inf + 3 = -Inf + [InlineData(0x31C0000000000000UL, 0x7800000000000000UL, 0x31C0000000000005UL, 0x7C00000000000000UL)] // 0 * +Inf + 5 -> qNaN + [InlineData(0x31C0000000000002UL, 0x7800000000000000UL, 0xF800000000000000UL, 0x7C00000000000000UL)] // 2 * +Inf + (-Inf) -> qNaN + public static void FusedMultiplyAddTest(ulong x, ulong y, ulong z, ulong expected) + { + Decimal64 result = Decimal64.FusedMultiplyAdd(Unsafe.BitCast(x), Unsafe.BitCast(y), Unsafe.BitCast(z)); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal64BitDecrement), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void BitDecrement_IntelReferenceVectors(ulong value, ulong expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal64.BitDecrement(Unsafe.BitCast(value)))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal64BitIncrement), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void BitIncrement_IntelReferenceVectors(ulong value, ulong expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal64.BitIncrement(Unsafe.BitCast(value)))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal64ILogB), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void ILogB_IntelReferenceVectors(ulong value, int expected) + { + Assert.Equal(expected, Decimal64.ILogB(Unsafe.BitCast(value))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal64ScaleB), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void ScaleB_IntelReferenceVectors(ulong value, int n, ulong expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal64.ScaleB(Unsafe.BitCast(value), n))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal64FusedMultiplyAdd), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void FusedMultiplyAdd_IntelReferenceVectors(ulong x, ulong y, ulong z, ulong expected) + { + Decimal64 result = Decimal64.FusedMultiplyAdd(Unsafe.BitCast(x), Unsafe.BitCast(y), Unsafe.BitCast(z)); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] [MemberData(nameof(DecimalIeee754IntelTestData.Decimal64Arithmetic), MemberType = typeof(DecimalIeee754IntelTestData))] public static void op_Arithmetic_IntelReferenceVectors(string operation, ulong left, ulong right, ulong expected) @@ -1565,6 +1831,38 @@ public static void op_Modulus_IntelReferenceVectors(ulong left, ulong right, ulo Assert.Equal(expected, Unsafe.BitCast(l % r)); } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal64Remainder), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void Ieee754Remainder_IntelReferenceVectors(ulong left, ulong right, ulong expected) + { + Decimal64 l = Unsafe.BitCast(left); + Decimal64 r = Unsafe.BitCast(right); + + Assert.Equal(expected, Unsafe.BitCast(Decimal64.Ieee754Remainder(l, r))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal64Sqrt), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void Sqrt_IntelReferenceVectors(ulong value, ulong expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal64.Sqrt(Unsafe.BitCast(value)))); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal64Quantize), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void Quantize_IntelReferenceVectors(ulong value, ulong quantum, ulong expected) + { + Decimal64 result = Decimal64.Quantize(Unsafe.BitCast(value), Unsafe.BitCast(quantum)); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal64Quantum), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void Quantum_IntelReferenceVectors(ulong value, ulong expected) + { + Assert.Equal(expected, Unsafe.BitCast(Decimal64.Quantum(Unsafe.BitCast(value)))); + } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] [MemberData(nameof(DecimalIeee754IntelTestData.Decimal64Comparison), MemberType = typeof(DecimalIeee754IntelTestData))] public static void op_Comparison_IntelReferenceVectors(string operation, ulong left, ulong right, bool expected) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DecimalIeee754IntelTestData.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DecimalIeee754IntelTestData.cs index 155a71063afe89..e75dc3296aed32 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DecimalIeee754IntelTestData.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DecimalIeee754IntelTestData.cs @@ -100,6 +100,30 @@ public static class DecimalIeee754IntelTestData private static readonly HashSet s_bid64Modulus = new() { "bid64_fmod" }; private static readonly HashSet s_bid128Modulus = new() { "bid128_fmod" }; + // Round-to-nearest IEEE 754 remainder (Ieee754Remainder), not the truncating fmod above. Every reference + // row is exercised, including NaN payload propagation and the invalid operations (Inf rem y, x rem 0) that + // quiet to the canonical NaN. + private static readonly HashSet s_bid32Remainder = new() { "bid32_rem" }; + private static readonly HashSet s_bid64Remainder = new() { "bid64_rem" }; + private static readonly HashSet s_bid128Remainder = new() { "bid128_rem" }; + + // Square root (Sqrt). Every reference row is exercised, including NaN payload propagation, sqrt(+Infinity), + // and the invalid operations (sqrt of a negative value) that quiet to the canonical NaN. + private static readonly HashSet s_bid32Sqrt = new() { "bid32_sqrt" }; + private static readonly HashSet s_bid64Sqrt = new() { "bid64_sqrt" }; + private static readonly HashSet s_bid128Sqrt = new() { "bid128_sqrt" }; + + // Quantize (adjust to the quantum of a second operand) and Quantum (1 x 10^exp sharing the exponent). Every + // reference row is exercised, including NaN payload propagation, the two-infinity and mixed-infinity cases, + // and the invalid rows (unrepresentable quantum) that quiet to the canonical NaN. + private static readonly HashSet s_bid32Quantize = new() { "bid32_quantize" }; + private static readonly HashSet s_bid64Quantize = new() { "bid64_quantize" }; + private static readonly HashSet s_bid128Quantize = new() { "bid128_quantize" }; + + private static readonly HashSet s_bid32Quantum = new() { "bid32_quantum" }; + private static readonly HashSet s_bid64Quantum = new() { "bid64_quantum" }; + private static readonly HashSet s_bid128Quantum = new() { "bid128_quantum" }; + // Round to an integral value under each rounding mode, mapping onto the .NET Round/Ceiling/Floor/Truncate // surface. `round_integral_exact` takes the mode from the rounding-context column, so only its // round-to-nearest-even (rnd == 0) rows are consumed here; the mode-named variants ignore that column. @@ -107,6 +131,34 @@ public static class DecimalIeee754IntelTestData private static readonly HashSet s_bid64RoundIntegral = new() { "bid64_round_integral_exact", "bid64_round_integral_nearest_even", "bid64_round_integral_nearest_away", "bid64_round_integral_negative", "bid64_round_integral_positive", "bid64_round_integral_zero" }; private static readonly HashSet s_bid128RoundIntegral = new() { "bid128_round_integral_exact", "bid128_round_integral_nearest_even", "bid128_round_integral_nearest_away", "bid128_round_integral_negative", "bid128_round_integral_positive", "bid128_round_integral_zero" }; + // ScaleB (the `int` scaling-exponent variant). NaN operands are skipped for the same payload-convention + // reason as RoundIntegral; invalid-flagged rows (overflow/underflow beyond the format range) are also + // skipped because Intel reports them via a sentinel plus the invalid flag rather than the saturated result. + private static readonly HashSet s_bid32ScaleB = new() { "bid32_scalbn" }; + private static readonly HashSet s_bid64ScaleB = new() { "bid64_scalbn" }; + private static readonly HashSet s_bid128ScaleB = new() { "bid128_scalbn" }; + + // BitIncrement/BitDecrement (IEEE 754 nextUp/nextDown). NaN operands are skipped for the same + // payload-convention reason as RoundIntegral. + private static readonly HashSet s_bid32BitIncrement = new() { "bid32_nextup" }; + private static readonly HashSet s_bid64BitIncrement = new() { "bid64_nextup" }; + private static readonly HashSet s_bid128BitIncrement = new() { "bid128_nextup" }; + private static readonly HashSet s_bid32BitDecrement = new() { "bid32_nextdown" }; + private static readonly HashSet s_bid64BitDecrement = new() { "bid64_nextdown" }; + private static readonly HashSet s_bid128BitDecrement = new() { "bid128_nextdown" }; + + // ILogB. Only finite, non-zero operands are consumed (invalid-flagged rows cover zero/infinity/NaN, where + // Intel's C99 ilogb sentinels diverge from the .NET int.MinValue/int.MaxValue contract). + private static readonly HashSet s_bid32ILogB = new() { "bid32_ilogb" }; + private static readonly HashSet s_bid64ILogB = new() { "bid64_ilogb" }; + private static readonly HashSet s_bid128ILogB = new() { "bid128_ilogb" }; + + // FusedMultiplyAdd. Every reference row is exercised, including NaN payload propagation and the invalid + // operations (0*Inf, Inf + opposite Inf) that quiet to the canonical NaN; all results match bit-exact. + private static readonly HashSet s_bid32Fma = new() { "bid32_fma" }; + private static readonly HashSet s_bid64Fma = new() { "bid64_fma" }; + private static readonly HashSet s_bid128Fma = new() { "bid128_fma" }; + /// /// Gets a value indicating whether the Intel readtest.in reference vectors are available, /// gating the theories that consume them. @@ -476,6 +528,138 @@ public static IEnumerable Decimal128Modulus() } } + public static IEnumerable Decimal32Remainder() + { + foreach (string[] fields in EnumerateRows(s_bid32Remainder)) + { + if (TryParseBid32(fields[2], out uint left) && TryParseBid32(fields[3], out uint right) && TryParseBid32(fields[4], out uint expected)) + { + yield return new object[] { left, right, expected }; + } + } + } + + public static IEnumerable Decimal64Remainder() + { + foreach (string[] fields in EnumerateRows(s_bid64Remainder)) + { + if (TryParseBid64(fields[2], out ulong left) && TryParseBid64(fields[3], out ulong right) && TryParseBid64(fields[4], out ulong expected)) + { + yield return new object[] { left, right, expected }; + } + } + } + + public static IEnumerable Decimal128Remainder() + { + foreach (string[] fields in EnumerateRows(s_bid128Remainder)) + { + if (TryParseBid128(fields[2], out UInt128 left) && TryParseBid128(fields[3], out UInt128 right) && TryParseBid128(fields[4], out UInt128 expected)) + { + yield return new object[] { left, right, expected }; + } + } + } + + public static IEnumerable Decimal32Sqrt() + { + foreach (string[] fields in EnumerateRows(s_bid32Sqrt)) + { + if (TryParseBid32(fields[2], out uint value) && TryParseBid32(fields[3], out uint expected)) + { + yield return new object[] { value, expected }; + } + } + } + + public static IEnumerable Decimal64Sqrt() + { + foreach (string[] fields in EnumerateRows(s_bid64Sqrt)) + { + if (TryParseBid64(fields[2], out ulong value) && TryParseBid64(fields[3], out ulong expected)) + { + yield return new object[] { value, expected }; + } + } + } + + public static IEnumerable Decimal128Sqrt() + { + foreach (string[] fields in EnumerateRows(s_bid128Sqrt)) + { + if (TryParseBid128(fields[2], out UInt128 value) && TryParseBid128(fields[3], out UInt128 expected)) + { + yield return new object[] { value, expected }; + } + } + } + + public static IEnumerable Decimal32Quantize() + { + foreach (string[] fields in EnumerateRows(s_bid32Quantize)) + { + if (TryParseBid32(fields[2], out uint value) && TryParseBid32(fields[3], out uint quantum) && TryParseBid32(fields[4], out uint expected)) + { + yield return new object[] { value, quantum, expected }; + } + } + } + + public static IEnumerable Decimal64Quantize() + { + foreach (string[] fields in EnumerateRows(s_bid64Quantize)) + { + if (TryParseBid64(fields[2], out ulong value) && TryParseBid64(fields[3], out ulong quantum) && TryParseBid64(fields[4], out ulong expected)) + { + yield return new object[] { value, quantum, expected }; + } + } + } + + public static IEnumerable Decimal128Quantize() + { + foreach (string[] fields in EnumerateRows(s_bid128Quantize)) + { + if (TryParseBid128(fields[2], out UInt128 value) && TryParseBid128(fields[3], out UInt128 quantum) && TryParseBid128(fields[4], out UInt128 expected)) + { + yield return new object[] { value, quantum, expected }; + } + } + } + + public static IEnumerable Decimal32Quantum() + { + foreach (string[] fields in EnumerateRows(s_bid32Quantum)) + { + if (TryParseBid32(fields[2], out uint value) && TryParseBid32(fields[3], out uint expected)) + { + yield return new object[] { value, expected }; + } + } + } + + public static IEnumerable Decimal64Quantum() + { + foreach (string[] fields in EnumerateRows(s_bid64Quantum)) + { + if (TryParseBid64(fields[2], out ulong value) && TryParseBid64(fields[3], out ulong expected)) + { + yield return new object[] { value, expected }; + } + } + } + + public static IEnumerable Decimal128Quantum() + { + foreach (string[] fields in EnumerateRows(s_bid128Quantum)) + { + if (TryParseBid128(fields[2], out UInt128 value) && TryParseBid128(fields[3], out UInt128 expected)) + { + yield return new object[] { value, expected }; + } + } + } + // NaN operands are skipped: rounding leaves the value's payload untouched, but Intel canonicalizes and quiets // NaNs so its result column would not match the raw operand bits. The mode is taken from the operation name. public static IEnumerable Decimal32RoundIntegral() @@ -511,6 +695,172 @@ public static IEnumerable Decimal128RoundIntegral() } } + // NaN operands are skipped for the same payload-quieting reason as RoundIntegral. + public static IEnumerable Decimal32BitIncrement() + { + foreach (string[] fields in EnumerateRows(s_bid32BitIncrement)) + { + if (TryParseBid32(fields[2], out uint value) && !IsBid32NaN(value) && TryParseBid32(fields[3], out uint expected)) + { + yield return new object[] { value, expected }; + } + } + } + + public static IEnumerable Decimal64BitIncrement() + { + foreach (string[] fields in EnumerateRows(s_bid64BitIncrement)) + { + if (TryParseBid64(fields[2], out ulong value) && !IsBid64NaN(value) && TryParseBid64(fields[3], out ulong expected)) + { + yield return new object[] { value, expected }; + } + } + } + + public static IEnumerable Decimal128BitIncrement() + { + foreach (string[] fields in EnumerateRows(s_bid128BitIncrement)) + { + if (TryParseBid128(fields[2], out UInt128 value) && !IsBid128NaN(value) && TryParseBid128(fields[3], out UInt128 expected)) + { + yield return new object[] { value, expected }; + } + } + } + + public static IEnumerable Decimal32BitDecrement() + { + foreach (string[] fields in EnumerateRows(s_bid32BitDecrement)) + { + if (TryParseBid32(fields[2], out uint value) && !IsBid32NaN(value) && TryParseBid32(fields[3], out uint expected)) + { + yield return new object[] { value, expected }; + } + } + } + + public static IEnumerable Decimal64BitDecrement() + { + foreach (string[] fields in EnumerateRows(s_bid64BitDecrement)) + { + if (TryParseBid64(fields[2], out ulong value) && !IsBid64NaN(value) && TryParseBid64(fields[3], out ulong expected)) + { + yield return new object[] { value, expected }; + } + } + } + + public static IEnumerable Decimal128BitDecrement() + { + foreach (string[] fields in EnumerateRows(s_bid128BitDecrement)) + { + if (TryParseBid128(fields[2], out UInt128 value) && !IsBid128NaN(value) && TryParseBid128(fields[3], out UInt128 expected)) + { + yield return new object[] { value, expected }; + } + } + } + + public static IEnumerable Decimal32ScaleB() + { + foreach (string[] fields in EnumerateRows(s_bid32ScaleB)) + { + if ((fields.Length >= 6) && TryParseBid32(fields[2], out uint value) && !IsBid32NaN(value) && TryParseScaleAmount(fields[3], out int n) && TryParseBid32(fields[4], out uint expected) && !IsInvalidFlagged(fields[5])) + { + yield return new object[] { value, n, expected }; + } + } + } + + public static IEnumerable Decimal64ScaleB() + { + foreach (string[] fields in EnumerateRows(s_bid64ScaleB)) + { + if ((fields.Length >= 6) && TryParseBid64(fields[2], out ulong value) && !IsBid64NaN(value) && TryParseScaleAmount(fields[3], out int n) && TryParseBid64(fields[4], out ulong expected) && !IsInvalidFlagged(fields[5])) + { + yield return new object[] { value, n, expected }; + } + } + } + + public static IEnumerable Decimal128ScaleB() + { + foreach (string[] fields in EnumerateRows(s_bid128ScaleB)) + { + if ((fields.Length >= 6) && TryParseBid128(fields[2], out UInt128 value) && !IsBid128NaN(value) && TryParseScaleAmount(fields[3], out int n) && TryParseBid128(fields[4], out UInt128 expected) && !IsInvalidFlagged(fields[5])) + { + yield return new object[] { value, n, expected }; + } + } + } + + public static IEnumerable Decimal32ILogB() + { + foreach (string[] fields in EnumerateRows(s_bid32ILogB)) + { + if (TryParseBid32(fields[2], out uint value) && !IsInvalidFlagged(fields[4]) && int.TryParse(fields[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out int expected)) + { + yield return new object[] { value, expected }; + } + } + } + + public static IEnumerable Decimal64ILogB() + { + foreach (string[] fields in EnumerateRows(s_bid64ILogB)) + { + if (TryParseBid64(fields[2], out ulong value) && !IsInvalidFlagged(fields[4]) && int.TryParse(fields[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out int expected)) + { + yield return new object[] { value, expected }; + } + } + } + + public static IEnumerable Decimal128ILogB() + { + foreach (string[] fields in EnumerateRows(s_bid128ILogB)) + { + if (TryParseBid128(fields[2], out UInt128 value) && !IsInvalidFlagged(fields[4]) && int.TryParse(fields[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out int expected)) + { + yield return new object[] { value, expected }; + } + } + } + + public static IEnumerable Decimal32FusedMultiplyAdd() + { + foreach (string[] fields in EnumerateRows(s_bid32Fma)) + { + if ((fields.Length >= 6) && TryParseBid32(fields[2], out uint x) && TryParseBid32(fields[3], out uint y) && TryParseBid32(fields[4], out uint z) && TryParseBid32(fields[5], out uint expected)) + { + yield return new object[] { x, y, z, expected }; + } + } + } + + public static IEnumerable Decimal64FusedMultiplyAdd() + { + foreach (string[] fields in EnumerateRows(s_bid64Fma)) + { + if ((fields.Length >= 6) && TryParseBid64(fields[2], out ulong x) && TryParseBid64(fields[3], out ulong y) && TryParseBid64(fields[4], out ulong z) && TryParseBid64(fields[5], out ulong expected)) + { + yield return new object[] { x, y, z, expected }; + } + } + } + + public static IEnumerable Decimal128FusedMultiplyAdd() + { + foreach (string[] fields in EnumerateRows(s_bid128Fma)) + { + if ((fields.Length >= 6) && TryParseBid128(fields[2], out UInt128 x) && TryParseBid128(fields[3], out UInt128 y) && TryParseBid128(fields[4], out UInt128 z) && TryParseBid128(fields[5], out UInt128 expected)) + { + yield return new object[] { x, y, z, expected }; + } + } + } + // For `bidNN_from_` the integer source type is the trailing token; for `bidNN_to__int` it is the // third underscore-separated token; for the binary and cross families it is the leading or third token. private static string IntegerSourceType(string operation) => operation.Substring(operation.LastIndexOf('_') + 1); @@ -639,6 +989,29 @@ private static IEnumerable EnumerateRows(HashSet operations) private static string OperationSuffix(string operation) => operation.Substring(operation.IndexOf('_') + 1); + // The scaling amount is a plain decimal integer; Intel occasionally writes it float-style (for example + // `1.0`), which is accepted only when the fractional part is entirely zero. + private static bool TryParseScaleAmount(string token, out int value) + { + int dot = token.IndexOf('.'); + + if (dot >= 0) + { + for (int i = dot + 1; i < token.Length; i++) + { + if (token[i] != '0') + { + value = 0; + return false; + } + } + + token = token.Substring(0, dot); + } + + return int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out value); + } + private static bool TryParseComparisonResult(string token, out bool value) { switch (token)