[Cfp-interest] constant rounding modes and type-generic macros

Joel C. Salomon joelcsalomon at gmail.com
Thu Apr 19 14:25:25 PDT 2012


On 04/07/2012 08:26 PM, Jim Thomas wrote:
> This is intended as a summary of discussion the past few weeks about how
> constant rounding modes affect functions invoked by type-generic macros
> in <tgmath.h>. We'll discuss and try to wrap up this issue at our April
> meeting. 

I’ve been considering how it may be possible to implement the
constant-rounding-mode magic required in CFP1’s Table 2. We briefly
discussed the issue at today’s phone conference, but it’s not yet ready
to be wrapped up. Here are my thoughts on the matter.

There are two sorts of implementations we need to discuss: those with
true static constant rounding modes (one of which is "dynamic"), and
those with only dynamic modes, where 754-2008’s constant modes must be
emulated.

On either sort of system, it is necessary that the compiler know that
the functions in Table 2 are “operations affected by constant rounding
mode”—but this compiler knowledge must be forgotten if macro replacement
has been suppressed.

For the purposes of argument, I will propose syntax used by a
hypothetical compiler:
• The __constrndfunc operator, when applied to a function
  type, yields the same function but with compiler knowing
  that it is affected by constant rounding mode.
• The __constrndmode identifier behaves as a constant
  expression, yielding the current static rounding mode if
  one is in existence, FE_DYNAMIC otherwise.

The first feature will suffice on systems where the Table 2 functions
are implemented only once each, rounding according to a dynamic mode:

    // <math.h>
    /* these functions round according to the current (possibly
     * default) dynamic mode and are used when macro replacement
     * has been suppressed
     */
    float cbrtf(float);
    double cbrt(double);

    #define cbrtf(x) __constrndfunc cbrtf(x)
    #define cbrt(x)  __constrndfunc cbrt(x)

    // <tgmath.h>
    #undef cbrt
    #define cbrt(x) _Generic((x),       \
        float:   __constrndfunc cbrtf,  \
        default: __constrndfunc cbrt    \
        )(x)

The second feature is intended for systems where Table 2 functions have
multiple implementations, for each rounding mode:

    // <math.h>
    /* these functions round according to the current (possibly
     * default) dynamic mode and are used when macro replacement
     * has been suppressed
     */
    float cbrtf(float);
    double cbrt(double);

    float __cbrtf_downward(float),   __cbrtf_tonearest(float),
          __cbrtf_towardzero(float), __cbrtf_upward(float);
    double __cbrt_downward(double),   __cbrt_tonearest(double),
           __cbrt_towardzero(double), __cbrt_upward(double);

    #define __constrndfunc(func) ( \
        __constrndmode == FE_DYNAMIC    ? func                  : \
        __constrndmode == FE_DOWNWARD   ? __##func##_downward   : \
        __constrndmode == FE_TONEAREST  ? __##func##_tonearest  : \
        __constrndmode == FE_TOWARDZERO ? __##func##_towardzero : \
        __constrndmode == FE_UPWARD     ? __##func##_upward     : \
        0 )

    #define cbrtf(x) __constrndfunc(cbrtf)(x)
    #define cbrt(x)  __constrndfunc(cbrt)(x)

    // <tgmath.h>
    #undef cbrt
    #define cbrt(x) _Generic((x),       \
        float:   __constrndfunc(cbrtf), \
        default: __constrndfunc(cbrt)   \
        )(x)

This model makes the most sense to me, but I’m open to critique.

Jim, in his email, suggested that the current CFP spec does not actually
allow for this implementation; his reading has it that the float
selection-branch of the <tgmath.h> cbrt() macro should be defined in
terms of the cbrtf() macro; i.e. (per the first form):

    // <math.h> as above

    // <tgmath.h>
    #undef cbrt
    #define cbrt(x) _Generic((x),       \
        float:   cbrtf(x),  /* this */  \
        default: __constrndfunc cbrt(x) \
        )

or (per the second form):

    // <math.h>
    float cbrtf(float), __cbrtf_downward(float), __cbrtf_upward(float),
          __cbrtf_tonearest(float), __cbrtf_towardzero(float);
    double cbrt(double), __cbrt_downward(double), __cbrt_upward(double),
           __cbrt_tonearest(double), __cbrt_towardzero(double);

    #define __constrndfunc(func, x) ( \
        __constrndmode == FE_DYNAMIC    ? func(x)                 : \
        __constrndmode == FE_DOWNWARD   ? __##func##_downward(x)  : \
        __constrndmode == FE_TONEAREST  ? __##func##_tonearest(x) : \
        __constrndmode == FE_TOWARDZERO ? __##func##_towardzero(x): \
        __constrndmode == FE_UPWARD     ? __##func##_upward(x)    : \
        SNAN )

    #define cbrtf(x) __constrndfunc(cbrtf, (x))
    #define cbrt(x)  __constrndfunc(cbrt, (x))

    // <tgmath.h>
    #undef cbrt
    #define cbrt(x) _Generic((x),          \
        float:   cbrtf(x),  /* this */     \
        default: __constrndfunc(cbrt, (x)) \
        )

The effective difference between my version above and Jim’s here is that
if the user undefines `cbrtf`, suppressing that macro expansion,
`cbrt(some_double)` will round according to the current constant
rounding mode while `cbrt(some_float)` will round according to default
rules.

I’ve quoted Jim’s letter below in full so we can discuss whether his
reading of the CPF spec is correct, and which version is actually desired.

—Joel

> The current CFP spec (11) has:
> 
> ----------
> Within the scope of an *FENV_ROUND *directive establishing a mode other
> than *FE_DYNAMIC, *all floating-point operators and invocations of
> functions indicated in Table 2 below,for which macro replacement has not
> been suppressed (7.1.4), shall be evaluated according to the specified
> constant rounding mode (as though no constant mode was specified and the
> corresponding dynamic rounding mode had been established by a call to
> *fesetround*). Invocations of functions for which macro replacement has
> been suppressed and invocations of functions other than those indicated
> in Table 2 shall not be affected by constant rounding modes — they are
> affected by (and affect) only the dynamic mode. Floating constants
> (6.4.4.2) that occur in the scope of a constant rounding mode shall be
> interpreted according to that mode.
> ----------
> 
> and (16) adds the following to 7.25:
> 
> ----------
> A type-generic macro corresponding to a function indicated in Table 2 is
> affected by constant rounding modes (7.6.2).
> ----------
> 
> This implies that functions invoked by <tgmath.h> macros are affected
> unless macro replacement has been suppressed. Thus in
> 
> #define STDC_WANT_IEC_60559_BFP
> #include <tgmath.h>
> #include <fenv.h>
> double d;
> float f;
>> {
> #pragma STDC FENV_ROUND FE_UPWARD
>> // Case 1
> … cbrt(d) …
>> // Case 2
> … cbrt(f) …
>> // Case 3
> #undef cbrtf
> … cbrt(f) …
>> // Case 4
> #undef cbrt
> … cbrt(d) …
>> // Case 5
> … cbft(f) …
>> }
> 
> the current CFP spec says:
> 
> Case 1 - which invokes function cbrt shall be affected by pragma,
> because user has not suppressed macro expansion for cbrt. Note that
> <tgmath.h> may itself undef cbrt, so must take care to assure the call
> is affected
> 
> Case 2 - which invokes function cbrtf shall be affected by pragma,
> because user has not suppressed macro expansion for cbftf
> 
> Case 3 - which invokes function cbrtf shall not be affected by pragma,
> because user has suppressed macro expansion for cbrtf
> 
> Case 4 - which invokes cbrt (without help from suppressed <tgmath.h>
> macro) shall not be affected by pragma, because user suppressed macro
> expansion for cbrt
> 
> Case 5 - same as Case 4 but with argument converted to double
> 
> How does this requirement fit with C11? C11 leaves it unspecified
> whether functions invoked by <tgmath.h> macros are subject to macro
> replacement. The example in C11 6.5.1.1 provides a sample implementation
> for the <tgmath.h> cbrt macro using the _Generic macro. This
> implementation disables macro expansion for all the functions it
> invokes. Thus it does not give the CFP-specified behavior above in Cases
> 1 and 2. However, C11 does not say or imply that this sample
> implementation is definitive. 
> 
> The following modification of the sample implementation in 6.5.1.1 would
> give the CFP-specified behavior:
> 
>       #define cbrt(X) _Generic((X),                   \
>                                   long double: cbrtl(X),     \
>                                   default: __round_wise_cbrt(X),          \
>                                   float: cbrtf(X)
>                                  
> where __round_wise_cbrt is affected by the constant rounding mode pragma
> (like cbrt where <math.h> but not <tgmath.h> has been included).
> 
> Any concerns about what we've specified? Is the behavior good for users?
> Is it reasonable to implement? Does it fit well enough with C11?
> 
> -Jim


More information about the Cfp-interest mailing list