Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
skyoxZ opened this issue Feb 12, 2024 · 7 comments · Fixed by #98510
Closed

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

skyoxZ opened this issue Feb 12, 2024 · 7 comments · Fixed by #98510
Labels
area-System.Numerics in-pr There is an active PR which will close this issue when it is merged

Comments

@skyoxZ
Copy link
Contributor

skyoxZ commented Feb 12, 2024

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)

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Feb 12, 2024
@ghost
Copy link

ghost commented Feb 12, 2024

Tagging subscribers to this area: @dotnet/area-system-numerics
See info in area-owners.md if you want to be subscribed.

Issue Details
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)

Author: skyoxZ
Assignees: -
Labels:

area-System.Numerics, untriaged

Milestone: -

@stephentoub
Copy link
Member

Can you please share a minimal repro showing the problem?

@stephentoub stephentoub added the needs-author-action An issue or pull request that requires more info or actions from the author. label Feb 12, 2024
@ghost
Copy link

ghost commented Feb 12, 2024

This issue has been marked needs-author-action and may be missing some important information.

@skyoxZ
Copy link
Contributor Author

skyoxZ commented Feb 13, 2024

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;
}

@ghost ghost removed the needs-author-action An issue or pull request that requires more info or actions from the author. label Feb 13, 2024
@stephentoub
Copy link
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;

?

@stephentoub
Copy link
Member

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

@skyoxZ
Copy link
Contributor Author

skyoxZ commented Feb 13, 2024

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;

@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Feb 15, 2024
@dotnet-policy-service dotnet-policy-service bot removed the untriaged New issue has not been triaged by the area owner label Mar 19, 2024
@github-actions github-actions bot locked and limited conversation to collaborators Apr 18, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Numerics in-pr There is an active PR which will close this issue when it is merged
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants