/* * Copyright (c) 2008-2020 Stefan Krah. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "mpdecimal.h" #include <assert.h> #include <ctype.h> #include <errno.h> #include <limits.h> #include <locale.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "io.h" #include "typearith.h" /* This file contains functions for decimal <-> string conversions, including PEP-3101 formatting for numeric types. */ #if defined(__GNUC__) && !defined(__INTEL_COMPILER) && __GNUC__ >= 7 #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" #pragma GCC diagnostic ignored "-Wmisleading-indentation" #pragma GCC diagnostic ignored "-Warray-bounds" #endif /* * Work around the behavior of tolower() and strcasecmp() in certain * locales. For example, in tr_TR.utf8: * * tolower((unsigned char)'I') == 'I' * * u is the exact uppercase version of l; n is strlen(l) or strlen(l)+1 */ static inline int _mpd_strneq(const char *s, const char *l, const char *u, size_t n) { … } static mpd_ssize_t strtoexp(const char *s) { … } /* * Scan 'len' words. The most significant word contains 'r' digits, * the remaining words are full words. Skip dpoint. The string 's' must * consist of digits and an optional single decimal point at 'dpoint'. */ static void string_to_coeff(mpd_uint_t *data, const char *s, const char *dpoint, int r, size_t len) { … } /* * Partially verify a numeric string of the form: * * [cdigits][.][cdigits][eE][+-][edigits] * * If successful, return a pointer to the location of the first * relevant coefficient digit. This digit is either non-zero or * part of one of the following patterns: * * ["0\x00", "0.\x00", "0.E", "0.e", "0E", "0e"] * * The locations of a single optional dot or indicator are stored * in 'dpoint' and 'exp'. * * The end of the string is stored in 'end'. If an indicator [eE] * occurs without trailing [edigits], the condition is caught * later by strtoexp(). */ static const char * scan_dpoint_exp(const char *s, const char **dpoint, const char **exp, const char **end) { … } /* scan the payload of a NaN */ static const char * scan_payload(const char *s, const char **end) { … } /* convert a character string to a decimal */ void mpd_qset_string(mpd_t *dec, const char *s, const mpd_context_t *ctx, uint32_t *status) { … } /* convert a character string to a decimal, use a maxcontext for conversion */ void mpd_qset_string_exact(mpd_t *dec, const char *s, uint32_t *status) { … } /* Print word x with n decimal digits to string s. dot is either NULL or the location of a decimal point. */ #define EXTRACT_DIGIT(s, x, d, dot) … static inline char * word_to_string(char *s, mpd_uint_t x, int n, char *dot) { … } /* Print exponent x to string s. Undefined for MPD_SSIZE_MIN. */ static inline char * exp_to_string(char *s, mpd_ssize_t x) { … } /* Print the coefficient of dec to string s. len(dec) > 0. */ static inline char * coeff_to_string(char *s, const mpd_t *dec) { … } /* Print the coefficient of dec to string s. len(dec) > 0. dot is either NULL or a pointer to the location of a decimal point. */ static inline char * coeff_to_string_dot(char *s, char *dot, const mpd_t *dec) { … } /* Format type */ #define MPD_FMT_LOWER … #define MPD_FMT_UPPER … #define MPD_FMT_TOSCI … #define MPD_FMT_TOENG … #define MPD_FMT_EXP … #define MPD_FMT_FIXED … #define MPD_FMT_PERCENT … #define MPD_FMT_SIGN_SPACE … #define MPD_FMT_SIGN_PLUS … /* Default place of the decimal point for MPD_FMT_TOSCI, MPD_FMT_EXP */ #define MPD_DEFAULT_DOTPLACE … /* * Set *result to the string representation of a decimal. Return the length * of *result, not including the terminating '\0' character. * * Formatting is done according to 'flags'. A return value of -1 with *result * set to NULL indicates MPD_Malloc_error. * * 'dplace' is the default place of the decimal point. It is always set to * MPD_DEFAULT_DOTPLACE except for zeros in combination with MPD_FMT_EXP. */ static mpd_ssize_t _mpd_to_string(char **result, const mpd_t *dec, int flags, mpd_ssize_t dplace) { … } char * mpd_to_sci(const mpd_t *dec, int fmt) { … } char * mpd_to_eng(const mpd_t *dec, int fmt) { … } mpd_ssize_t mpd_to_sci_size(char **res, const mpd_t *dec, int fmt) { … } mpd_ssize_t mpd_to_eng_size(char **res, const mpd_t *dec, int fmt) { … } /* Copy a single UTF-8 char to dest. See: The Unicode Standard, version 5.2, chapter 3.9: Well-formed UTF-8 byte sequences. */ static int _mpd_copy_utf8(char dest[5], const char *s) { … } int mpd_validate_lconv(mpd_spec_t *spec) { … } int mpd_parse_fmt_str(mpd_spec_t *spec, const char *fmt, int caps) { … } /* * The following functions assume that spec->min_width <= MPD_MAX_PREC, which * is made sure in mpd_qformat_spec. Then, even with a spec that inserts a * four-byte separator after each digit, nbytes in the following struct * cannot overflow. */ /* Multibyte string */ mpd_mbstr_t; static inline void _mpd_bcopy(char *dest, const char *src, mpd_ssize_t n) { … } static inline void _mbstr_copy_char(mpd_mbstr_t *dest, const char *src, mpd_ssize_t n) { … } static inline void _mbstr_copy_ascii(mpd_mbstr_t *dest, const char *src, mpd_ssize_t n) { … } static inline void _mbstr_copy_pad(mpd_mbstr_t *dest, mpd_ssize_t n) { … } /* * Copy a numeric string to dest->data, adding separators in the integer * part according to spec->grouping. If leading zero padding is enabled * and the result is smaller than spec->min_width, continue adding zeros * and separators until the minimum width is reached. * * The final length of dest->data is stored in dest->nbytes. The number * of UTF-8 characters is stored in dest->nchars. * * First run (dest->data == NULL): determine the length of the result * string and store it in dest->nbytes. * * Second run (write to dest->data): data is written in chunks and in * reverse order, starting with the rest of the numeric string. */ static void _mpd_add_sep_dot(mpd_mbstr_t *dest, const char *sign, /* location of optional sign */ const char *src, mpd_ssize_t n_src, /* integer part and length */ const char *dot, /* location of optional decimal point */ const char *rest, mpd_ssize_t n_rest, /* remaining part and length */ const mpd_spec_t *spec) { … } /* * Convert a numeric-string to its locale-specific appearance. * The string must have one of these forms: * * 1) [sign] digits [exponent-part] * 2) [sign] digits '.' [digits] [exponent-part] * * Not allowed, since _mpd_to_string() never returns this form: * * 3) [sign] '.' digits [exponent-part] * * Input: result->data := original numeric string (ASCII) * result->bytes := strlen(result->data) * result->nchars := strlen(result->data) * * Output: result->data := modified or original string * result->bytes := strlen(result->data) * result->nchars := number of characters (possibly UTF-8) */ static int _mpd_apply_lconv(mpd_mbstr_t *result, const mpd_spec_t *spec, uint32_t *status) { … } /* Add padding to the formatted string if necessary. */ static int _mpd_add_pad(mpd_mbstr_t *result, const mpd_spec_t *spec, uint32_t *status) { … } /* Round a number to prec digits. The adjusted exponent stays the same or increases by one if rounding up crosses a power of ten boundary. If result->digits would exceed MPD_MAX_PREC+1, MPD_Invalid_operation is set and the result is NaN. */ static inline void _mpd_round(mpd_t *result, const mpd_t *a, mpd_ssize_t prec, const mpd_context_t *ctx, uint32_t *status) { … } /* * Return the string representation of an mpd_t, formatted according to 'spec'. * The format specification is assumed to be valid. Memory errors are indicated * as usual. This function is quiet. */ char * mpd_qformat_spec(const mpd_t *dec, const mpd_spec_t *spec, const mpd_context_t *ctx, uint32_t *status) { … } char * mpd_qformat(const mpd_t *dec, const char *fmt, const mpd_context_t *ctx, uint32_t *status) { … } /* * The specification has a *condition* called Invalid_operation and an * IEEE *signal* called Invalid_operation. The former corresponds to * MPD_Invalid_operation, the latter to MPD_IEEE_Invalid_operation. * MPD_IEEE_Invalid_operation comprises the following conditions: * * [MPD_Conversion_syntax, MPD_Division_impossible, MPD_Division_undefined, * MPD_Fpu_error, MPD_Invalid_context, MPD_Invalid_operation, * MPD_Malloc_error] * * In the following functions, 'flag' denotes the condition, 'signal' * denotes the IEEE signal. */ static const char *mpd_flag_string[MPD_NUM_FLAGS] = …; static const char *mpd_signal_string[MPD_NUM_FLAGS] = …; /* print conditions to buffer, separated by spaces */ int mpd_snprint_flags(char *dest, int nmemb, uint32_t flags) { … } /* print conditions to buffer, in list form */ int mpd_lsnprint_flags(char *dest, int nmemb, uint32_t flags, const char *flag_string[]) { … } /* print signals to buffer, in list form */ int mpd_lsnprint_signals(char *dest, int nmemb, uint32_t flags, const char *signal_string[]) { … } /* The following two functions are mainly intended for debugging. */ void mpd_fprint(FILE *file, const mpd_t *dec) { … } void mpd_print(const mpd_t *dec) { … }