Skip to content

INumber.Min(+QNaN, 1) returns 1 #98285

Closed

Description

@skyoxZ
float qNaN = BitConverter.Int32BitsToSingle(0x7FC00000);
Console.WriteLine(float.Min(qNaN, 1));

Double/Single/Half.Min(+QNaN, 1) correctly returns NaN but INumber.Min not:

/// <summary>Compares two values to compute which is lesser.</summary>
/// <param name="x">The value to compare with <paramref name="y" />.</param>
/// <param name="y">The value to compare with <paramref name="x" />.</param>
/// <returns><paramref name="x" /> if it is less than <paramref name="y" />; otherwise, <paramref name="y" />.</returns>
/// <remarks>For <see cref="IFloatingPoint{TSelf}" /> this method matches the IEEE 754:2019 <c>minimum</c> function. This requires NaN inputs to be propagated back to the caller and for <c>-0.0</c> to be treated as less than <c>+0.0</c>.</remarks>
static virtual TSelf Min(TSelf x, TSelf y)
{
// This matches the IEEE 754:2019 `minimum` function
//
// It propagates NaN inputs back to the caller and
// otherwise returns the larger of the inputs. It
// treats +0 as larger than -0 as per the specification.
if ((x != y) && !TSelf.IsNaN(x))
{
return x < y ? x : y;
}
return TSelf.IsNegative(x) ? x : y;
}

(another issue: search .. in INumber.cs to find a redundant dot)

Activity

  1. ghost added
    untriagedNew issue has not been triaged by the area owner
    on Feb 12, 2024
  2. stephentoub commented on Feb 12, 2024

    @stephentoub
    Member

    Can you please share a minimal repro showing the problem?

  3. added
    needs-author-actionAn issue or pull request that requires more info or actions from the author.
    on Feb 12, 2024
  4. skyoxZ commented on Feb 13, 2024

    @skyoxZ
    ContributorAuthor

    The output should be NaN NaN but now it's NaN 1:

    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Numerics;
    
    float qNaN = BitConverter.Int32BitsToSingle(0x7FC00000);
    Console.WriteLine(new Calculator<float>().Min(qNaN, 1f));
    Console.WriteLine(new Calculator<NumberWrapper<float>>().Min(qNaN, 1f));
    
    class Calculator<T> where T : INumber<T>
    {
        public T Min(T x, T y) => T.Min(x, y);
    }
    
    struct NumberWrapper<T> : INumber<NumberWrapper<T>> where T : INumber<T>
    {
        public T Value;
    
        public NumberWrapper(T value) => Value = value;
        public static implicit operator NumberWrapper<T>(T value) => new NumberWrapper<T>(value);
        public static implicit operator T(NumberWrapper<T> value) => value.Value;
    
        public static NumberWrapper<T> One => T.One;
        public static int Radix => T.Radix;
        public static NumberWrapper<T> Zero => T.Zero;
        public static NumberWrapper<T> AdditiveIdentity => T.AdditiveIdentity;
        public static NumberWrapper<T> MultiplicativeIdentity => T.MultiplicativeIdentity;
    
        public static NumberWrapper<T> Abs(NumberWrapper<T> value) => T.Abs(value);
        public static bool IsCanonical(NumberWrapper<T> value) => T.IsCanonical(value);
        public static bool IsComplexNumber(NumberWrapper<T> value) => T.IsComplexNumber(value);
        public static bool IsEvenInteger(NumberWrapper<T> value) => T.IsEvenInteger(value);
        public static bool IsFinite(NumberWrapper<T> value) => T.IsFinite(value);
        public static bool IsImaginaryNumber(NumberWrapper<T> value) => T.IsImaginaryNumber(value);
        public static bool IsInfinity(NumberWrapper<T> value) => T.IsInfinity(value);
        public static bool IsInteger(NumberWrapper<T> value) => T.IsInteger(value);
        public static bool IsNaN(NumberWrapper<T> value) => T.IsNaN(value);
        public static bool IsNegative(NumberWrapper<T> value) => T.IsNegative(value);
        public static bool IsNegativeInfinity(NumberWrapper<T> value) => T.IsNegativeInfinity(value);
        public static bool IsNormal(NumberWrapper<T> value) => T.IsNormal(value);
        public static bool IsOddInteger(NumberWrapper<T> value) => T.IsOddInteger(value);
        public static bool IsPositive(NumberWrapper<T> value) => T.IsPositive(value);
        public static bool IsPositiveInfinity(NumberWrapper<T> value) => T.IsPositiveInfinity(value);
        public static bool IsRealNumber(NumberWrapper<T> value) => T.IsRealNumber(value);
        public static bool IsSubnormal(NumberWrapper<T> value) => T.IsSubnormal(value);
        public static bool IsZero(NumberWrapper<T> value) => T.IsZero(value);
        public static NumberWrapper<T> MaxMagnitude(NumberWrapper<T> x, NumberWrapper<T> y) => T.MaxMagnitude(x, y);
        public static NumberWrapper<T> MaxMagnitudeNumber(NumberWrapper<T> x, NumberWrapper<T> y) => T.MaxMagnitudeNumber(x, y);
        public static NumberWrapper<T> MinMagnitude(NumberWrapper<T> x, NumberWrapper<T> y) => T.MinMagnitude(x, y);
        public static NumberWrapper<T> MinMagnitudeNumber(NumberWrapper<T> x, NumberWrapper<T> y) => T.MinMagnitudeNumber(x, y);
        public static NumberWrapper<T> Parse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider) => T.Parse(s, style, provider);
        public static NumberWrapper<T> Parse(string s, NumberStyles style, IFormatProvider? provider) => T.Parse(s, style, provider);
        public static NumberWrapper<T> Parse(ReadOnlySpan<char> s, IFormatProvider? provider) => T.Parse(s, provider);
        public static NumberWrapper<T> Parse(string s, IFormatProvider? provider) => T.Parse(s, provider);
    
        public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out NumberWrapper<T> result)
        {
            var succeeded = T.TryParse(s, style, provider, out T actualResult);
            result = actualResult;
            return succeeded;
        }
        public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out NumberWrapper<T> result)
        {
            var succeeded = T.TryParse(s, style, provider, out T actualResult);
            result = actualResult;
            return succeeded;
        }
        public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, [MaybeNullWhen(false)] out NumberWrapper<T> result)
        {
            var succeeded = T.TryParse(s, provider, out T actualResult);
            result = actualResult;
            return succeeded;
        }
        public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out NumberWrapper<T> result)
        {
            var succeeded = T.TryParse(s, provider, out T actualResult);
            result = actualResult;
            return succeeded;
        }
        public int CompareTo(object? obj)
        {
            if (obj is not NumberWrapper<T> other)
            {
                return (obj is null) ? 1 : throw new ArgumentException();
            }
            return CompareTo(other);
        }
        public int CompareTo(NumberWrapper<T> other) => Value.CompareTo(other.Value);
        public override bool Equals([NotNullWhen(true)] object? obj) => (obj is NumberWrapper<T> other) && Equals(other);
        public bool Equals(NumberWrapper<T> other) => Value.Equals(other.Value);
        public override int GetHashCode() => Value.GetHashCode();
        public string ToString(string? format, IFormatProvider? formatProvider) => Value.ToString(format, formatProvider);
        public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) => Value.TryFormat(destination, out charsWritten, format, provider);
        
        static bool INumberBase<NumberWrapper<T>>.TryConvertFromChecked<TOther>(TOther value, out NumberWrapper<T> result)
        {
            bool succeeded = T.TryConvertFromChecked(value, out T actualResult);
            result = actualResult;
            return succeeded;
    
        }
        static bool INumberBase<NumberWrapper<T>>.TryConvertFromSaturating<TOther>(TOther value, out NumberWrapper<T> result)
        {
            bool succeeded = T.TryConvertFromSaturating(value, out T actualResult);
            result = actualResult;
            return succeeded;
    
        }
        static bool INumberBase<NumberWrapper<T>>.TryConvertFromTruncating<TOther>(TOther value, out NumberWrapper<T> result)
        {
            bool succeeded = T.TryConvertFromTruncating(value, out T actualResult);
            result = actualResult;
            return succeeded;
    
        }
        static bool INumberBase<NumberWrapper<T>>.TryConvertToChecked<TOther>(NumberWrapper<T> value, out TOther result) => T.TryConvertToChecked(value, out result);
        static bool INumberBase<NumberWrapper<T>>.TryConvertToSaturating<TOther>(NumberWrapper<T> value, out TOther result) => T.TryConvertToSaturating(value, out result);
        static bool INumberBase<NumberWrapper<T>>.TryConvertToTruncating<TOther>(NumberWrapper<T> value, out TOther result) => T.TryConvertToTruncating(value, out result);
    
        public static NumberWrapper<T> operator +(NumberWrapper<T> value) => +value.Value;
        public static NumberWrapper<T> operator +(NumberWrapper<T> left, NumberWrapper<T> right) => left.Value + right.Value;
        public static NumberWrapper<T> operator -(NumberWrapper<T> value) => -value.Value;
        public static NumberWrapper<T> operator -(NumberWrapper<T> left, NumberWrapper<T> right) => left.Value - right.Value;
        public static NumberWrapper<T> operator ++(NumberWrapper<T> value) => value.Value++;
        public static NumberWrapper<T> operator --(NumberWrapper<T> value) => value.Value--;
        public static NumberWrapper<T> operator *(NumberWrapper<T> left, NumberWrapper<T> right) => left.Value * right.Value;
        public static NumberWrapper<T> operator /(NumberWrapper<T> left, NumberWrapper<T> right) => left.Value / right.Value;
        public static NumberWrapper<T> operator %(NumberWrapper<T> left, NumberWrapper<T> right) => left.Value % right.Value;
        public static bool operator ==(NumberWrapper<T> left, NumberWrapper<T> right) => left.Value == right.Value;
        public static bool operator !=(NumberWrapper<T> left, NumberWrapper<T> right) => left.Value != right.Value;
        public static bool operator <(NumberWrapper<T> left, NumberWrapper<T> right) => left.Value > right.Value;
        public static bool operator >(NumberWrapper<T> left, NumberWrapper<T> right) => left.Value < right.Value;
        public static bool operator <=(NumberWrapper<T> left, NumberWrapper<T> right) => left.Value <= right.Value;
        public static bool operator >=(NumberWrapper<T> left, NumberWrapper<T> right) => left.Value >= right.Value;
    }
  5. ghost removed
    needs-author-actionAn issue or pull request that requires more info or actions from the author.
    on Feb 13, 2024
  6. stephentoub commented on Feb 13, 2024

    @stephentoub
    Member

    Did you purposefully implement < and > incorrectly?

        public static bool operator <(NumberWrapper<T> left, NumberWrapper<T> right) => left.Value > right.Value;
        public static bool operator >(NumberWrapper<T> left, NumberWrapper<T> right) => left.Value < right.Value;
    

    ?

  7. stephentoub commented on Feb 13, 2024

    @stephentoub
    Member

    But, yes, the Min implementation does look a bit off.

  8. skyoxZ commented on Feb 13, 2024

    @skyoxZ
    ContributorAuthor

    Did you purposefully implement < and > incorrectly?

        public static bool operator <(NumberWrapper<T> left, NumberWrapper<T> right) => left.Value > right.Value;
        public static bool operator >(NumberWrapper<T> left, NumberWrapper<T> right) => left.Value < right.Value;
    

    ?

    No, it's a mistake. Interesting, I copied them from

    public static bool operator <(BinaryIntegerWrapper<T> left, BinaryIntegerWrapper<T> right) => left.Value > right.Value;
    public static bool operator >(BinaryIntegerWrapper<T> left, BinaryIntegerWrapper<T> right) => left.Value < right.Value;

  9. ghost added
    in-prThere is an active PR which will close this issue when it is merged
    on Feb 15, 2024
  10. locked and limited conversation to collaborators on Apr 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-System.Numericsin-prThere is an active PR which will close this issue when it is merged

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions