1

IEEE 754-2008:

7.5 Underflow

The underflow exception shall be signaled when a tiny non-zero result is detected. For binary formats, this shall be either:

a) after rounding — when a non-zero result computed as though the exponent range were unbounded would lie strictly between ±bemin, or

b) before rounding — when a non-zero result computed as though both the exponent range and the precision were unbounded would lie strictly between ±bemin.

The implementer shall choose how tininess is detected, but shall detect tininess in the same way for all operations in radix two, including conversion operations under a binary rounding attribute.

However, both C11 and C17..C2x (working draft — February 5, 2020, n2479.pdf) say nothing about tininess:

$ pdfgrep.exe -i 'tininess' ISO-IEC-9899-2011.pdf n2479.pdf --color never
<nothing>

Confused.

Question: why there is no FLT_TININESS macro (-1 -- indeterminable, 0 -- after rounding, 1 -- before rounding)?

UPD. Reason of the question: as usual: some FP tests (which test the correctness of results generated by FP operations) are failed, because expected raised exception is FE_INEXACT and actual raised exceptions are FE_INEXACT and FE_UNDERFLOW. Then it turned out that the HW determines tininess before rounding. Hence, the logical question appears: "how to determine whether tininess is detected before rounding or after rounding or indeterminable?". Since it is not possible to determine it in compile time, there is need to determine it in run time.

pmor
  • 5,392
  • 4
  • 17
  • 36
  • 2
    Neither IEEE 754 nor the C standard provide a way to explicitly report whether tininess is detected before or after rounding. You can test whether it is detected before or after rounding by executing an operation that would report underflow if tininess is detected before rounding but not after and testing whether it reports underflow. – Eric Postpischil Apr 20 '21 at 11:27
  • @EricPostpischil Thanks for the quick reply. The similar story as with `HAS_SUBNORM is -1` case: there is need for two reference values: one for `HAS_SUBNORM is 1` case, one for `HAS_SUBNORM is 0` case. Test is failed if `not equal to both of these reference values`. – pmor Apr 20 '21 at 11:37
  • @EricPostpischil Any idea / guess why _neither IEEE 754 nor the C standard provide a way to explicitly report whether tininess is detected before or after rounding_? Will `FLT_TININESS` be useful? – pmor Apr 20 '21 at 11:39
  • @pmor Why do you need to know? – Support Ukraine Apr 20 '21 at 12:12
  • 1
    I doubt there is evidence in the record as to why nobody chose to provide a report for this. Likely nobody thought it valuable. That part of this post is not a good question for Stack Overflow. – Eric Postpischil Apr 20 '21 at 12:32
  • 2
    As to finding a test case, it is easy if you can rely on the behavior of C’s `fma` “function”; just evaluate `fma(a, b, c)` where c is the smallest normal and `a` and `b` are numbers of opposite sign whose product is below the smallest representable non-zero value in magnitude. E.g., `a` could be the smallest positive representable number and `b` could be −¼. The mathematical result is subnormal, but it rounds to normal. – Eric Postpischil Apr 20 '21 at 12:36
  • @4386427 _Why do you need to know?_: added UPD. – pmor Apr 20 '21 at 13:27
  • @pmor Knowing the problem that people really want to solve can give you ideas about alternative approaches – Support Ukraine Apr 20 '21 at 13:40
  • pmor, I see "tiny" as being non-zero and 1) sub-normal, 2) and maybe the smallest non-zero normal. You may find [Can the floating-point status flag FE_UNDERFLOW set when the result is not sub-normal?](https://stackoverflow.com/q/46611633/2410359) useful. – chux - Reinstate Monica Apr 20 '21 at 13:57
  • @chux-ReinstateMonica Yes, I see it the same way! _the smallest non-zero normal_ seems to be `XXX_MIN`. Thanks for the link -- useful! – pmor Apr 20 '21 at 15:49
  • @chux-ReinstateMonica Is _being non-zero_ redundant here? Does `XXX_MIN` imply _non-zero_? – pmor Apr 20 '21 at 16:06
  • @chux-ReinstateMonica Note that by definition `FP_ZERO` and `FP_NORMAL` are _mutually exclusive kinds of floating-point values_. – pmor Apr 20 '21 at 16:14
  • @chux-ReinstateMonica If `clang / gcc` uses `after rounding`, then why `0x3f7fffff [FP_NORMAL] * 0x00800000 [FP_NORMAL]` leads to `FE_INEXACT and FE_UNDERFLOW` instead of `FE_INEXACT` while producing correct result `0x00800000 [FP_NORMAL]`? Can you check please? – pmor Apr 20 '21 at 17:23
  • @pmor Because setting both FE_INEXACT and FE_UNDERFLOW are allowed per "a) after rounding — when a non-zero result computed as though the exponent range were unbounded would lie strictly between ±bemin," – chux - Reinstate Monica Apr 20 '21 at 17:31
  • @chux-ReinstateMonica Thanks! It turns out that SW FP impl., which is under test, uses `after rounding` but may not produce `FE_UNDERFLOW` in some cases. Have to fix. – pmor Apr 20 '21 at 18:14
  • 1
    @pmor If those "some cases" are [exact](https://stackoverflow.com/questions/42277132/when-does-underflow-occur/42278622#42278622), then no FE_UNDERFLOW – chux - Reinstate Monica Apr 20 '21 at 18:24

1 Answers1

1

The following program may determine whether tininess is reported before or after rounding.

#include <fenv.h>
#include <float.h>


_Static_assert(FLT_RADIX == 2, "This program expects binary floating-point.");

typedef float Float;
enum {  //  Change FLT prefix according to type set for Float, above.
    Precision       = FLT_MANT_DIG,     // Number of bits in significand.
    MinimumExponent = FLT_MIN_EXP-1,    // Minimum normal exponent.
        /*  The -1 is due to C's definition of floating-point exponents being
            for significands in [1/2, 1) instead of [1, 2).
        */
};


// Use the following if your compiler supports it.  Not all do.
//#pragma STDC FENV_ACCESS ON


//  Report true iff a*b reports underflow.
static _Bool ProductUnderflows(Float a, Float b)
{
    feclearexcept(FE_ALL_EXCEPT);
    volatile Float c;
    c = a*b;
    return fetestexcept(FE_UNDERFLOW);
}


#include <math.h>
#include <stdio.h>
#include <stdlib.h>


int main(void)
{
    if (fesetround(FE_TONEAREST) != 0)
    {
        fprintf(stderr, "Error, cannot set rounding mode to nearest.\n");
        exit(EXIT_FAILURE);
    }

    /*  Find the least positive integer that does not divide the number of bits
        in a significand (also called p or the precision of the type).
    */
    int q = 1;
    while (Precision % q == 0)
        ++q;

    //  Set a to a string of q bits after the radix point.
    Float a = 1 - ldexp(1, -q);

    /*  Consider 1/a.  This necessarily rounds down and sets b to a repeating
        pattern of a 1 bit followed by q-1 0 bits.

        To see that it rounds down, consider the binary representation of the
        mathematical quotient 1/a.  It is a repeating pattern of a 1 bit
        followed by q-1 0 bits.  So the 1 bits land at offsets from the first 1
        bit of q, 2q, 3q, and so on.  So they only land at multiples of q.  And
        we know p is not a multiple of q, so there is no 1 bit at the position
        p bits beyond the leading bit.  In other words, the first bit that is
        does not fit in the p-bit significand is 0. So the residue being
        discarded during rounding is less than 1/2 ULP, so round-to-nearest
        rounds down.

        We set b to 1/a scaled so that a*b is just below the normal range.

        Then the mathematical product of a and b has a significand of
        ceil(p/q)*q 1 bits, which is greater than p, so the product must be
        rounded to fit in a signifcand.  In round-to-nearest-ties-to-even mode,
        it will round upward, so the floating-point product of a and b will be
        the smallest normal number.  Therefore, there is an underflow if
        tininess is detected before rounding but not if it is detected after
        rounding.
    */
    Float b = ldexp(1/a, MinimumExponent);

    printf("a = %a.\n", a);
    printf("b = %a.\n", b);

    /*  Test that we hit the boundary correctly:  (a/2)*b underflows but
        (2*a)*b does not.  Also test that underflow reporting works.
    */
    if (!ProductUnderflows(a/2, b))
    {
        fprintf(stderr,
"Internal error, %a * %a -> %a is expected to underflow but did not.\n",
            a/2, b, (a/2)*b);
        exit(EXIT_FAILURE);
    }
    if (ProductUnderflows(2*a, b))
    {
        fprintf(stderr,
"Internal error, %a * %a -> %a is expected not to underflow but did.\n",
            2*a, b, (2*a)*b);
        exit(EXIT_FAILURE);
    }

    //  Test whether tininess is detected before or after rounding.
    printf("Tininess is detected %s rounding.\n",
        ProductUnderflows(a, b) ? "before" : "after");
}
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • FYI: on aarch64 with clang 10.0.0 this program compiled as `clang t11.c -O3 -std=c11 -lm -Wall -Wextra -pedantic -ffp-model=strict` and executing as `./a.out` prints (among others) `Tininess is detected before rounding`. Despite the fact that clang 10.0.0 does not support `pragma STDC FENV_ACCESS ON`, the FP operations do raise FP exceptions (test program with `1.0f / 0.0f` leads to raising of `FE_DIVBYZERO`). The reason of "before rounding" is because Arm `FPRoundBase` uses "before rounding" if `FPCR.AH != '1'` (AH stands for Alternate Handling). – pmor Jun 06 '23 at 14:26
  • The "Alternate Handling" is relevant when `FEAT_AFP` is implemented. In my case `FEAT_AFP` is not implemented: `cat /proc/cpuinfo | grep Features | grep afp` prints nothing. The `__builtin_aarch64_get_fpcr()` returns 0. Meaning that `FPCR.AH` is 0. Now the core question: _why_ Arm decided to use "tininess after rounding" if "Annex U of 754r recommended that only tininess [**after rounding**](https://en.wikipedia.org/wiki/IEEE_754-2008_revision) and inexact as loss of accuracy be a cause for underflow signal."? – pmor Jun 06 '23 at 14:30
  • Asked a [dedicated question](https://stackoverflow.com/q/76415860/1778275). – pmor Jun 07 '23 at 13:26