Runtime: Floating-Point Classification APIs

Created on 8 Mar 2017  路  14Comments  路  Source: dotnet/runtime

Rationale

The .NET framework does not currently provide support for classifying various floating-point types other than Infinity and NAN.

Efficiently classifying floating-point values can be very useful to optimize various code paths when dealing with numbers that are outside the normal input range.

Proposed API

``` C#
public struct Double
{
///

Determines whether the specified value is finite (zero, subnormal, or normal and not infinite or NaN).
public static bool IsFinite(double value);

// IsInfinity is already provided

// IsNaN is already provided

/// <summary>Determines whether the specified value is normal (not zero, subnormal, infinite, or NaN).</summary>
public static bool IsNormal(double value);

/// <summary>Determines whether the specified value is negative.</summary>
public static bool IsNegative(double value);

/// <summary>Determines whether the specified value is subnormal.</summary>
public static bool IsSubnormal(double value);

}

public struct Single
{
///

Determines whether the specified value is finite (zero, subnormal, or normal and not infinite or NaN).
public static bool IsFinite(float value);

// IsInfinity is already provided

// IsNaN is already provided

/// <summary>Determines whether the specified value is normal (not zero, subnormal, infinite, or NaN).</summary>
public static bool IsNormal(float value);

/// <summary>Determines whether the specified value is negative.</summary>
public static bool IsNegative(float value);

/// <summary>Determines whether the specified value is subnormal.</summary>
public static bool IsSubnormal(float value);

}
```

api-approved area-System.Numerics

All 14 comments

FYI. @mellinoe

C# Subnormal = -2, Normal = -1, Zero = 0, Infinite = 1, NaN = 2

Are these values significant in any way? It sticks out as odd / significant that some are negative.

The MS CRT implementation has them configured like that so you can say:
"Anything less than or equal to 0 is finite, and anything greater than 0 is not finite."

Also so you can say "0" is "0".

I updated the proposal above with an implementation. There is probably a trick to easily determining if a number is normal (rather than calling into Classify), but I don't recall what it is off the top of my hand.

/cc @dcwuser 's area of expertise.

IsFinite

Yes, we need this. It's useful all over the place, for example testing for "is neither NaN nor infinite" without having to make two separate calls.

IsSigned

We need this, for example to test for negative zero. But the proposed name is confusing. Positive numbers also have a sign. "IsNegative" would be a better name. The only objection I can think of that that one might not want to think of a NaN with the sign bit set as negative, but I don't find that very compelling. We talk about "negative zero", not "signed zero", and I suspect that anyone who wanted to talk about this distinction would quickly adopt the analogous "negative NaN".

IsNormal

This has fewer use cases than IsFinite and IsNegative, but is still nice-to-have. I would slightly prefer the opposite IsSubnormal, though. One reason is that "IsNormal" might well be confused by non-IEEE-754 experts for "IsFinite". Another reason is that sub-normality is more likely what one is testing for.

The definition of sub-normality is that all exponent bits are 0 and the leading mantissa bit is also 0. For a typical FP number, the leading mantissa bit is 1 because if it were not 1 you could just shift the exponent until it is one. But for sub-normals, you have run out of exponent shift.

The smallest normal number is therefore represented by 0x000800000000000 and the largest subnormal by 0x0007FFFFFFFFFFF. You can test for subnormality by testing whether (zero-index) bits 1-12 are all 0, i.e. u & 0x7FF800000000000 == 0.

Note by this definition 0.0 is sub-normal. That's a debatable convention. I don't recall whether the IEEE-754 spec makes a ruling on this point, but if you want to exclude 0.0, just do an additional u != 0 test.

Classify

I vote against this part of the proposal.

In typical practical use cases, one needs to branch based on a specific property: is the number negative?, is it NaN? is its absolute value less than 4.0?, etc. So individual-property tests are useful. The only practical case I can think of where this particular classification is useful is if one needs specific handing for precisely these distinctions; in that case one could call Classify and switch on the result.

But "precisely these distinctions" is pretty unusual. What if you need to handle positives and negatives differently? Then this classification scheme is not useful. What if you don't need to distinguish normals from sub-normals? Then this classification scheme is just adding unneeded complexity.

Various classification schemes will be useful for particular problems. But when you elevate one particular classification scheme to the definitive, API-endorsed, universal classification scheme, that API is likely to have too much complexity and too little use.

I definitely agree that IsNegative is better than IsSigned and have updated the proposal.

This has fewer use cases than IsFinite and IsNegative, but is still nice-to-have. I would slightly prefer the opposite IsSubnormal, though. One reason is that "IsNormal" might well be confused by non-IEEE-754 experts for "IsFinite". Another reason is that sub-normality is more likely what one is testing for.

I agree here as well. I went with IsNormal for parity with what is provided in C/C++. I believe it will be more familiar to the people who will actually make use of the function and the summary of the method can easily explain the use-case here. However, I think providing both an IsNormal and IsSubnormal method might be acceptable here (given that the code is so simple).

Classify

The way it is constructed, you can easily check just for what you want. For example: A finite number is an enum value less than or equal to zero, while a non-finite number is classified as an enum value greater than 0.

While it likely won't get used as much as IsFinite (which I expect to be the most used function by far), it does provide a single check that can then be optimized to any individual group you need (and does so in only 2-3 comparisons).

Is there any further feedback here or can this get moved to API Ready For Review?

No more feedback from me.

@mellinoe, any chance you could mark this as API Ready For Review (or tag the appropriate people)?

@tannergooding Done; we can try to get this into the next design meeting.

We just took a look. It seems the APIs are valuable and we should expose them. We prefer the static IsXxx methods over the enum because logistically the enum would have to live side by side with Single and Double so it would add yet another type in the System namespace that very few people would ever to work with.

So let's just start with the methods:

```C#
struct Single
{
// IsInfinity is already provided
// IsNaN is already provided

public static bool IsNormal(float value);
public static bool IsSubnormal(float value);
public static bool IsNegative(float value);
public static bool IsFinite(float value);

}

struct Double
{
// IsInfinity is already provided
// IsNaN is already provided

public static bool IsNormal(double value);
public static bool IsSubnormal(double value);
public static bool IsNegative(double value);
public static bool IsFinite(double value);

}
```

@tannergooding Please update the issue to match this proposal.

Updated the proposed API to match the approved API.

The API surface that was not approved is:
```C#
public enum NumberClassification // FloatingPointClass is another idea for the name
{
Subnormal = -2,
Normal = -1,
Zero = 0,
Infinite = 1,
NaN = 2
}

///

Classifies the specified value as NaN, infinite, normal, subnormal, or zero.
public static NumberClassification Double.Classify(double value);

///

Classifies the specified value as NaN, infinite, normal, subnormal, or zero.
public static NumberClassification Single.Classify(float value);
```

Tests were added here: https://github.com/dotnet/corefx/pull/22490

Everything should be good now.

Was this page helpful?
0 / 5 - 0 ratings