clanguage-lawyerc23

Existence or not of long_double_t (standard C: C23)


The new standard C (ISO/IEC 9899:2023, aka C23),
in the Annex H, several macros and types are defined,
relative to real and complex arithmetic,
subject to the standard ISO/IEC 60559 (floating-point arithmetic).

According to subsection H.1, there are three
implementation-defined macros to test conformance
with the definitions of Annex H.

__STDC_IEC_60559_TYPES__  
__STDC_IEC_60559_BFP__  
__STDC_IEC_60559_DFP__  

In addition, to "activate" the associated macros and types,
the user has to define the macro:

__STDC_WANT_IEC_60559_TYPES_EXT__  

The wording of the standard is clear in several details:

In subsection H.11, the type long_double_t is added to <math.h>.

However, I cannot deduce under which conditions one can ensure the existence of long_double_t in a given implementation.

QUESTION:

If __STDC_IEC_60559_TYPES__ is defined, is it enough to be sure that long_double_t is also defined in <math.h>?

My doubt comes from the following particular case that could arise:

In this case, decimal types are defined.
However, long_double_t is not a decimal type.

SUB-QUESTION:
Is the macro __STDC_IEC_60559_BFP__ required to exist in order to ensure existence of long_double_t?

COMMENTS:

As @John Bollinger explains in his answer,
the existence of *BFP__ and/or *DFP__ macros
only ensures that _FloatN and _DecimalN exist for certain small values of N.
(I was aware of that detail, but my explanation was not the best.)

Link to a standard draft of C23


Solution

  • TL;DR: the minimum basis for relying on a conforming implementation to provide long_double_t is that it defines macro __STDC_IEC_60559_TYPES__ to 202311L.

    However, the translation unit must also define __STDC_WANT_IEC_60559_TYPES_EXT__ (to anything) before including math.h for the first time for that definition to actually be exposed. (C23 H.8/1)

    The wording of the standard is clear in several details:

    Apparently not so clear.

    As a preliminary matter, the spec says

    An implementation that defines __STDC_IEC_60559_TYPES__ to 202311L shall conform to the specifications in this annex.

    This must be understood to relieve implementations from conforming to anything in the annex if they do not define __STDC_IEC_60559_TYPES__ to that specific value (even if they define it to some other value). This is an intentional forward-compatibility measure.

    The spec goes on to say:

    An implementation may define __STDC_IEC_60559_TYPES__ only if it defines __STDC_IEC_60559_BFP__ [...] or defines __STDC_IEC_60559_DFP__

    Note well that per the previous, neither that provision nor anything else in the annex applies unless __STDC_IEC_60559_TYPES__ is defined to exactly 202311L. Otherwise, all bets are off as far as Annex H (of C23) is concerned. For example, an implementation that defined __STDC_IEC_60559_TYPES__ to nothing, and did not define either __STDC_IEC_60559_BFP__ or __STDC_IEC_60559_DFP__, would not for that reason fail to conform.

    • If __STDC_IEC_60559_BFP__ is defined by the compiler, it implies that _FloatN, _FloatN_t and other _FloatN* types are defined.

    Not exactly. The spec does not associate any requirements with an implementation defining __STDC_IEC_60559_BFP__ alone. In addition to the general gating by definition of __STDC_IEC_60559_TYPES__, discussed above, the spec explicitly conditions some of the particular provisions in this area on the latter macro being defined.

    When all those conditions are met, a conforming implementation provides types _Float32 and _Float64, and those are equivalent to float and double, respectively. Also, under those circumstances, if long double corresponds to one of the ISO 60559 binary exchange formats for some width N greater than 64, then the implementation provides the corresponding _FloatN type as an equivalent to long double. Other _FloatN types are allowed, but not required, and the set of them is implementation-defined. (H.2.1/4)

    A conforming implementation that binds itself to the provisions of this annex provides _FloatN_t in its math.h for each _FloatN type it provides.

    • If __STDC_IEC_60559_DFP__ is defined by the compiler, it implies that _DecimalN, _DecimalN_t and other _DecimalN* types are defined.

    Again, implementations are not bound to anything in the annex unless they define __STDC_IEC_60559_TYPES__ appropriately. In that case, __STDC_IEC_60559_DFP__ being defined implies that _Decimal32, _Decimal64, and _Decimal128 are provided. Additional _DecimalN types are explicitly allowed, and that the set of such additional types is implementation defined.

    When the provisions of the annex apply at all, math.h will provide a _DecimalN_t corresponding to each _DecimalN that the implementation defines.

    • In either case, the macro __STDC_IEC_60559_TYPES__ is defined.

    No, there is no such requirement. The logic is the other way around. As already discussed, none of Annex H applies unless __STDC_IEC_60559_TYPES__ is defined appropriately. If it is, then at least one of the other two macros must also be defined, but one of those being defined is not a basis for concluding anything about __STDC_IEC_60559_TYPES__.

    • If the macro __STDC_IEC_60559_TYPES__ is defined, it means that at least one of the macros __STDC_IEC_60559_BFP__ or __STDC_IEC_60559_DFP__ (or both) is defined.

    More specifically, this applies only if __STDC_IEC_60559_TYPES__ is defined to exactly 202311L.


    If __STDC_IEC_60559_TYPES__ is defined, is it enough to be sure that long_double_t is also defined in <math.h>?

    Macro __STDC_IEC_60559_TYPES__ being defined to exactly 202311L is enough to be sure that long_double_t is available from the implementation. The annex does not place any additional conditions on that. The program does need to opt in, however. C23 H.8/1:

    The identifiers added to library headers by this annex are defined or declared by their respective headers only if the macro __STDC_WANT_IEC_60559_TYPES_EXT__ is defined (by the user) at the point in the code where the appropriate header is first included.

    You wrote:

    My doubt comes from the following particular case that could arise:

    • __STDC_IEC_60559_TYPES__ and __STDC_IEC_60559_DFP__ are defined, but __STDC_IEC_60559_BFP__ is not defined.

    In this case, decimal types are defined.

    True.

    However, long_double_t is not a decimal type.

    Who says? C23 6.2.6.1/1:

    The representations of all types are unspecified except as stated in this subclause.

    That subclause does not define the representation of any FP types.

    There is no requirement that either long double or long_double_t be a binary type. There seems not even to be a requirement that those two types be equivalent, though I would expect that as a quality of implementation matter.

    But there is no problem even if long_double_t is a binary type. __STDC_IEC_60559_BFP__ not being defined does not imply that the implementation does not provide any binary FP types. It means only that the implementation declines to promise that it conforms to all the provisions of Annex H with regard to binary FP types.

    Is the macro __STDC_IEC_60559_BFP__ required to exist in order to ensure existence of long_double_t?

    No. H.11/6 specifies that long_double_t is available from math.h, subject to program opt-in as discussed above, and that is not otherwise conditioned on anything other than the same __STDC_IEC_60559_TYPES__ feature test macro on which everything in the annex is conditioned. Thereore, if a conforming implementation defines __STDC_IEC_60559_TYPES__ to 202311L then its math.h defines long_double_t to those programs that opt in.