#if HAVE_CONFIG_H # include "config.h" #endif #if ! defined(HAVE_CONFIG_H) || HAVE_STRING_H # include #endif #include /* for snprintf() */ #include /* for calloc() */ #include #include #include "calculator.h" #include "string_manip.h" #include "number_formatting.h" #ifdef MEMWATCH #include "memwatch.h" #endif static size_t zero_strip(char *num); static void add_prefix(char *num, size_t length, int base); static char *engineering_formatted_number(char *digits, mp_exp_t exp, const int precision, const int base, const int prefix); static char *full_precision_formatted_number(char *digits, mp_exp_t exp, const int base, const int prefix); static char *automatically_formatted_number(char *digits, mp_exp_t exp, const int base, const int prefix, char *truncated_flag); static char *precision_formatted_number(char *digits, mp_exp_t exp, const int precision, const int base, const int prefix); /* this function takes a number (mpfr_t) and prints it. * This is a blatant ripoff of mpfr's mpfr_out_str(), because it formats things * (sorta) the way I want them formatted, though this prints things out to a * string, and does all the fancy presentation stuff we've come to expect from * wcalc. */ char *num_to_str_complex(const mpfr_t num, const int base, const int engr, const int prec, const int prefix, char *truncated_flag) { char *s, *retstr; mp_exp_t e; Dprintf("num_to_str_complex: base: %i, engr: %i, prec: %i, prefix: %i\n", base, engr, prec, prefix); if (mpfr_nan_p(num)) { return (char *)strdup("@NaN@"); } if (mpfr_inf_p(num)) { if (mpfr_sgn(num) > 0) { return (char *)strdup("@Inf@"); } else { return (char *)strdup("-@Inf@"); } } if (mpfr_zero_p(num)) { if (mpfr_sgn(num) >= 0) { return (char *)strdup("0"); } else { return (char *)strdup("-0"); } } s = mpfr_get_str(NULL, &e, base, 0, num, GMP_RNDN); /* s is the string * e is the number of integers (the exponent) if positive * * Now, if there's odd formatting involved, make mpfr do the rounding, * so we know it's "correct": */ if (prec > -1) { if (engr == 0) { size_t significant_figures = 0; Dprintf("prec > -1 && engr == 0\n"); /*printf("e: %li\n", (long)e); * printf("s: %s\n", s); * printf("prec: %i\n", prec); */ significant_figures = ((e > 0) ? e : 0) + prec; if (significant_figures < 2) { /* why is this the minimum? */ s = mpfr_get_str(s, &e, base, 2, num, GMP_RNDN); if (s[1] > '4') { /* XXX: LAME! */ unsigned foo; foo = s[0] - '0'; foo++; snprintf(s, 3, "%u", foo); e++; } } else { mpfr_free_str(s); s = mpfr_get_str(NULL, &e, base, significant_figures, num, GMP_RNDN); } } else { s = mpfr_get_str(NULL, &e, base, 1 + prec, num, GMP_RNDN); } } Dprintf("post-mpfr e: %li s: %s\n", (long int)e, s); *truncated_flag = 0; if (-2 == prec) { retstr = full_precision_formatted_number(s, e, base, prefix); } else if (engr != 0) { retstr = engineering_formatted_number(s, e, prec, base, prefix); } else if (-1 == prec) { retstr = automatically_formatted_number(s, e, base, prefix, truncated_flag); } else { retstr = precision_formatted_number(s, e, prec, base, prefix); } mpfr_free_str(s); Dprintf("return string: %s\n", retstr); return retstr; } char *precision_formatted_number(char *digits, mp_exp_t exp, const int precision, const int base, const int prefix) { size_t length; size_t full_length; size_t decimal_count = 0; size_t print_limit; char *retstring, *curs, *dcurs = digits; length = strlen(digits); /* testing against both zero and length because length is unsigned */ if (exp > 0 && (size_t)exp > length) length = exp; length += 3; if (length < (size_t) (precision + 3)) { // leading zero, decimal, and null length = (size_t) (precision + 3); } Dprintf("Precision Formatted Number\n"); Dprintf("digits: %s(%u), exp: %i, base: %i, prefix: %i, precision: %i\n", digits, (unsigned)length, (int)exp, base, prefix, precision); // ten extra, 'cuz of the *possible* exponent full_length = length + 10; curs = retstring = (char *)calloc(full_length, sizeof(char)); Dprintf("length: %lu, full_length: %lu\n", length, full_length); // now, copy the digits into the output string, carefully // copy over the negative sign if (*dcurs == '-') { snprintf(curs++, length--, "%c", *dcurs++); } // copy in a prefix if (prefix) { char *nc; add_prefix(curs, length, base); nc = strchr(curs, '\0'); length -= nc - curs; curs = nc; } // copy in the integers if (exp > 0) { snprintf(curs++, length--, "%c", *dcurs++); exp--; // leading digit while (exp > 0 && *dcurs) { snprintf(curs++, length--, "%c", *dcurs++); exp--; } for (; exp > 0; exp--) { snprintf(curs++, length--, "0"); } } else { snprintf(curs++, length--, "0"); } if (precision > 0) { // the decimal snprintf(curs++, length--, "."); Dprintf("the integers: %s\n", retstring); Dprintf("l: %lu, fl: %lu, dc: %u, p: %i, e: %i\n", length, full_length, (unsigned)decimal_count, (int)precision, (int)exp); // everything after this is affected by decimalcount // copy in the leading zeros while (exp < 0 && (ssize_t)decimal_count <= precision) { snprintf(curs++, length--, "0"); exp++; decimal_count++; } Dprintf("l: %lu, fl: %lu, dc: %u, p: %i, e: %i\n", length, full_length, (unsigned)decimal_count, (int)precision, (int)exp); // copy in the rest of the mantissa (the decimals) Dprintf("leading zeros: %s\n", retstring); // this variable exists because snprintf's return value is unreliable, // and can be larger than the number of digits printed print_limit = ((length < (precision - decimal_count + 1)) ? length : (precision - decimal_count + 1)); snprintf(curs, print_limit, "%s", dcurs); } return retstring; } char *full_precision_formatted_number(char *digits, mp_exp_t exp, const int base, const int prefix) { size_t length; size_t full_length; size_t decimal_count = 0; size_t printed; char *retstring, *curs, *dcurs = digits; length = strlen(digits); /* testing against both zero and length because length is unsigned */ if (exp > 0 && (size_t)exp > length) length = exp; length += 3; /* the null, the (possible) sign, and the decimal */ Dprintf("Full Precision Formatted Number\n"); Dprintf("digits: %s(%u), exp: %i, base: %i, prefix: %i\n", digits, (unsigned)length, (int)exp, base, prefix); Dprintf("strlen(digits): %u\n", (unsigned)strlen(digits)); // ten extra, 'cuz of the *possible* exponent full_length = length + 10; curs = retstring = (char *)calloc(sizeof(char), full_length); Dprintf("length: %lu, full_length: %lu\n", length, full_length); // now, copy the digits into the output string, carefully // copy over the negative sign if (*dcurs == '-') { snprintf(curs++, length--, "%c", *dcurs++); } // copy in a prefix if (prefix) { char *nc; add_prefix(curs, length, base); nc = strchr(curs, '\0'); length -= nc - curs; curs = nc; } Dprintf("ready for ints: %s\n", retstring); // copy over the integers if (exp > 0) { snprintf(curs++, length--, "%c", *dcurs++); exp--; // leading digit while (exp > 0 && *dcurs) { snprintf(curs++, length--, "%c", *dcurs++); exp--; } for (; exp > 0; exp--) { snprintf(curs++, length--, "0"); } } else { snprintf(curs++, length--, "0"); } // the decimal snprintf(curs++, length--, "."); Dprintf("the integers: %s\n", retstring); Dprintf("length: %lu, full_length: %lu\n", length, full_length); // XXX: Currently, this function is not used for decimals, so... // the leading decimal zeros while (exp < 0) { snprintf(curs++, length--, "0"); exp++; decimal_count++; } // the rest of the mantissa (the decimals) // this variable exists because snprintf's return value is unreliable. // and can be larger than the number of digits printed printed = ((length - 1 < strlen(dcurs)) ? length - 1 : strlen(dcurs)); snprintf(curs, length, "%s", dcurs); length -= printed; decimal_count += printed; // strip off the trailing 0's zero_strip(retstring); // copy in an exponent if necessary if (exp != 0) { curs = strchr(retstring, '\0'); Dprintf("space left: %lu\n", full_length - (curs - retstring)); snprintf(curs, full_length - (curs - retstring), (base <= 10 ? "e%ld" : "@%ld"), (long)exp); } return retstring; } char *automatically_formatted_number(char *digits, mp_exp_t exp, const int base, const int prefix, char *truncated_flag) { size_t length; size_t full_length; size_t decimal_count = 0; size_t printed; char *retstring, *curs, *dcurs = digits; length = strlen(digits); /* testing against both zero and length because length is unsigned */ if (exp > 0 && (size_t)exp > length) length = exp; length += 3; /* the null, the (possible) sign, and the decimal */ Dprintf("Automatically Formatted Number\n"); Dprintf("digits: %s(%u), exp: %i, base: %i, prefix: %i\n", digits, (unsigned)length, (int)exp, base, prefix); Dprintf("strlen(digits): %u\n", (unsigned)strlen(digits)); // ten extra, 'cuz of the *possible* exponent full_length = length + 10; curs = retstring = (char *)calloc(sizeof(char), full_length); Dprintf("length: %lu, full_length: %lu\n", length, full_length); // now, copy the digits into the output string, carefully // copy over the negative sign if (*dcurs == '-') { snprintf(curs++, length--, "%c", *dcurs++); } // copy in a prefix if (prefix) { char *nc; add_prefix(curs, length, base); nc = strchr(curs, '\0'); length -= nc - curs; curs = nc; } Dprintf("ready for ints: %s\n", retstring); // copy over the integers if (exp > 0) { snprintf(curs++, length--, "%c", *dcurs++); exp--; // leading digit while (exp > 0 && *dcurs) { snprintf(curs++, length--, "%c", *dcurs++); exp--; } for (; exp > 0; exp--) { snprintf(curs++, length--, "0"); } } else { snprintf(curs++, length--, "0"); } // the decimal snprintf(curs++, length--, "."); Dprintf("the integers: %s\n", retstring); Dprintf("length: %lu, full_length: %lu\n", length, full_length); // XXX: Currently, this function is not used for decimals, so... // the leading decimal zeros while (exp < 0) { snprintf(curs++, length--, "0"); exp++; decimal_count++; } // the rest of the mantissa (the decimals) // this variable exists because snprintf's return value is unreliable. // and can be larger than the number of digits printed printed = ((length - 1 < strlen(dcurs)) ? length - 1 : strlen(dcurs)); snprintf(curs, length, "%s", dcurs); length -= printed; decimal_count += printed; // strip off the trailing 0's zero_strip(retstring); { /* XXX: This seems like a stupid hack. Is this for * readability? Note to self: remove this if it * ever becomes an issue again (and merge * full_precision_formatted_number back with this * function). */ char *period = strchr(retstring, '.'); Dprintf("retstring: %s\n", retstring); Dprintf("period: %s\n", period); if (period && strlen(period) > 10) { period[10] = 0; *truncated_flag = 1; zero_strip(retstring); } } // copy in an exponent if necessary if (exp != 0) { curs = strchr(retstring, '\0'); Dprintf("space left: %lu\n", full_length - (curs - retstring)); snprintf(curs, full_length - (curs - retstring), (base <= 10 ? "e%ld" : "@%ld"), (long)exp); } return retstring; } char *engineering_formatted_number(char *digits, mp_exp_t exp, const int precision, const int base, const int prefix) { size_t length; size_t full_length; char *retstring, *curs, *dcurs = digits; length = strlen(digits); /* testing against both zero and length because length is unsigned */ if (exp > 0 && (size_t)exp > length) length = exp; length += 3; /* the null, the (possible) sign, and the decimal */ Dprintf("Engineering Formatted Number\n"); Dprintf("digits: %s(%u), exp: %i, prec: %i, prefix: %i\n", digits, (unsigned)length, (int)exp, precision, prefix); // ten extra, 'cuz of the exponent full_length = length + 10; curs = retstring = (char *)calloc(sizeof(char), full_length); Dprintf("length: %lu, full_length: %lu\n", length, full_length); // now, copy the digits into the output string, carefully // copy over the negative sign if (*dcurs == '-') { snprintf(curs++, length--, "%c", *dcurs++); } // copy in a prefix if (prefix) { char *nc; add_prefix(curs, length, base); nc = strchr(curs, '\0'); length -= nc - curs; curs = nc; } // copy over the integer snprintf(curs++, length--, "%c", *dcurs++); exp--; // the decimal snprintf(curs++, length--, "."); Dprintf("the integers: %s\n", retstring); Dprintf("length: %lu, full_length: %lu\n", length, full_length); // now, add in the rest of the digits // note that the digits are already correctly rounded and I've already // allocated enough space, because of how I asked mpfr for the original // digit string. snprintf(curs, length, "%s", dcurs); Dprintf("the decimals: %s\n", retstring); // strip off the trailing 0's if (-1 == precision) { zero_strip(retstring); } // copy in an exponent curs = strchr(retstring, '\0'); Dprintf("space left: %lu\n", full_length - (curs - retstring)); snprintf(curs, full_length - (curs - retstring), (base <= 10 ? "e%ld" : "@%ld"), (long)exp); return retstring; } /* this function removes zeros and decimals from the back end of a number, * and returns how many characters it stripped */ size_t zero_strip(char *num) { size_t curs = strlen(num) - 1; size_t count = 0; while ('0' == num[curs]) { num[curs--] = '\0'; count++; } if ('.' == num[curs]) { num[curs] = '\0'; count++; } return count; } /* this function prints a prefix for the specified base into the specified * string */ void add_prefix(char *num, size_t length, int base) { switch (base) { case 16: snprintf(num, length, "0x"); return; case 10: return; case 8: snprintf(num, length, "0"); return; case 2: snprintf(num, length, "0b"); return; } }