Document: WG14 N1271


fpclassify() with binary and decimal FP


Submitter: Fred Tydeman (USA)
Submission Date: 2007-10-10
Source: WG14
Version: 1.0
Date: 2007-10-10
Subject: fpclassify() with binary and decimal FP

Problem

An implementation that supports both binary and decimal IEEE-754 floating-point (FP) has problems with the FP classification macros, the FP comparison macros, and possibly the <tgmath.h> functions.

C99 in section 7.12.3.1 has a possible definition of fpclassify() macro:

#define fpclassify(x) \
((sizeof (x) == sizeof (float)) ? __fpclassifyf(x) : \
(sizeof (x) == sizeof (double)) ? __fpclassifyd(x) : \
__fpclassifyl(x))

Extending this to work with both binary FP and decimal FP has problems. It will not work if sizeof(float) == sizeof(_Decimal32) or if sizeof(double) == sizeof(_Decimal64); both of which are true for the common 32-bit based implementations.

The rationale for the decimal FP paper (N1242) says (in 9.3) this problem is solved with compiler magic. That gives no help to third party library vendors who wish to write portable code that will be usable with multiple compilers. That section also mentions the idea of adding a language operator like type_of or radix_of.

Discussion

Since types are not known during the preprocessor phase of translation, a preprocessor based solution will not work.

gcc and EDG have a typeof() macro that returns a string that can be used to create source code with. For example,

typeof(x) y = x;

declares y to be of the same type as x and initializes y with the value of x.

It is not clear how one uses this facility to help with fpclassify(). If the string returned by this macro could be compared, e.g., strcmp(typeof(x),typeof(double)), then it could be used.

EDG has several builtin macros for working with type-generic function macros. One is for a mix of 3 float and 3 complex types. Another is for a mix of 12 fixed point types. They could add one for a mix of 3 binary float and 3 decimal float types. I believe that this is how one of their macros is used:

#define fpclassify(x) \
__generic(x,,, __fpclassifyd(x), __fpclassifyf(x), __fpclassifyl(x),,,)(x)

A portable compiler magic solution is needed. Below are two possible type_of solutions, followed by a radix_of solution.

A type_of solution is a translation time function that returns different integers for different type specifiers. It needs to work with expressions and should also work for types.

void, bit-fields, struct, union, arrays, pointers and functions need not be supported.

Type qualifiers (const, volatile and restrict) should be ignored.

Storage-class specifiers, such as register, should be ignored.

The following solution hides actual return values, but requires that the function work with both expressions and types. Since the actual return values are not documented, this makes it easy for an implementation to extend with other types. It also has the least name space pollution.

#define fpclassify(x) \
((type_of(x) == type_of(float)) ? __fpclassf(x) : \
(type_of(x) == type_of(double)) ? __fpclassd(x) : \
(type_of(x) == type_of(long double)) ? __fpclassl(x) : \
(type_of(x) == type_of(_Decimal32)) ? __fpclassf32(x) : \
(type_of(x) == type_of(_Decimal64)) ? __fpclassd64(x) : \
(type_of(x) == type_of(_Decimal128)) ? __fpclassl128(x) : \
__fpclass_bad(x))

The following solution works with just expressions, but requires that the return values have names. These names could be either preprocessor symbols or enums.

#define fpclassify(x) \
((type_of(x) == TYPE_float) ? __fpclassf(x) : \
(type_of(x) == TYPE_double) ? __fpclassd(x) : \
(type_of(x) == TYPE_long_double) ? __fpclassl(x) : \
(type_of(x) == TYPE_Decimal32) ? __fpclassf32(x) : \
(type_of(x) == TYPE_Decimal64) ? __fpclassd64(x) : \
(type_of(x) == TYPE_Decimal128) ? __fpclassl128(x) : \
__fpclass_bad(x))


The idea of radix_of would need to be combined with sizeof in the fpclassify() macro. It seems like using both would be make for a more verbose fpclassify(). Having more operations to determine the function to call might also make it harder to do constant folding (so as to reduce the code down to zero runtime tests and a single function call).

#define fpclassify(x) \
(((2==radix_of(x)) && (sizeof(x)==sizeof(float))) ? __fpclassf(x) : \
((2==radix_of(x)) && (sizeof(x) == sizeof(double))) ? __fpclassd(x) : \
((2==radix_of(x)) && (sizeof(x) == sizeof(long double))) ? __fpclassl(x) : \
((10==radix_of(x)) && (sizeof(x) == sizeof(_Decimal32))) ? __fpclassf32(x) : \
((10==radix_of(x)) && (sizeof(x) == sizeof(_Decimal64))) ? __fpclassd64(x) : \
((10==radix_of(x)) && (sizeof(x) == sizeof(_Decimal128))) ? __fpclassl128(x) : \
__fpclass_bad(x))

Thinking very long term, we may need a facility that will be able to work with ( binary, decimal ) x ( float, double, long double ) x ( real, complex, imaginary ), or 18 types. To make matters worse, a two argument function like pow(x,y), would have 18*18, or 324, combinations to deal with. This is not a problem now, since the decimal FP work is not suggesting any support for complex or imaginary, and prohibits mixing binary FP with decimal FP.

Suggested change for C1x