4
\$\begingroup\$

I tried to implement printf family functions in C89, after I've read K&R book. The goals are three:

  1. Do not use dynamic memory allocation (like malloc);
  2. Write a modularized code;
  3. Write portable code without use machine dependent behaviour;
  4. Avoid undefined behaviour.

Performance is not a goal, I used naive algorithm with a lot of recursion. I also tried to follow a Linux-like coding style.

I tried to be as close as possible with C89, but my implementation doesn’t support all features:

  • d, i, o, x, X, u, c, s, n and % conversion specifier are fully supported with all flags and length modifier;
  • f, e, E, g and G are partially supported, they don't do rounding and L length modifier is not supported.
  • p is not supported (because it is machine dependent).

I had issues with floating point because there are not good tools in C89 to handle these numbers (for example to check the existence of NaN or inf).

Header eprintf.h:

#include <stdio.h>
#include <stdarg.h>

#ifndef E_PRINTF
#define E_PRINTF

int e_printf(char *format, ...);

int e_fprintf(FILE *stream, char *format, ...);

int e_sprintf(char *str, char *format, ...);

int e_vprintf( char *fomat, va_list ap);

int e_vfprintf(FILE *stream, char *format, va_list ap);

int e_vsprintf(char *str, char *format, va_list ap);

#endif /* E_PRINTF */

Implementation eprintf.c:

#include <assert.h>
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include <limits.h>
#include <stdlib.h>
#include <math.h> /* For printing floating points. */

#include "eprintf.h"

/*
 * Custom macros.
 */

#define E_PRINT_ERROR -1

#define E_DIVISOR_10_SIGN(value) (value < 0 ? -10 : 10)

#define E_FLOAT_DEFAULT_PRECISION 6

#define E_FLOAT_ISFINITE(value) (!(fabs(value) >= HUGE_VAL))

#define E_FLOAT_ISNAN(value) (value != value)

#define E_UNSIGNED_SYMBOL(data, base) (base == 8 ? "0" : \
                                      (data->fmt.flag.uppercase ? "0X" : "0x" ))

/*
 * Private structs.
 */

/*
 * Contains a full representation of a format string.
 */
struct e_print_format {
        struct {
                unsigned show_sign : 1; /* '+' */
                unsigned left_align : 1; /* '-' */
                unsigned initial_space : 1; /* ' ' */
                unsigned zero_pad : 1; /* '0' */
                unsigned alternative_output : 1; /* '#' */

                unsigned uppercase : 1; /* es. "INF" or "inf". */

                /*
                 * Print trailing zeros for fractions, it is false only with
                 * "%g" or "%G".
                 */
                unsigned trailing_zeros : 1;
        } flag;

        int field_width; /* 0 -> not present. */
        int precision; /* "-1" -> not present. */
        char length_modifier; /* "\0" or 0 -> not present. */

        char specifier; /* Conversion specifier. */
};

/*
 * Data related to output method (string or stream).
 */
struct e_print_output {
        enum { E_OUTPUT_STREAM, E_OUTPUT_STR, E_OUTPUT_NONE } mode;

        FILE *file;

        char *str;
        char *str_ptr;

        int chrs_printed; /* Number of chracters printed or written. */
};

/*
 * Container for all settings and format.
 */
struct e_print_data {
        struct e_print_output out;

        struct e_print_format fmt;
};

/*
 * Private functions implementation.
 */

/*
 * Common private functions used in other private functions.
 */

/*
 * e_reset_format: resets "fmt" with default values. Useful because "fmt" is
 * valid only inside a "%" specifier.
 */
static void e_reset_format(struct e_print_format *fmt)
{
        assert(fmt != NULL);

        fmt->flag.show_sign = fmt->flag.left_align = 0;
        fmt->flag.initial_space = fmt->flag.zero_pad = 0;
        fmt->flag.alternative_output = fmt->flag.uppercase = 0;
        fmt->field_width = 0;
        fmt->flag.trailing_zeros = 1;
        fmt->precision = -1;
        fmt->length_modifier = 0;
        fmt->specifier = 0;
}

/*
 * e_reset_data: resets "data" with default values.
 */
static void e_reset_data(struct e_print_data *data)
{
        assert(data != NULL);

        e_reset_format(&data->fmt);

        data->out.mode = E_OUTPUT_NONE;
        data->out.file = NULL;
        data->out.str = data->out.str_ptr = NULL;
        data->out.chrs_printed = 0;
}

/*
 * e_emit_str: prints a string "str" according to "data->out.mode" and returns
 * the length of the string, or "E_PRINT_ERROR" on error.
 *
 * Mode can be:
 *      * "E_OUTPUT_NONE": the string is not printed;
 *      * "E_OUTPUT_STREAM": the string is written on an open stream;
 *      * "E_OUTPUT_STR": the string is written on an array and then is
 *      added the null terminator "\0", make sure that the array is large
 *      enough!
 *
 */
static int e_emit_str(struct e_print_data *data, char *str)
{
        int length;

        assert(data != NULL && str != NULL);

        length = strlen(str);

        if (data->out.mode == E_OUTPUT_NONE)
                return length;

        if (data->out.mode == E_OUTPUT_STREAM)
                return fputs(str, data->out.file) == EOF ? E_PRINT_ERROR : length;

        /* E_OUTPUT_STR */
        strcat(data->out.str_ptr, str);
        data->out.str_ptr += length;

        return length;
}

/*
 * e_emit_char: prints a character "chr" according to "data->out.mode" and
 * returns 1, or "E_PRINT_ERROR" on error.
 *
 * Mode can be:
 *      * "E_OUTPUT_NONE": the string is not printed;
 *      * "E_OUTPUT_STREAM": the character is written to an open stream;
 *      * "E_OUTPUT_STR": the character is written to an array and then is
 *      written the null terminator "\0", make sure that the array is large
 *      enough!
 *
 */
static int e_emit_char(struct e_print_data *data, char chr)
{
        assert(data != NULL);

        if (data->out.mode == E_OUTPUT_NONE)
                return 1;

        if (data->out.mode == E_OUTPUT_STREAM)
                return fputc(chr, data->out.file) == EOF ? E_PRINT_ERROR : 1;

        /* E_OUTPUT_STR */
        *data->out.str_ptr++ = chr;
        *data->out.str_ptr = '\0';

        return 1;
}

/*
 * e_str_to_int: wrapper to "strtol" that saves the result in "result" as a
 * "int" and returns the number of characters consumed, or "-1" on error.
 *
 * The first character of "str" must be a digit or '-' sign.
 */
static int e_str_to_int(char *str, int *result)
{
        char *end;
        long parsed;
        int chrs_read;

        assert(str != NULL && result != NULL && (*str == '-' || isdigit(*str)));

        if ((parsed = strtol(str, &end, 10)) > INT_MAX || parsed < INT_MIN)
                return -1;

        *result = (int) parsed;

        for (chrs_read = 0; str != end; str++)
                chrs_read++;

        return chrs_read;
}

/*
 * e_print_field_width: pads the output according to "data" and "length" and
 * returns the number of characters printed, or "E_PRINT_ERROR" on error.
 * "length" must the length of output except for padding.
 */
static int e_print_field_width(struct e_print_data *data, int length)
{
        int chrs_printed = 0;
        char chr;

        assert(data != NULL && length >= 0);

        if (length >= data->fmt.field_width)
                return 0;

        chr = data->fmt.flag.zero_pad ? '0' : ' ';

        for (length = data->fmt.field_width - length; length > 0; length--) {
                if (e_emit_char(data, chr) == E_PRINT_ERROR)
                        return E_PRINT_ERROR;

                chrs_printed++;
        }

        return chrs_printed;
}

/*
 * The following four functions are convenient to pad the output. They should be
 * inlined, but in C89 there is no keyword "inline".
 *
 * The first and the last are used for unsigned conversion, the two in the
 * middle are used for signed conversion, because in this case the pad will be
 * placed between the sign and the digits.
 */

/*
 * e_print_left_padding: pads the output with spaces or zeros on the left and
 * returns the number of space printed, or "E_PRINT_ERROR" on error. "length"
 * must be a positive integer that indicates the length of output except for
 * padding.
 */
static int e_print_left_padding(struct e_print_data *data, int length)
{
        assert(data != NULL && length >= 0);

        if (data->fmt.field_width > 0 && !data->fmt.flag.left_align)
                return e_print_field_width(data, length);

        return 0; /* No padding. */
}

/*
 * e_print_left_padding_before_sign: pads the output with spaces on the left and
 * returns the number of space printed, or "E_PRINT_ERROR" on error. "length"
 * must be a positive integer that indicates the length of output except for
 * padding.
 */
static int e_print_left_padding_before_sign(struct e_print_data *data,
                                            int length)
{
        assert(data != NULL && length >= 0);

        if (data->fmt.field_width > 0
            && !data->fmt.flag.left_align
            && !data->fmt.flag.zero_pad)
                return e_print_field_width(data, length);

        return 0; /* No padding. */
}

/*
 * e_print_left_padding_after_sign: pads the output with zeros on the left and
 * returns the number of space printed, or "E_PRINT_ERROR" on error. "length"
 * must be a positive integer that indicates the length of output except for
 * padding.
 */
static int e_print_left_padding_after_sign(struct e_print_data *data,
                                           int length)
{
        assert(data != NULL && length >= 0);

        if (data->fmt.field_width > 0
            && !data->fmt.flag.left_align
            && data->fmt.flag.zero_pad)
                return e_print_field_width(data, length);

        return 0; /* No padding. */
}

/*
 * e_print_right_padding: pads the output with spaces on the right and returns
 * the number of space printed, or "E_PRINT_ERROR" on error. "length" must be a
 * positive integer that indicates the length of output except for padding.
 */
static int e_print_right_padding(struct e_print_data *data, int length)
{
        assert(data != NULL && length >= 0);

        if (data->fmt.field_width > 0 && data->fmt.flag.left_align)
                return e_print_field_width(data, length);

        return 0; /* No padding. */
}

/*
 * Private functions to print "str" and "char".
 */

/*
 * e_print_str: prints a string "str" according to format given in "data" and
 * returns the number of characters printed, "-1" on error.
 */
static int e_print_str(struct e_print_data *data, char *str)
{
        int length, chrs_printed, tmp;

        assert(data != NULL && str != NULL);

        data->fmt.flag.zero_pad = 0; /* Useless with strings. */

        /* Because precision CAN limit the characters to print. */
        length = strlen(str);
        if (data->fmt.precision >= 0 && length > data->fmt.precision)
                length = data->fmt.precision;

        chrs_printed = length;

        if ((tmp = e_print_left_padding(data, length)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        /* Print the entire string of just some characters. */
        if (data->fmt.precision >= 0) {
                tmp = length; /* length <= strlen(str) */
                while (*str != '\0' && --tmp >= 0) {
                        if (e_emit_char(data, *str++) == E_PRINT_ERROR)
                                return E_PRINT_ERROR;
                }
        } else {
                if (e_emit_str(data, str) == E_PRINT_ERROR)
                        return E_PRINT_ERROR;
        }

        if ((tmp = e_print_right_padding(data, length)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        return chrs_printed;
}

/*
 * e_print_char: prints a character "chr" according to "data" and returns the
 * number of characters printed, or "E_PRINT_ERROR" on error.
 */
static int e_print_char(struct e_print_data *data, char chr)
{
        int chrs_printed = 0, tmp;
        const int length = 1; /* A char is one character. */

        assert(data != NULL);

        data->fmt.flag.zero_pad = 0; /* Useless with "char". */

        if ((tmp = e_print_left_padding(data, length)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        if ((tmp = e_emit_char(data, chr)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        if ((tmp = e_print_right_padding(data, length)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        return chrs_printed;
}

/*
 * Private functions related to print "long" values ("int" are converted to
 * "long".
 */

/*
 * e_get_nr_digits_long: naive function to get the number of digits of "number".
 */
static int e_get_nr_digits_long(long number)
{
        int digits = 1; /* A number have always at least one digit. */

        while ((number /= E_DIVISOR_10_SIGN(number)) != 0)
                digits++;

        return digits;
}

static int e_print_long(struct e_print_data *data, long value);

/*
 * e_get_length_double_normal: returns the number of character that should be
 * printed with "value" according to "data", except for field width, with "%d"
 * format.
 *
 * This routine is used to calculate the pad for field width. I know it is
 * naive.
 */
static int e_get_length_long(struct e_print_data *data, long value)
{
        struct e_print_data tmp;

        /*
         * I reuse "e_print_long" to get the number of characters that should be
         * printed, but I need create a dummy "data" struct to avoid printing
         * and padding.
         */
        tmp = *data;
        tmp.out.mode = E_OUTPUT_NONE;
        tmp.fmt.field_width = 0;

        return e_print_long(&tmp, value);
}

/*
 * e_print_long_rec: recursive function called by "e_print_long" that prints
 * each digit of "value" and returns the number of chracters printed, or
 * "E_PRINT_ERROR" on error.
 */
static int e_print_long_rec(struct e_print_data *data, long value)
{
        int chrs_printed = 0;
        char out;

        assert(data != NULL);

        if (value == 0) {
                if (data->fmt.precision != 0)
                        chrs_printed += e_emit_char(data, '0');
                return chrs_printed;
        }

        if (value / E_DIVISOR_10_SIGN(value) != 0)
                chrs_printed = e_print_long_rec(data, value / E_DIVISOR_10_SIGN(value));

        out = '0' + (value < 0 ? -(value % -10) : value % 10);

        if (e_emit_char(data, out) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else 
                return chrs_printed + 1;
}

/*
 * e_print_long: prints "value" according to "data" and returns the number of
 * character printed, or "E_PRINT_ERROR" on error.
 */
static int e_print_long(struct e_print_data *data, long value)
{
        int chrs_printed = 0, length = 0, digits, tmp;

        assert(data != NULL);

        if (data->fmt.field_width > 0)
                length = e_get_length_long(data, value);

        if ((tmp = e_print_left_padding_before_sign(data, length)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        /* Make sure that initial space is printed only if there is no sign. */
        if (data->fmt.flag.show_sign && value >= 0)
                tmp = e_emit_char(data, '+');
        else if (value < 0)
                tmp = e_emit_char(data, '-');
        else if (data->fmt.flag.initial_space)
                tmp = e_emit_char(data, ' ');
        else
                tmp = 0;

        if (tmp == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        /* Zeros must be placed after the sign width zero pad flag. */
        if ((tmp = e_print_left_padding_after_sign(data, length)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        /* Precision with integer is the minimium number of digits to print. */
        if (data->fmt.precision >= 0) {
                digits = e_get_nr_digits_long(value);

                /* if digits >= data->fmt.precision there is no print. */
                for (digits = data->fmt.precision - digits;
                     digits > 0;
                     digits--, chrs_printed++)
                        if (e_emit_char(data, '0') == E_PRINT_ERROR)
                                return E_PRINT_ERROR;
        }

        if ((tmp = e_print_long_rec(data, value)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        if ((tmp = e_print_right_padding(data, length)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        return chrs_printed;
}

/*
 * Private functions to print "unsigned long" ("unsigned int" are converted to
 * "unsigned long").
 */

/*
 * e_get_nr_digits_unsigned_long: naive function to get the number of digits of
 * "number".
 */
static int e_get_nr_digits_unsigned_long(unsigned long number, int base)
{
        /* A number have always at least one digit. */
        int digits = 1;

        while ((number /= base) != 0)
                digits++;

        return digits;
}

static int e_print_unsigned_long(struct e_print_data *data,
                                 unsigned long value,
                                 int base);

/*
 * e_get_length_double_normal: returns the number of character that should be
 * printed with "value" according to "data", except for field width, with "%d"
 * format.
 *
 * This routine is used to calculate the pad for field width. I know it is
 * naive.
 */
static int e_get_length_unsigned_long(struct e_print_data *data,
                                      unsigned long value,
                                      int base)
{
        struct e_print_data tmp;

        /*
         * I reuse "e_print_unsigned_long" to get the number of characters that
         * should be printed, but I need create a dummy "data" struct to avoid
         * printing and padding.
         */
        tmp = *data;
        tmp.out.mode = E_OUTPUT_NONE;
        tmp.fmt.field_width = 0;

        return e_print_unsigned_long(&tmp, value, base);
}

/*
 * e_print_unsigned_long_rec: recursive function called by
 * "e_print_unsigned_long" to print each digits of "value"; it returns the
 * number of character printed, or "E_PRINT_ERROR" on error.
 */
static int e_print_unsigned_long_rec(struct e_print_data *data,
                                      unsigned long value,
                                      int base)
{
        int chrs_printed = 0;
        unsigned short remainder;
        char out;
        unsigned long div;

        assert(data != NULL);

        if (value == 0) {
                if (data->fmt.precision != 0)
                        chrs_printed = e_emit_char(data, '0');
                return chrs_printed;
        }

        if ((div = value / base) != 0) {
                chrs_printed = e_print_unsigned_long_rec(data, div, base);

                if (chrs_printed == E_PRINT_ERROR)
                        return E_PRINT_ERROR;
        }

        if ((remainder = value % base) > 9)
                /* Only 'x' and 'X' uses letters for a digit. */
                out = remainder - 10 + (data->fmt.flag.uppercase ? 'A' : 'a');
        else
                out = remainder + '0';

        if (e_emit_char(data, out) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                return chrs_printed + 1;
}

/*
 * e_print_unsigned_long: prints "value" according to "data" and "base" and
 * returns the number of character printed, or "E_PRINT_ERROR" on error. "base"
 * must be a valid base (10, 8 or 16).
 */
static int e_print_unsigned_long(struct e_print_data *data,
                                  unsigned long value,
                                  int base)
{
        int chrs_printed = 0, length = 0, digits, tmp;

        assert(data != NULL);

        /* Value used for padding. */
        if (data->fmt.field_width > 0)
                length = e_get_length_unsigned_long(data, value, base);

        if ((tmp = e_print_left_padding(data, length)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        /* Print precision. */
        if (data->fmt.precision >= 0) {
                digits = e_get_nr_digits_unsigned_long(value, base);

                for (digits = data->fmt.precision - digits;
                     digits > 0;
                     digits--, chrs_printed++)
                        if (e_emit_char(data, '0') == E_PRINT_ERROR)
                                return E_PRINT_ERROR;
        }

        /* Because zero doesn't have the base prefix. */
        if (value != 0 && data->fmt.flag.alternative_output && base != 10) {
                if ((tmp = e_emit_str(data, E_UNSIGNED_SYMBOL(data, base))) == E_PRINT_ERROR)
                        return E_PRINT_ERROR;
                else
                        chrs_printed += tmp;
        }

        if ((tmp = e_print_unsigned_long_rec(data, value, base)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        if ((tmp = e_print_right_padding(data, length)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        return chrs_printed;
}

/*
 * Private functions to print floating point numbers ("%f", "%e" and "%g").
 */

static int e_print_double_normal(struct e_print_data *data, double value);

static int e_print_double_exp(struct e_print_data *data, double value);

/*
 * e_get_length_double_normal: returns the number of character that should be
 * printed with "value" according to "data", except for field width, with "%f"
 * format.
 *
 * This routine is used to calculate the pad for field width. I know it is
 * naive.
 */
static int e_get_length_double_normal(struct e_print_data *data, double value)
{
        struct e_print_data tmp;

        /*
         * I reuse "e_print_double_normal" to get the number of character to be
         * printed, but I do not want to print anything, so I need to adjust
         * "data". I also do not want to apply padding (so no field width).
         *
         * Also this function can't be called directly by
         * "e_print_double_normal", otherwhise there is a infinite recursion. It
         * must be called by the specific function that calculate the field
         * width!
         */
        tmp = *data;
        tmp.out.mode = E_OUTPUT_NONE;
        tmp.fmt.field_width = 0;

        return e_print_double_normal(&tmp, value);
}

/*
 * e_get_length_double_exp: returns the number of character that should be
 * printed with "value" according to "data", except for field width, with "%e"
 * format.
 *
 * This routine is used to calculate the pad for field width. I know it is
 * naive.
 */
static int e_get_length_double_exp(struct e_print_data *data, double value)
{
        struct e_print_data tmp;

        /*
         * I reuse "e_print_double_exp" to get the number of character to be
         * printed, but I do not want to print anything, so I need to adjust
         * "data". I also do not want to apply padding (so no field width).
         *
         * Also this function can't be called directly by
         * "e_print_double_exp", otherwhise there is a infinite recursion. It
         * must be called by the specific function that calculate the field
         * width!
         */
        tmp = *data;
        tmp.out.mode = E_OUTPUT_NONE;
        tmp.fmt.field_width = 0;

        return e_print_double_exp(&tmp, value);
}

/*
 * e_print_double_int: prints the integer part of "value" and returns the
 * numbers of digits printed, or "E_PRINT_ERROR" on error. If "value" is non
 * positive, no chrs_printed are printed.
 */
static int e_print_double_int(struct e_print_data *data, double value)
{
        int chrs_printed = 0;
        double ret;

        assert(data != NULL);

        if (value <= 0)
                return 0;

        ret = fmod(value, 10);
        chrs_printed = e_print_double_int(data, floor(value / 10));

        if (e_emit_char(data, '0' + ret) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed++;

        return chrs_printed;
}

/*
 * e_print_double_frac: prints the fractional part of "value" and returns the
 * number of characters printed, or "E_PRINT_ERROR" on error.
 */
static int e_print_double_frac(struct e_print_data *data, double value)
{
        int chrs_printed = 0, precision;
        double digit;

        assert(data != NULL);

        /* Do not print '.' if there is no digits with this specifier. */
        if ((data->fmt.specifier == 'g' || data->fmt.specifier == 'G')
            && !data->fmt.flag.trailing_zeros && value * 10 == 0)
                return chrs_printed;

        /* Force '.' with '#' flag. */
        if (data->fmt.flag.alternative_output || data->fmt.precision > 0)
                chrs_printed += e_emit_char(data, '.');

        /* Defined behaviour if precision is zero. */
        if (data->fmt.precision == 0)
                return chrs_printed;

        for (precision = data->fmt.precision; precision > 0; precision--) {
                value = modf(value * 10, &digit);

                /* Do not print trailing zeros for "%g" or "%G". */
                if (digit == 0 && !data->fmt.flag.trailing_zeros)
                        return chrs_printed;

                /* Safe cast because "digit" is alway a number between 0-9. */
                chrs_printed += e_emit_char(data, '0' + (int)digit);
        }

        return chrs_printed;
}

/*
 * e_is_negative_zero: return 1 if "zero" is a negative zero, 0 otherwhise.
 *
 * If a platform doesn't support negative zero, it always returns false.
 * Otherwise, if a platform support two zeros, it returns true only if "zero"
 * is a negative zero and returns false if it is a positive zero or another
 * number.
 */
static int e_is_negative_zero(double zero)
{
        static const double neg_zero = -0., pos_zero = +0.;

        /* Bit a bit comparision, it's a dirty hack. */
        if (memcmp(&neg_zero, &pos_zero, sizeof(double)) == 0)
                return 0; /* No support for negative zero. */

        return memcmp(&zero, &neg_zero, sizeof(double)) == 0 ? 1 : 0;
}

/*
 * e_print_double_nan: prints "nan" according to "data" and returns the number
 * of characters printed.
 *
 * I made a custom function to avoid clutter in other functions for double. This
 * function is naive and it not fully supports NANs, because C89 don't give
 * tools to work with this value (instead of C99).
 */
static int e_print_double_nan(struct e_print_data *data)
{
        /* Buffer for initial space, "nan" or "NAN" and terminator. */
        char buffer[5] = { '\0', '\0', '\0', '\0', '\0' }, *ptr_buf = buffer;

        assert(data != NULL);

        if (data->fmt.flag.initial_space)
                *ptr_buf++ = ' ';

        strcat(ptr_buf, data->fmt.flag.uppercase ? "NAN" : "nan");

        return e_print_str(data, buffer);
}

/*
 * e_print_double_prefix: prints the prefix of a double number (initial space or
 * sign) and returns the number of characters printed (1 if a prefix is printed
 * or 0 if no prefix is printed), or "E_PRINT_ERROR" on error.
 *
 * The number can't be "NAN" or "inf".
 */
static int e_print_double_prefix(struct e_print_data *data, double value)
{
        char chr = 0;

        assert(data != NULL && !E_FLOAT_ISNAN(value) && E_FLOAT_ISFINITE(value));

        /* Initial space is printed only if there is no sign. */
        if (value < 0 || e_is_negative_zero(value))
                chr = '-';
        else if (data->fmt.flag.show_sign) /* Force sign for positive values. */
                chr = '+';
        else if (data->fmt.flag.initial_space)
                chr = ' ';

        if (!chr)
                return 0; /* No prefix printed. */

        if (e_emit_char(data, chr) == E_PRINT_ERROR)
                return E_PRINT_ERROR;

        return 1;
}

/*
 * e_print_double_inf: prints "inf" according to "data" and return the number of
 * characters printed. "data" must be a valid object (non-null) and "inf" must
 * be infinite.
 *
 * I made a custom function to avoid clutter in other function for double.
 */
static int e_print_double_inf(struct e_print_data *data, double inf)
{
        /*
         * Buffer for initial space or sign, "INF" or "inf" and null terminator.
         */
        char buffer[5] = { '\0', '\0', '\0', '\0', '\0' }, *ptr_buf = buffer;

        assert(data != NULL && !E_FLOAT_ISNAN(inf) && !E_FLOAT_ISFINITE(inf));

        if (inf < 0)
                *ptr_buf++ = '-';
        else if (data->fmt.flag.show_sign)
                *ptr_buf++ = '+';
        else if (data->fmt.flag.initial_space)
                *ptr_buf++ = ' ';

        strcat(ptr_buf, data->fmt.flag.uppercase ? "INF" : "inf");

        return e_print_str(data, buffer);
}

static int e_print_double(struct e_print_data *data, double value)
{
        int chrs_printed = 0, tmp;
        double fp_frac, fp_int;

        assert(data != NULL);

        fp_frac = modf(fabs(value), &fp_int);

        /*
         * Print integer part. I need to handle separately the case when integer
         * is zero because "e_print_double_int" doesn't print a single zero.
         */
        if (fp_int == 0) {
                if ((tmp = e_emit_char(data, '0')) == E_PRINT_ERROR)
                        return E_PRINT_ERROR;
                else
                        chrs_printed += tmp;
        } else {
                if ((tmp = e_print_double_int(data, fp_int)) == E_PRINT_ERROR)
                        return E_PRINT_ERROR;
                else
                        chrs_printed += tmp;
        }

        /*
         * Precision limits the number of significant digits for this
         * conversion, so "e_print_double_frac" will print only the remain
         * digits. Not if "fp_int" is zero, because this digits doesn't count.
         */
        if (fp_int != 0
            && (data->fmt.specifier == 'g' || data->fmt.specifier == 'G'))
                data->fmt.precision -= chrs_printed;

        /* Print fractional part. */
        if ((tmp = e_print_double_frac(data, fp_frac)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        return chrs_printed;
}

/*
 * e_print_double_normal: prints "value" with all digits.
 *
 * This implementation is naive and not precise. As example it fails to print
 * "144115188075855877", it instead prints "144115188075855882". It also doesn't
 * rounds the number.
 */
static int e_print_double_normal(struct e_print_data *data, double value)
{
        int chrs_printed = 0, length = 0, tmp;

        assert(data != NULL && data->fmt.precision >= 0
               && !E_FLOAT_ISNAN(value) && E_FLOAT_ISFINITE(value));

        /* Value used for padding. */
        if (data->fmt.field_width > 0)
            length = e_get_length_double_normal(data, value);

        if ((tmp = e_print_left_padding_before_sign(data, length)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        if ((tmp = e_print_double_prefix(data, value)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        if ((tmp = e_print_left_padding_after_sign(data, length)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        if ((tmp = e_print_double(data, value)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        if ((tmp = e_print_right_padding(data, length)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        return chrs_printed;
}

/*
 * e_frexp10: like "frexp" but in base 10. Naive function!
 */
static double e_frexp10(double value, int *exp)
{
        assert(exp != NULL);

        if (value == 0)
                return *exp = 0;

        *exp = floor(log10(fabs(value)));

        return value * pow(10, -*exp);
}

/*
 * e_print_double_exp: prints "value" according to "data" in scientific notation
 * and returns the number of characters printed, or "E_PRINT_ERROR" on error.
 * "value" can't be "NAN" or "inf".
 */
static int e_print_double_exp(struct e_print_data *data, double value)
{
        int chrs_printed = 0, fp_exp = 0, field_width = 0, length = 0, tmp;
        double fp_frac;

        assert(data != NULL && data->fmt.precision >= 0
               && !E_FLOAT_ISNAN(value) && E_FLOAT_ISFINITE(value));

        /* Value used for padding. */
        if (data->fmt.field_width > 0)
            length = e_get_length_double_exp(data, value);

        if (value == 0) /* Because "value" can be a negative zero. */
                fp_frac = value;
        else
                fp_frac = e_frexp10(value, &fp_exp);

        if ((tmp = e_print_left_padding_before_sign(data, length)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        if ((tmp = e_print_double_prefix(data, value)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        if ((tmp = e_print_left_padding_after_sign(data, length)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        /* Print normalized fraction. */
        if ((tmp = e_print_double(data, fp_frac)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        /* Print exponent. */
        if ((tmp = e_emit_char(data, data->fmt.flag.uppercase ? 'E' : 'e')) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        /*
         * Because I reuse the function to print the exponent, I need to adjust
         * "data" to print the number in exponential format (a sign followed by
         * at least two digits).
         */
        field_width = data->fmt.field_width;
        data->fmt.field_width = 0;
        data->fmt.flag.show_sign = 1;
        data->fmt.precision = 2;

        chrs_printed += e_print_long(data, (long)fp_exp);

        /* Only restore field width, other fields are useless at this point. */
        data->fmt.field_width = field_width;

        if ((tmp = e_print_right_padding(data, length)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_printed += tmp;

        return chrs_printed;
}

/*
 * Private functions to parse a format string.
 */

/*
 * e_parse_format_flags: parses flag characters, saves them on "data->fmt.flag"
 * and returns the number of characters read.
 */
static int e_parse_format_flags(struct e_print_data *data, char *str)
{
        int is_flag, chrs_read = 0;

        assert(data != NULL && str != NULL);

        is_flag = 1;
        while (is_flag) {
                switch (*str) {
                        case '-':
                                data->fmt.flag.left_align = 1;
                                break;
                        case '+':
                                data->fmt.flag.show_sign = 1;
                                break;
                        case ' ':
                                data->fmt.flag.initial_space = 1;
                                break;
                        case '0':
                                data->fmt.flag.zero_pad = 1;
                                break;
                        case '#':
                                data->fmt.flag.alternative_output = 1;
                                break;
                        default:
                                is_flag = 0;
                                break;
                }

                if (is_flag)
                        str++, chrs_read++;
        }

        /* Resolve conflicts. */
        if (data->fmt.flag.zero_pad && data->fmt.flag.left_align)
                data->fmt.flag.zero_pad = 0;
        if (data->fmt.flag.initial_space && data->fmt.flag.show_sign)
                data->fmt.flag.initial_space = 0;

        return chrs_read;
}

/*
 * e_parse_field_width: parses field width, saves the value on
 * "data->fmt.field_width" and returns the number of character read, or
 * "E_PRINT_ERROR" on error.
 */
static int e_parse_field_width(struct e_print_data *data, char *str, va_list ap)
{
        int chrs_read = 0, field_width;

        assert(data != NULL && str != NULL);

        if (*str == '\0' || *str == '.' || (!isdigit(*str) && *str != '*'))
                return 0; /* No field width found. */

        if (*str == '*') {
                chrs_read++;
                field_width = va_arg(ap, int);
        } else {
                if ((chrs_read += e_str_to_int(str, &field_width)) == -1)
                        return E_PRINT_ERROR;
        }

        if (field_width < 0) {
                data->fmt.flag.left_align = 1;

                if (INT_MAX + field_width < 0)
                        return E_PRINT_ERROR; /* Overflow. */
                else
                        field_width = -field_width;
        }

        data->fmt.field_width = field_width;

        return chrs_read;
}

/*
 * e_parse_precision: parses precision, saves the value on "data->fmt.precision"
 * and returns the numbers of characters read, or "E_PRINT_ERROR" on error.
 */
static int e_parse_precision(struct e_print_data *data, char *str, va_list ap)
{
        int chrs_read = 0, precision;

        assert(data != NULL && str != NULL);

        if (*str != '.')
                return chrs_read; /* No precision found. */

        chrs_read++, str++;

        if (*str == '*') {
                chrs_read++;
                precision = va_arg(ap, int);
        } else if (isdigit(*str) || *str == '-') {
                if ((chrs_read += e_str_to_int(str, &precision)) == -1)
                        return E_PRINT_ERROR;
        } else { /* Only a single period '.'. */
                precision = 0;
        }

        /* A negative precision is taken as if it is omitted. */
        if (precision >= 0)
                data->fmt.precision = precision;

        return chrs_read;
}

/*
 * e_parse_format_length_modifier: parses an optional length modifier ('l', 'h'
 * or 'L'), saves the value on "data->fmt.length_modifier" and returns 1 if
 * "chr" is a modifier, 0 otherwhise.
 */
static int e_parse_format_length_modifier(struct e_print_data *data, char chr)
{
        assert(data != NULL);

        switch (chr) {
                case 'h':
                case 'l':
                case 'L':
                        data->fmt.length_modifier = chr;
                        break;
                default: /* Not a length modifier, it is not an error! */
                        break;
        }

        return data->fmt.length_modifier ? 1 : 0;
}

/*
 * e_parse_format: parses a format string except for conversion specifier and
 * returns the number of characters read, or "E_PRINT_ERROR" on error.
 *
 * The string pointer "str" must start with the first character after '%'.
 */
static int e_parse_format(struct e_print_data *data, char *str, va_list ap)
{
        int tmp, chrs_read = 0;

        assert(data != NULL && str != NULL);

        if (*str == '\0')
                return E_PRINT_ERROR; /* No format characters after '%'. */

        e_reset_format(&data->fmt);

        /* Flag characters. */
        if ((tmp = e_parse_format_flags(data, str)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_read += tmp, str += tmp;

        /* Field width. */
        if ((tmp = e_parse_field_width(data, str, ap)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_read += tmp, str += tmp;

        /* Precision. */
        if ((tmp = e_parse_precision(data, str, ap)) == E_PRINT_ERROR)
                return E_PRINT_ERROR;
        else
                chrs_read += tmp, str += tmp;

        /* Length modifier. */
        return chrs_read + e_parse_format_length_modifier(data, *str);
}

/*
 * Private functions that handle generic values.
 */

/*
 * e_print_generic_int: prints a double "value" according to "data" and returns
 * the number of characters printed, or "E_PRINT_ERROR" on error.
 */
static int e_print_generic_double(struct e_print_data *data, double value)
{
        int fp_exp;

        assert(data != NULL);

        if (data->fmt.precision == -1)
                data->fmt.precision = E_FLOAT_DEFAULT_PRECISION;

        /* Precision is useless with these special values. */
        if (E_FLOAT_ISNAN(value) || !E_FLOAT_ISFINITE(value))
                data->fmt.precision = -1;

        /* Handle special values separately. */
        if (E_FLOAT_ISNAN(value))
                return e_print_double_nan(data);
        if (!E_FLOAT_ISFINITE(value))
                return e_print_double_inf(data, value);

        switch (data->fmt.specifier) {
                case 'f':
                        return e_print_double_normal(data, value);
                case 'G':
                case 'g':
                        if (!data->fmt.flag.alternative_output)
                                data->fmt.flag.trailing_zeros = 0;

                        if (data->fmt.precision == 0)
                                data->fmt.precision = 1;

                        /*
                         * Yes, it is a waste of computation because if
                         * "e_print_double_exp" is called this function is
                         * called again.
                         */
                        e_frexp10(value, &fp_exp);

                        if (fp_exp < -4 || fp_exp >= data->fmt.precision)
                                return e_print_double_exp(data, value);
                        else
                                return e_print_double_normal(data, value);
                case 'E':
                case 'e':
                        return e_print_double_exp(data, value);
                default:
                        return E_PRINT_ERROR;
        }
}

/*
 * e_print_generic_int: prints an integer taken in "ap" according to "chr"
 * conversion specifier and returns the number of characters printed, or
 * "E_PRINT_ERROR" on error.
 */
static int e_print_generic_int(struct e_print_data *data,
                               char chr,
                               va_list ap)
{
        union { long l; unsigned long lu; } value;
        int base;

        assert(data != NULL);

        /* Zero pad is ignored if precision is given with integers. */
        if (data->fmt.precision >= 0 && data->fmt.flag.zero_pad)
                data->fmt.flag.zero_pad = 0;

        /* Precalculate the base for unsigned value. */
        switch (chr) {
                case 'X':
                case 'x':
                        base = 16;
                        break;
                case 'o':
                        base = 8;
                        break;
                case 'u':
                        base = 10;
                        break;
                default: /* 'd' and 'i'. */
                        break;
        }

        /*
         * Note that in '...' every type of rank less than 'int' are promoted to
         * 'int' (es. 'short' becomes 'int').
         */
        switch (chr) {
                case 'd':
                case 'i':
                        if (data->fmt.length_modifier == 'l')
                                value.l = va_arg(ap, long);
                        else
                                value.l = va_arg(ap, int);

                        return e_print_long(data, value.l);
                case 'X':
                        data->fmt.flag.uppercase = 1;
                        /* FALLTHROUGH */
                case 'x':
                case 'o':
                case 'u':
                        if (data->fmt.length_modifier == 'l')
                                value.lu = va_arg(ap, unsigned long);
                        else
                                value.lu = va_arg(ap, unsigned);

                        return e_print_unsigned_long(data, value.lu, base);
                default:
                        return E_PRINT_ERROR;
        }
}

/*
 * e_print_value: prints a value taken in "ap" according to "chr" conversion
 * specifier and returns the number of characters printed, or "E_PRINT_ERROR" on
 * error.
 */
static int e_print_generic_value(struct e_print_data *data,
                                 char chr,
                                 va_list ap)
{
        int *n_ptr;

        assert(data != NULL);

        data->fmt.specifier = chr;

        switch (chr) {
                case '\0': /* Fake conversion specifier, do nothing. */
                        return 0;
                case 'X':
                        data->fmt.flag.uppercase = 1;
                        /* FALLTHROUGH */
                case 'd':
                case 'i':
                case 'u':
                case 'o':
                case 'x':
                        return e_print_generic_int(data, chr, ap);
                case 'c':
                        return e_print_char(data, (unsigned)va_arg(ap, int));
                case 's':
                        return e_print_str(data, va_arg(ap, char *));
                case '%':
                        return e_emit_char(data, '%');
                case 'E':
                case 'G':
                        data->fmt.flag.uppercase = 1;
                        /* FALLTHROUGH */
                case 'f':
                case 'e':
                case 'g':
                        return e_print_generic_double(data, va_arg(ap, double));
                case 'n':
                        n_ptr = va_arg(ap, int *);

                        /* Custom behaviour if pointer is NULL. */
                        if (n_ptr == NULL)
                                return E_PRINT_ERROR;
                        else
                                *n_ptr = data->out.chrs_printed;

                        return 0;
                default: /* Unrecognized specifier. */
                        return E_PRINT_ERROR;
        }
}

/*
 * e_print_generic: prints an output according to the arguments and returns the
 * number of characters printed, or "-1" on error.
 *
 * "data" is used * to direct output to a stream or a string, "format" is used
 * to format the output and "ap" is used to take values described in "format".
 */
static int e_print_generic(struct e_print_data *data,
                           char *format,
                           va_list ap)
{
        char *chr;
        int chrs_printed, tmp;

        assert(data != NULL && format != NULL);

        for (chrs_printed = 0, chr = format;
             *chr;
             chr++, data->out.chrs_printed = chrs_printed) {
                if (*chr != '%') {
                        if ((tmp = e_emit_char(data, *chr)) == E_PRINT_ERROR)
                                return E_PRINT_ERROR;

                        chrs_printed += tmp;
                        continue;
                }

                if ((tmp = e_parse_format(data, ++chr, ap)) == E_PRINT_ERROR)
                        return E_PRINT_ERROR;

                chr += tmp;

                if ((tmp = e_print_generic_value(data, *chr, ap)) == E_PRINT_ERROR)
                        return E_PRINT_ERROR;

                chrs_printed += tmp;
        }

        return chrs_printed;
}

/*
 * Public API implementation.
 */

int e_printf(char *format, ...)
{
        int retval;
        va_list ap;

        assert(format != NULL);

        va_start(ap, format);

        retval = e_vprintf(format, ap);

        va_end(ap);

        return retval;
}

int e_fprintf(FILE *stream, char *format, ...)
{
        int retval;
        va_list ap;

        assert(stream != NULL && format != NULL);

        va_start(ap, format);

        retval = e_vfprintf(stream, format, ap);

        va_end(ap);

        return retval;
}

int e_sprintf(char *str, char *format, ...)
{
        int retval;
        va_list ap;

        assert(str != NULL && format != NULL);

        va_start(ap, format);

        retval = e_vsprintf(str, format, ap);

        va_end(ap);

        return retval;
}

int e_vprintf(char *format, va_list ap)
{
        assert(format != NULL);

        return e_vfprintf(stdout, format, ap);
}

int e_vfprintf(FILE *stream, char *format, va_list ap)
{
        struct e_print_data data;

        assert(stream != NULL && format != NULL);

        e_reset_data(&data);
        data.out.mode = E_OUTPUT_STREAM;
        data.out.file = stream;

        return e_print_generic(&data, format, ap);
}

int e_vsprintf(char *str, char *format, va_list ap)
{
        struct e_print_data data;

        assert(str != NULL && format != NULL);

        e_reset_data(&data);
        *str = '\0';
        data.out.mode = E_OUTPUT_STR;
        data.out.str_ptr = data.out.str = str;

        return e_print_generic(&data, format, ap);
}

Example program example.c:

#include "eprintf.h"

int main(void)
{
        int n;
        char buf[50];

        n = e_printf("Hello %-15.20s %g\n", "world", 0.232443211);

        e_printf("Retval = %5d\n", n);

        e_sprintf(buf, "%f %c %n", 50.23, 'I', &n);

        puts(buf);

        e_fprintf(stdout, "Value: %#.10X\n", 3422);

        return 0;
}

Compile it with: gcc -Wpedantic -Wall -Wextra -ansi -g -o example example.c eprintf.c.

If you want to run this code easily, you can go to my repository where you can find a Makefile and lots of unit tests.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Your own ...printf() - a grand undertaking - Impressive.


K&R vs C89

"had issues with floating point because there are not good tools in C89" --> K&R, in effect, is just pre-C89. I'll assume still the goal is C89 and not K&R C.

const

C89 uses const as in int fprintf (FILE *stresxn, const char *format, ...). I'd expect:

// int e_printf(char *format, ...);
int e_printf(const char *format, ...);

... and for the other functions. The is const ripples down into the various helper functions too.

is...()

isdigit(int ch) is defined for values in the unsigned char range and EOF. As char can be signed, better code would insure the function is called with unsigned char values.

char *str
...
// isdigit(*str)
isdigit((unsigned char) *str)

p is not supported ...

"because it is machine dependent" is more like "implementation dependent". Code could simply convert the void * argument to unsigned long and print that with "0x%lX" when the sizeof(void *) <= sizeof(unsigned long). A deeper alternative would use a union of void * and unsigned char *. example here uses binary.

issues with ... existence of NaN or inf

Inf: x < -DBL_MAX || x > DBL_MAX: Well defined.

E_FLOAT_ISFINITE(value) (!(fabs(value) >= HUGE_VAL)) is incorrect as HUGE_VAL == DBL_MAX is possible.

Nan: x != x: Somewhat well defined as code has done.

eprintf.h

Code nice and tight. I'd expect some documentation here giving the overall goal of this function set.

Include order

For eprintf.c consider eprintf.h first as a test that the .h file is not dependent on any user prior include.

long string limitation

int length; length = strlen(str); limits string length to INT_MAX. Pedantically string length is up to SIZE_MAX and size_t.

Pedantic: signed char

Since code is C89-ish (design for all 3 encoding types), best to explicitly use unsigned char * when accessing the string data. Non-2's complement is mis-interpret-able when reading a -0 as that is not a string terminating null-character.

Lots of good error checking

Printing FP

This is hard to do right and handle all corner cases.

e_print_double() prints a truncated value (as OP has noted) rather than a rounded one as seen in higher quality implementations . To round right is not trivial.

Total loss of precision with tiny values

Consider values near DBL_TRUE_MIN: pow(10, -*exp) becomes 0.0.

    *exp = floor(log10(fabs(value)));
    return value * pow(10, -*exp);

Powers of 10

For edge cases of value near powers of 10, I suspect value * pow(10, -*exp) can return a rounded value of 10.0 rather than < 10.0 resulting in errant output.

Function to print a double - exactly may offer insight or at least a test reference.

Errant comment

e_print_generic_int with e_print_generic_double()

/* e_print_generic_int: prints a double "value" according ... */
static int e_print_generic_double(struct e_print_data *data, double value)

Good avoidance of -INT_MIN in e_print_long_rec()

IAC, in C89, / and % are more loosely defined. Recommend div_t div(int numer, int denom); for a consistent quotient, remainder.

div_t qr = div(value, E_DIVISOR_10_SIGN(value));
if (qr.quot) {
  chrs_printed = e_print_long_rec(data, qr.quot);
}
out = '0' + abs((int)qr.rem);

I see no reasons for E_DIVISOR_10_SIGN(value) here.

// div_t qr = div(value, E_DIVISOR_10_SIGN(value));
div_t qr = div(value, 10);

10 vs. 9

With base 10 part of code, I find coding 10 more informative than 9.

// if ((remainder = value % base) > 9)
if ((remainder = value % base) >= 10)

Minor: Code assumes A-F,a-f are consecutive

Not specified by C, yet true for ASCII, EBCDIC and every character encoding I know.

Alternative:

// out = remainder - 10 + (data->fmt.flag.uppercase ? 'A' : 'a');
out = (data->fmt.flag.uppercase ? "ABCDEF" : "abcdef")[remainder - 10];

OK thats a wrap for today.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ @ema-pe I In C89 I do not find the spec "Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type.". So could use the union with an unsigned char[] to present something. \$\endgroup\$ Commented Apr 21, 2020 at 12:52
  • 1
    \$\begingroup\$ @ema-pe I see no overflow possibility with (!(fabs(value) >= HUGE_VAL)) - it is just a compare. Code appears to mostly use E_FLOAT_ISFINITE() as a test of value, not the result of an operation like 2.0*x. Doing a "returns HUGE_VAL and sets errno" does require ERRNO=0 set prior to known the ERRNO was due to the just prior operation and not some earlier op like underflow. IAC, to reiterate in case not clear: E_FLOAT_ISFINITE() is an incorrect test to detect infinity as HUGE_VAL may be DBL_MAX on systems without infinity and INF on systems with infinity. \$\endgroup\$ Commented Apr 21, 2020 at 13:09
  • 1
    \$\begingroup\$ @ema-pe IOWs: there appears to need to test 1) if a value if infinite and 2) to test if an operation overflowed. \$\endgroup\$ Commented Apr 21, 2020 at 13:09
  • 1
    \$\begingroup\$ @ema-pe Re: FP stuff. As it is hard to achieve a high quality rounded result, it might be useful to separate the FP to another module. Consider exact DBL_MIN may be > 1000 dgits digits. \$\endgroup\$ Commented Apr 21, 2020 at 13:17
  • 1
    \$\begingroup\$ @ema-pe that spec appears in later C specs. \$\endgroup\$ Commented Apr 21, 2020 at 13:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.