/* mpfr_fpif -- Binary export & import of MPFR numbers (floating-point interchange format) Copyright 2012-2021 Free Software Foundation, Inc. Contributed by Olivier Demengeon. This file is part of the GNU MPFR Library. The GNU MPFR Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. The GNU MPFR Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU MPFR Library; see the file COPYING.LESSER. If not, see https://www.gnu.org/licenses/ or write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mpfr-impl.h" #if !defined (HAVE_BIG_ENDIAN) && !defined (HAVE_LITTLE_ENDIAN) #error "Endianness is unknown. Not supported yet." #endif /* The format is described as follows. Any multi-byte number is encoded in little endian. 1. We first store the precision p (this format is able to represent any precision from 1 to 2^64 + 248). Let B be the first byte (0 <= B <= 255). * If B >= 8, the precision p is B-7. Here, the condition is equivalent to 1 <= p <= 248. * If B <= 7, the next B+1 bytes contain p-249. Here, the condition is equivalent to 249 <= p <= 2^64 + 248. We will use the following macros: * MPFR_MAX_PRECSIZE = 7 * MPFR_MAX_EMBEDDED_PRECISION = 255 - 7 = 248 2. Then we store the sign bit and exponent related information (possibly a special value). We first have byte A = [seeeeeee], where s is the sign bit and E = [eeeeeee] such that: * If 0 <= E <= 94, then the exponent e is E-47 (-47 <= e <= 47). * If 95 <= E <= 110, the exponent is stored in the next E-94 bytes (1 to 16 bytes) in sign + absolute value representation, where the absolute value is increased by 47 (e <= -47 or 47 <= e). * If 111 <= E <= 118, the exponent size S is stored in the next E-110 bytes (1 to 8), then the exponent itself is stored in the next S bytes. [Not implemented yet] * If 119 <= E <= 127, we have a special value: E = 119 (MPFR_KIND_ZERO) for a signed zero; E = 120 (MPFR_KIND_INF) for a signed infinity; E = 121 (MPFR_KIND_NAN) for NaN. 3. Then we store the significand (for regular values). The sign bit is preserved by the import/export functions, even for NaN. Note: When a size is stored, it must be minimal, i.e. a number cannot start with a null byte. Otherwise the import may fail. */ #define MPFR_MAX_PRECSIZE 7 #define MPFR_MAX_EMBEDDED_PRECISION (255 - MPFR_MAX_PRECSIZE) #define MPFR_KIND_ZERO 119 #define MPFR_KIND_INF 120 #define MPFR_KIND_NAN 121 #define MPFR_MAX_EMBEDDED_EXPONENT 47 #define MPFR_EXTERNAL_EXPONENT 94 /* Begin: Low level helper functions */ /* storage must have an unsigned type */ #define COUNT_NB_BYTE(storage, size) \ do \ { \ (storage) >>= 8; \ (size)++; \ } \ while ((storage) != 0) #define ALLOC_RESULT(buffer, buffer_size, wanted_size) \ do \ { \ if ((buffer) == NULL || *(buffer_size) < (wanted_size)) \ { \ (buffer) = (unsigned char *) mpfr_reallocate_func \ ((buffer), *(buffer_size), (wanted_size)); \ MPFR_ASSERTN((buffer) != 0); \ } \ *(buffer_size) = (wanted_size); \ } \ while (0) /* * size in byte of a MPFR number in a binary object of a variable size */ #define MAX_VARIABLE_STORAGE(exponent_size, precision) \ ((size_t)(((precision) >> 3) + (exponent_size) + \ ((precision) > 248 ? sizeof(mpfr_prec_t) : 0) + 3)) /* copy in result[] the values in data[] with a different endianness, where data_size might be smaller than data_max_size, so that we only copy data_size bytes from the end of data[]. */ static void #if defined (HAVE_BIG_ENDIAN) putLittleEndianData (unsigned char *result, unsigned char *data, size_t data_max_size, size_t data_size) #elif defined (HAVE_LITTLE_ENDIAN) putBigEndianData (unsigned char *result, unsigned char *data, size_t data_max_size, size_t data_size) #endif { size_t j; MPFR_ASSERTD (data_size <= data_max_size); for (j = 0; j < data_size; j++) result[j] = data[data_max_size - j - 1]; } /* copy in result[] the values in data[] with the same endianness */ static void #if defined (HAVE_BIG_ENDIAN) putBigEndianData (unsigned char *result, unsigned char *data, size_t data_max_size, size_t data_size) #elif defined (HAVE_LITTLE_ENDIAN) putLittleEndianData (unsigned char *result, unsigned char *data, size_t data_max_size, size_t data_size) #endif { MPFR_ASSERTD (data_size <= data_max_size); memcpy (result, data, data_size); } /* copy in result[] the values in data[] with a different endianness; the data are written at the end of the result[] buffer (if data_size < data_max_size, the first bytes of result[] are left untouched). */ static void #if defined (HAVE_BIG_ENDIAN) getLittleEndianData (unsigned char *result, unsigned char *data, size_t data_max_size, size_t data_size) #elif defined (HAVE_LITTLE_ENDIAN) getBigEndianData (unsigned char *result, unsigned char *data, size_t data_max_size, size_t data_size) #endif { size_t j; MPFR_ASSERTD (data_size <= data_max_size); for (j = 0; j < data_size; j++) result[data_max_size - j - 1] = data[j]; } /* copy in result[] the values in data[] with the same endianness */ static void #if defined (HAVE_BIG_ENDIAN) getBigEndianData (unsigned char *result, unsigned char *data, size_t data_max_size, size_t data_size) #elif defined (HAVE_LITTLE_ENDIAN) getLittleEndianData (unsigned char *result, unsigned char *data, size_t data_max_size, size_t data_size) #endif { MPFR_ASSERTD (data_size <= data_max_size); memcpy (result, data, data_size); } /* End: Low level helper functions */ /* Internal Function */ /* * buffer : OUT : store the precision in binary format, can be null * (may be reallocated if too small) * buffer_size : IN/OUT : size of the buffer => size used in the buffer * precision : IN : precision to store * return pointer to a buffer storing the precision in binary format */ static unsigned char * mpfr_fpif_store_precision (unsigned char *buffer, size_t *buffer_size, mpfr_prec_t precision) { unsigned char *result; size_t size_precision; MPFR_ASSERTD (precision >= 1); size_precision = 0; if (precision > MPFR_MAX_EMBEDDED_PRECISION) { mpfr_uprec_t copy_precision; copy_precision = precision - (MPFR_MAX_EMBEDDED_PRECISION + 1); COUNT_NB_BYTE(copy_precision, size_precision); } result = buffer; ALLOC_RESULT(result, buffer_size, size_precision + 1); if (precision > MPFR_MAX_EMBEDDED_PRECISION) { result[0] = size_precision - 1; precision -= (MPFR_MAX_EMBEDDED_PRECISION + 1); putLittleEndianData (result + 1, (unsigned char *) &precision, sizeof(mpfr_prec_t), size_precision); } else result[0] = precision + MPFR_MAX_PRECSIZE; return result; } #define BUFFER_SIZE 8 /* * fh : IN : file handler * return the precision stored in the binary buffer, 0 in case of error */ static mpfr_prec_t mpfr_fpif_read_precision_from_file (FILE *fh) { mpfr_prec_t precision; size_t precision_size; unsigned char buffer[BUFFER_SIZE]; if (fh == NULL) return 0; if (fread (buffer, 1, 1, fh) != 1) return 0; precision_size = buffer[0]; if (precision_size > MPFR_MAX_PRECSIZE) return precision_size - MPFR_MAX_PRECSIZE; precision_size++; MPFR_ASSERTD (precision_size <= BUFFER_SIZE); /* Read the precision in little-endian format. */ if (fread (buffer, precision_size, 1, fh) != 1) return 0; /* Justification of the #if below. */ MPFR_ASSERTD (precision_size <= MPFR_MAX_PRECSIZE + 1); #if (MPFR_MAX_PRECSIZE + 1) * CHAR_BIT > MPFR_PREC_BITS while (precision_size > sizeof(mpfr_prec_t)) { if (buffer[precision_size-1] != 0) return 0; /* the read precision doesn't fit in a mpfr_prec_t */ precision_size--; } #endif /* To detect bugs affecting particular platforms (thus MPFR_ASSERTN)... */ MPFR_ASSERTN (precision_size <= sizeof(mpfr_prec_t)); /* Since mpfr_prec_t is signed, one also needs to check that the most significant bit of the corresponding unsigned value is 0. */ if (precision_size == sizeof(mpfr_prec_t) && buffer[precision_size-1] >= 0x80) return 0; /* the read precision doesn't fit in a mpfr_prec_t */ precision = 0; /* to pad with 0's if data_size < data_max_size */ /* On big-endian machines, the data must be copied at the end of the precision object in the memory; thus data_max_size (3rd argument) must be sizeof(mpfr_prec_t). */ getLittleEndianData ((unsigned char *) &precision, buffer, sizeof(mpfr_prec_t), precision_size); return precision + (MPFR_MAX_EMBEDDED_PRECISION + 1); } /* * buffer : OUT : store the kind of the MPFR number x, its sign, the size of * its exponent and its exponent value in a binary format, * can be null (may be reallocated if too small) * buffer_size : IN/OUT : size of the buffer => size used in the buffer * x : IN : MPFR number * return pointer to a buffer storing the kind of the MPFR number x, its sign, * the size of its exponent and its exponent value in a binary format, */ /* TODO * Exponents that use more than 16 bytes are not managed (not an issue * until one has integer types larger than 128 bits). */ static unsigned char* mpfr_fpif_store_exponent (unsigned char *buffer, size_t *buffer_size, mpfr_ptr x) { unsigned char *result; mpfr_uexp_t uexp; size_t exponent_size; exponent_size = 0; if (MPFR_IS_PURE_FP (x)) { mpfr_exp_t exponent = MPFR_GET_EXP (x); if (exponent > MPFR_MAX_EMBEDDED_EXPONENT || exponent < -MPFR_MAX_EMBEDDED_EXPONENT) { mpfr_uexp_t copy_exponent, exp_sign_bit; uexp = SAFE_ABS (mpfr_uexp_t, exponent) - MPFR_MAX_EMBEDDED_EXPONENT; /* Shift uexp to take the sign bit of the exponent into account. Because of constraints on the valid exponents, this cannot overflow (check with an MPFR_ASSERTD). */ copy_exponent = uexp << 1; MPFR_ASSERTD (copy_exponent > uexp); COUNT_NB_BYTE(copy_exponent, exponent_size); MPFR_ASSERTN (exponent_size <= 16); /* see TODO */ /* Sign bit of the exponent. */ exp_sign_bit = (mpfr_uexp_t) 1 << (8 * exponent_size - 1); MPFR_ASSERTD (uexp < exp_sign_bit); if (exponent < 0) uexp |= exp_sign_bit; } else uexp = exponent + MPFR_MAX_EMBEDDED_EXPONENT; } result = buffer; ALLOC_RESULT(result, buffer_size, exponent_size + 1); if (MPFR_IS_PURE_FP (x)) { if (exponent_size == 0) result[0] = uexp; else { result[0] = MPFR_EXTERNAL_EXPONENT + exponent_size; putLittleEndianData (result + 1, (unsigned char *) &uexp, sizeof(mpfr_exp_t), exponent_size); } } else if (MPFR_IS_ZERO (x)) result[0] = MPFR_KIND_ZERO; else if (MPFR_IS_INF (x)) result[0] = MPFR_KIND_INF; else { MPFR_ASSERTD (MPFR_IS_NAN (x)); result[0] = MPFR_KIND_NAN; } /* Set the sign, even for NaN. */ if (MPFR_IS_NEG (x)) result[0] |= 0x80; return result; } /* * x : OUT : MPFR number extracted from the binary buffer * fh : IN : file handler (should not be NULL) * return 0 if successful */ /* TODO * Exponents that use more than 16 bytes are not managed (this is not * an issue if the data were written by MPFR with mpfr_exp_t not larger * than 128 bits). */ static int mpfr_fpif_read_exponent_from_file (mpfr_ptr x, FILE * fh) { mpfr_exp_t exponent; mpfr_uexp_t uexp; size_t exponent_size; int sign; unsigned char buffer[sizeof(mpfr_exp_t)]; MPFR_ASSERTD(fh != NULL); if (fread (buffer, 1, 1, fh) != 1) return 1; /* sign value that can be used with MPFR_SET_SIGN, mpfr_set_zero and mpfr_set_inf */ sign = (buffer[0] & 0x80) ? MPFR_SIGN_NEG : MPFR_SIGN_POS; /* Set the sign, even for NaN. */ MPFR_SET_SIGN (x, sign); exponent = buffer[0] & 0x7F; exponent_size = 1; if (exponent > MPFR_EXTERNAL_EXPONENT && exponent < MPFR_KIND_ZERO) { mpfr_uexp_t exp_sign_bit; exponent_size = exponent - MPFR_EXTERNAL_EXPONENT; /* A failure is acceptable when the exponent starts with leading zeros, even if it would fit in mpfr_exp_t (see format description). */ if (MPFR_UNLIKELY (exponent_size > 16 /* see TODO */ || exponent_size > sizeof(mpfr_exp_t))) return 1; if (MPFR_UNLIKELY (fread (buffer, exponent_size, 1, fh) != 1)) return 1; uexp = 0; getLittleEndianData ((unsigned char *) &uexp, buffer, sizeof(mpfr_exp_t), exponent_size); /* Sign bit of the exponent. */ exp_sign_bit = uexp & ((mpfr_uexp_t) 1 << (8 * exponent_size - 1)); uexp &= ~exp_sign_bit; uexp += MPFR_MAX_EMBEDDED_EXPONENT; if (MPFR_UNLIKELY (uexp > MPFR_EMAX_MAX && uexp > -MPFR_EMIN_MIN)) return 1; exponent = exp_sign_bit ? - (mpfr_exp_t) uexp : (mpfr_exp_t) uexp; if (MPFR_UNLIKELY (! MPFR_EXP_IN_RANGE (exponent))) return 1; MPFR_SET_EXP (x, exponent); exponent_size++; } else if (exponent == MPFR_KIND_ZERO) MPFR_SET_ZERO (x); else if (exponent == MPFR_KIND_INF) MPFR_SET_INF (x); else if (exponent == MPFR_KIND_NAN) MPFR_SET_NAN (x); else if (exponent <= MPFR_EXTERNAL_EXPONENT) { exponent -= MPFR_MAX_EMBEDDED_EXPONENT; if (MPFR_UNLIKELY (! MPFR_EXP_IN_RANGE (exponent))) return 1; MPFR_SET_EXP (x, exponent); } else return 1; return 0; } /* * buffer : OUT : store the limb of the MPFR number x in a binary format, * can be null (may be reallocated if too small) * buffer_size : IN/OUT : size of the buffer => size used in the buffer * x : IN : MPFR number * return pointer to a buffer storing the limb of the MPFR number x in a binary * format */ static unsigned char* mpfr_fpif_store_limbs (unsigned char *buffer, size_t *buffer_size, mpfr_ptr x) { unsigned char *result; mpfr_prec_t precision; size_t nb_byte; size_t nb_limb, mp_bytes_per_limb; size_t nb_partial_byte; size_t i, j; precision = mpfr_get_prec (x); nb_byte = (precision + 7) >> 3; mp_bytes_per_limb = mp_bits_per_limb >> 3; nb_partial_byte = nb_byte % mp_bytes_per_limb; nb_limb = (nb_byte + mp_bytes_per_limb - 1) / mp_bytes_per_limb; result = buffer; ALLOC_RESULT(result, buffer_size, nb_byte); putBigEndianData (result, (unsigned char*) MPFR_MANT(x), sizeof(mp_limb_t), nb_partial_byte); for (i = nb_partial_byte, j = (nb_partial_byte == 0) ? 0 : 1; j < nb_limb; i += mp_bytes_per_limb, j++) putLittleEndianData (result + i, (unsigned char*) (MPFR_MANT(x) + j), sizeof(mp_limb_t), sizeof(mp_limb_t)); return result; } /* * x : OUT : MPFR number extracted from the binary buffer, should have the same * precision than the number in the binary format * buffer : IN : limb of the MPFR number x in a binary format * nb_byte : IN : size of the buffer (in bytes) * Assume buffer is not NULL. */ static void mpfr_fpif_read_limbs (mpfr_ptr x, unsigned char *buffer, size_t nb_byte) { size_t mp_bytes_per_limb; size_t nb_partial_byte; size_t i, j; MPFR_ASSERTD (buffer != NULL); mp_bytes_per_limb = mp_bits_per_limb >> 3; nb_partial_byte = nb_byte % mp_bytes_per_limb; if (nb_partial_byte > 0) { memset (MPFR_MANT(x), 0, sizeof(mp_limb_t)); getBigEndianData ((unsigned char*) MPFR_MANT(x), buffer, sizeof(mp_limb_t), nb_partial_byte); } for (i = nb_partial_byte, j = (nb_partial_byte == 0) ? 0 : 1; i < nb_byte; i += mp_bytes_per_limb, j++) getLittleEndianData ((unsigned char*) (MPFR_MANT(x) + j), buffer + i, sizeof(mp_limb_t), sizeof(mp_limb_t)); } /* External Function */ /* * fh : IN : file handler * x : IN : MPFR number to put in the file * return 0 if successful */ int mpfr_fpif_export (FILE *fh, mpfr_ptr x) { int status; unsigned char *buf; unsigned char *bufResult; size_t used_size, buf_size; if (fh == NULL) return -1; buf_size = MAX_VARIABLE_STORAGE(sizeof(mpfr_exp_t), mpfr_get_prec (x)); buf = (unsigned char*) mpfr_allocate_func (buf_size); MPFR_ASSERTN(buf != NULL); used_size = buf_size; buf = mpfr_fpif_store_precision (buf, &used_size, mpfr_get_prec (x)); used_size > buf_size ? buf_size = used_size : 0; status = fwrite (buf, used_size, 1, fh); if (status != 1) { mpfr_free_func (buf, buf_size); return -1; } used_size = buf_size; bufResult = mpfr_fpif_store_exponent (buf, &used_size, x); /* bufResult cannot be NULL: if reallocation failed in mpfr_fpif_store_exponent, an assertion failed */ buf = bufResult; used_size > buf_size ? buf_size = used_size : 0; status = fwrite (buf, used_size, 1, fh); if (status != 1) { mpfr_free_func (buf, buf_size); return -1; } if (mpfr_regular_p (x)) { used_size = buf_size; buf = mpfr_fpif_store_limbs (buf, &used_size, x); used_size > buf_size ? buf_size = used_size : 0; status = fwrite (buf, used_size, 1, fh); if (status != 1) { mpfr_free_func (buf, buf_size); return -1; } } mpfr_free_func (buf, buf_size); return 0; } /* * x : IN/OUT : MPFR number extracted from the file, its precision is reset to * be able to hold the number * fh : IN : file handler * Return 0 if the import was successful. */ int mpfr_fpif_import (mpfr_ptr x, FILE *fh) { int status; mpfr_prec_t precision; unsigned char *buffer; size_t used_size; precision = mpfr_fpif_read_precision_from_file (fh); if (precision == 0) /* precision = 0 means an error */ return -1; MPFR_ASSERTD(fh != NULL); /* checked by mpfr_fpif_read_precision_from_file */ if (precision > MPFR_PREC_MAX) return -1; MPFR_STAT_STATIC_ASSERT (MPFR_PREC_MIN == 1); /* as specified */ mpfr_set_prec (x, precision); status = mpfr_fpif_read_exponent_from_file (x, fh); if (status != 0) { mpfr_set_nan (x); return -1; } /* Warning! The significand of x is not set yet. Thus use MPFR_IS_SINGULAR for the test. */ if (!MPFR_IS_SINGULAR (x)) { /* For portability, we need to consider bytes with only 8 significant bits in the interchange format. That's OK because CHAR_BIT >= 8. But the implementation is currently not clear when CHAR_BIT > 8. This may have never been tested. For safety, require CHAR_BIT == 8, and test/adapt the code if this ever fails. */ MPFR_STAT_STATIC_ASSERT (CHAR_BIT == 8); MPFR_STAT_STATIC_ASSERT ((MPFR_PREC_MAX + 7) >> 3 <= (size_t) -1); used_size = (precision + 7) >> 3; /* ceil(precision/8) */ buffer = (unsigned char*) mpfr_allocate_func (used_size); MPFR_ASSERTN(buffer != NULL); status = fread (buffer, used_size, 1, fh); if (status != 1) { mpfr_free_func (buffer, used_size); mpfr_set_nan (x); return -1; } mpfr_fpif_read_limbs (x, buffer, used_size); mpfr_free_func (buffer, used_size); } return 0; }