/* EAM July 2004 (revised to dynamic buffer July 2005) * There are probably an infinite number of things that can * go wrong if the user mis-matches arguments and format strings * in the call to sprintf, but I hope none will do worse than * result in a garbage output string. */ void f_sprintf(union argument *arg) { struct value a[10], *args; struct value num_params; struct value result; char *buffer; int bufsize; char *next_start, *outpos, tempchar; int next_length; char *prev_start; int prev_pos; int i, remaining; int nargs = 0; enum DATA_TYPES spec_type; /* Retrieve number of parameters from top of stack */ pop(&num_params); nargs = num_params.v.int_val; if (nargs > 10) { /* Fall back to slow but sure allocation */ args = gp_alloc(sizeof(struct value)*nargs, "sprintf args"); } else args = a; for (i=0; i<nargs; i++) pop(&args[i]); /* pop next argument */ /* Make sure we got a format string of some sort */ if (args[nargs-1].type != STRING) int_error(NO_CARET,"First parameter to sprintf must be a format string"); /* Allocate space for the output string. If this isn't */ /* long enough we can reallocate a larger space later. */ bufsize = 80 + strlen(args[nargs-1].v.string_val); buffer = gp_alloc(bufsize, "f_sprintf"); /* Copy leading fragment of format into output buffer */ outpos = buffer; next_start = args[nargs-1].v.string_val; next_length = strcspn(next_start,"%"); strncpy(outpos, next_start, next_length); next_start += next_length; outpos += next_length; /* Format the remaining sprintf() parameters one by one */ prev_start = next_start; prev_pos = next_length; remaining = nargs - 1; /* If the user has set an explicit LC_NUMERIC locale, apply it */ /* to sprintf calls during expression evaluation. */ set_numeric_locale(); /* Each time we start this loop we are pointing to a % character */ while (remaining-->0 && next_start[0] && next_start[1]) { struct value *next_param = &args[remaining]; /* Check for %%; print as literal and don't consume a parameter */ if (!strncmp(next_start,"%%",2)) { next_start++; do { *outpos++ = *next_start++; } while(*next_start && *next_start != '%'); remaining++; continue; } next_length = strcspn(next_start+1,"%") + 1; tempchar = next_start[next_length]; next_start[next_length] = '\0'; spec_type = sprintf_specifier(next_start); /* string value <-> numerical value check */ if ( spec_type == STRING && next_param->type != STRING ) int_error(NO_CARET,"f_sprintf: attempt to print numeric value with string format"); if ( spec_type != STRING && next_param->type == STRING ) int_error(NO_CARET,"f_sprintf: attempt to print string value with numeric format"); #ifdef HAVE_SNPRINTF /* Use the format to print next arg */ switch(spec_type) { case INTGR: snprintf(outpos,bufsize-(outpos-buffer), next_start, (int)real(next_param)); break; case CMPLX: snprintf(outpos,bufsize-(outpos-buffer), next_start, real(next_param)); break; case STRING: snprintf(outpos,bufsize-(outpos-buffer), next_start, next_param->v.string_val); break; default: int_error(NO_CARET,"internal error: invalid spec_type"); } #else /* FIXME - this is bad; we should dummy up an snprintf equivalent */ switch(spec_type) { case INTGR: sprintf(outpos, next_start, (int)real(next_param)); break; case CMPLX: sprintf(outpos, next_start, real(next_param)); break; case STRING: sprintf(outpos, next_start, next_param->v.string_val); break; default: int_error(NO_CARET,"internal error: invalid spec_type"); } #endif next_start[next_length] = tempchar; next_start += next_length; outpos = &buffer[strlen(buffer)]; /* Check whether previous parameter output hit the end of the buffer */ /* If so, reallocate a larger buffer, go back and try it again. */ if (strlen(buffer) >= bufsize-2) { bufsize *= 2; buffer = gp_realloc(buffer, bufsize, "f_sprintf"); next_start = prev_start; outpos = buffer + prev_pos; remaining++; continue; } else { prev_start = next_start; prev_pos = outpos - buffer; } } /* Copy the trailing portion of the format, if any */ /* We could just call snprintf(), but it doesn't check for */ /* whether there really are more variables to handle. */ i = bufsize - (outpos-buffer); while (*next_start && --i > 0) { if (*next_start == '%' && *(next_start+1) == '%') next_start++; *outpos++ = *next_start++; } *outpos = '\0'; FPRINTF((stderr," snprintf result = \"%s\"\n",buffer)); push(Gstring(&result, buffer)); free(buffer); /* Free any strings from parameters we have now used */ for (i=0; i<nargs; i++) gpfree_string(&args[i]); if (args != a) free(args); /* Return to C locale for internal use */ reset_numeric_locale(); }
/* HBB 20010121: added code to maintain consistency between mantissa * and exponent across sprintf() calls. The problem: format string * '%t*10^%T' will display 9.99 as '10.0*10^0', but 10.01 as * '1.0*10^1'. This causes problems for people using the %T part, * only, with logscaled axes, in combination with the occasional * round-off error. */ void gprintf( char *dest, size_t count, char *format, double log10_base, double x) { char temp[MAX_LINE_LEN + 1]; char *t; TBOOLEAN seen_mantissa = FALSE; /* memorize if mantissa has been output, already */ int stored_power = 0; /* power that matches the mantissa output earlier */ set_numeric_locale(); for (;;) { /*{{{ copy to dest until % */ while (*format != '%') if (!(*dest++ = *format++)) { reset_numeric_locale(); return; /* end of format */ } /*}}} */ /*{{{ check for %% */ if (format[1] == '%') { *dest++ = '%'; format += 2; continue; } /*}}} */ /*{{{ copy format part to temp, excluding conversion character */ t = temp; *t++ = '%'; /* dont put isdigit first since sideeffect in macro is bad */ while (*++format == '.' || isdigit((unsigned char) *format) || *format == '-' || *format == '+' || *format == ' ' || *format == '\'') *t++ = *format; /*}}} */ /*{{{ convert conversion character */ switch (*format) { /*{{{ x and o */ case 'x': case 'X': case 'o': case 'O': t[0] = *format; t[1] = 0; sprintf(dest, temp, (int) x); break; /*}}} */ /*{{{ e, f and g */ case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': t[0] = *format; t[1] = 0; sprintf(dest, temp, x); break; /*}}} */ /*{{{ l --- mantissa to current log base */ case 'l': { double mantissa; t[0] = 'f'; t[1] = 0; mant_exp(log10_base, x, FALSE, &mantissa, &stored_power, temp); seen_mantissa = TRUE; sprintf(dest, temp, mantissa); break; } /*}}} */ /*{{{ t --- base-10 mantissa */ case 't': { double mantissa; t[0] = 'f'; t[1] = 0; mant_exp(1.0, x, FALSE, &mantissa, &stored_power, temp); seen_mantissa = TRUE; sprintf(dest, temp, mantissa); break; } /*}}} */ /*{{{ s --- base-1000 / 'scientific' mantissa */ case 's': { double mantissa; t[0] = 'f'; t[1] = 0; mant_exp(1.0, x, TRUE, &mantissa, &stored_power, temp); seen_mantissa = TRUE; sprintf(dest, temp, mantissa); break; } /*}}} */ /*{{{ L --- power to current log base */ case 'L': { int power; t[0] = 'd'; t[1] = 0; if (seen_mantissa) power = stored_power; else mant_exp(log10_base, x, FALSE, NULL, &power, "%.0f"); sprintf(dest, temp, power); break; } /*}}} */ /*{{{ T --- power of ten */ case 'T': { int power; t[0] = 'd'; t[1] = 0; if (seen_mantissa) power = stored_power; else mant_exp(1.0, x, FALSE, NULL, &power, "%.0f"); sprintf(dest, temp, power); break; } /*}}} */ /*{{{ S --- power of 1000 / 'scientific' */ case 'S': { int power; t[0] = 'd'; t[1] = 0; if (seen_mantissa) power = stored_power; else mant_exp(1.0, x, TRUE, NULL, &power, "%.0f"); sprintf(dest, temp, power); break; } /*}}} */ /*{{{ c --- ISO decimal unit prefix letters */ case 'c': { int power; t[0] = 'c'; t[1] = 0; if (seen_mantissa) power = stored_power; else mant_exp(1.0, x, TRUE, NULL, &power, "%.0f"); if (power >= -18 && power <= 18) { /* -18 -> 0, 0 -> 6, +18 -> 12, ... */ /* HBB 20010121: avoid division of -ve ints! */ power = (power + 18) / 3; sprintf(dest, temp, "afpnum kMGTPE"[power]); } else { /* please extend the range ! */ /* name power name power ------------------------- atto -18 Exa 18 femto -15 Peta 15 pico -12 Tera 12 nano -9 Giga 9 micro -6 Mega 6 milli -3 kilo 3 */ /* for the moment, print e+21 for example */ sprintf(dest, "e%+02d", (power - 6) * 3); } break; } /*}}} */ /*{{{ P --- multiple of pi */ case 'P': { t[0] = 'f'; t[1] = 0; sprintf(dest, temp, x / M_PI); break; } /*}}} */ default: reset_numeric_locale(); int_error(NO_CARET, "Bad format character"); } /* switch */ /*}}} */ /* change decimal `.' to the actual entry in decimalsign */ if (decimalsign != NULL) { char *dotpos1 = dest, *dotpos2; size_t newlength = strlen(decimalsign); int dot; /* dot is the default decimalsign we will be replacing */ dot = *get_decimal_locale(); /* replace every dot by the contents of decimalsign */ while ((dotpos2 = strchr(dotpos1,dot)) != NULL) { size_t taillength = strlen(dotpos2); dotpos1 = dotpos2 + newlength; /* test if the new value for dest would be too long */ if (dotpos1 - dest + taillength > count) int_error(NO_CARET, "format too long due to long decimalsign string"); /* move tail end of string out of the way */ memmove(dotpos1, dotpos2 + 1, taillength); /* insert decimalsign */ memcpy(dotpos2, decimalsign, newlength); } /* clear temporary variables for safety */ dotpos1=NULL; dotpos2=NULL; } /* this was at the end of every single case, before: */ dest += strlen(dest); ++format; } /* for ever */ reset_numeric_locale(); }
/* EAM Nov 2012: * Unbelievably, the count parameter has been silently ignored or * improperly applied ever since this routine was introduced back * in version 3.7. Now fixed to prevent buffer overflow. */ void gprintf( char *outstring, size_t count, char *format, double log10_base, double x) { char tempdest[MAX_LINE_LEN + 1]; char temp[MAX_LINE_LEN + 1]; char *t; TBOOLEAN seen_mantissa = FALSE; /* remember if mantissa was already output */ double stored_power_base = 0; /* base for the last mantissa output*/ int stored_power = 0; /* power matching the mantissa output earlier */ TBOOLEAN got_hash = FALSE; char *dest = &tempdest[0]; char *limit = &tempdest[MAX_LINE_LEN]; #define remaining_space (size_t)(limit-dest) *dest = '\0'; set_numeric_locale(); for (;;) { /*{{{ copy to dest until % */ while (*format != '%') if (!(*dest++ = *format++) || (remaining_space == 0)) { safe_strncpy(outstring,tempdest,count); reset_numeric_locale(); return; /* end of format */ } /*}}} */ /*{{{ check for %% */ if (format[1] == '%') { *dest++ = '%'; format += 2; continue; } /*}}} */ /*{{{ copy format part to temp, excluding conversion character */ t = temp; *t++ = '%'; if (format[1] == '#') { *t++ = '#'; format++; got_hash = TRUE; } /* dont put isdigit first since sideeffect in macro is bad */ while (*++format == '.' || isdigit((unsigned char) *format) || *format == '-' || *format == '+' || *format == ' ' || *format == '\'') *t++ = *format; /*}}} */ /*{{{ convert conversion character */ switch (*format) { /*{{{ x and o */ case 'x': case 'X': case 'o': case 'O': if (fabs(x) >= (double)INT_MAX) { t[0] = 'l'; t[1] = 'l'; t[2] = *format; t[3] = '\0'; snprintf(dest, remaining_space, temp, (long long) x); } else { t[0] = *format; t[1] = '\0'; snprintf(dest, remaining_space, temp, (int) x); } break; /*}}} */ /*{{{ e, f and g */ case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': t[0] = *format; t[1] = 0; snprintf(dest, remaining_space, temp, x); break; /*}}} */ /*{{{ l --- mantissa to current log base */ case 'l': { double mantissa; t[0] = 'f'; t[1] = 0; stored_power_base = log10_base; mant_exp(stored_power_base, x, FALSE, &mantissa, &stored_power, temp); seen_mantissa = TRUE; snprintf(dest, remaining_space, temp, mantissa); break; } /*}}} */ /*{{{ t --- base-10 mantissa */ case 't': { double mantissa; t[0] = 'f'; t[1] = 0; stored_power_base = 1.0; mant_exp(stored_power_base, x, FALSE, &mantissa, &stored_power, temp); seen_mantissa = TRUE; snprintf(dest, remaining_space, temp, mantissa); break; } /*}}} */ /*{{{ s --- base-1000 / 'scientific' mantissa */ case 's': { double mantissa; t[0] = 'f'; t[1] = 0; stored_power_base = 1.0; mant_exp(stored_power_base, x, TRUE, &mantissa, &stored_power, temp); seen_mantissa = TRUE; snprintf(dest, remaining_space, temp, mantissa); break; } /*}}} */ /*{{{ b --- base-1024 mantissa */ case 'b': { double mantissa; t[0] = 'f'; t[1] = 0; stored_power_base = log10(1024); mant_exp(stored_power_base, x, FALSE, &mantissa, &stored_power, temp); seen_mantissa = TRUE; snprintf(dest, remaining_space, temp, mantissa); break; } /*}}} */ /*{{{ L --- power to current log base */ case 'L': { int power; t[0] = 'd'; t[1] = 0; if (seen_mantissa) if (stored_power_base == log10_base) power = stored_power; else int_error(NO_CARET, "Format character mismatch: %%L is only valid with %%l"); else stored_power_base = log10_base; mant_exp(log10_base, x, FALSE, NULL, &power, "%.0f"); snprintf(dest, remaining_space, temp, power); break; } /*}}} */ /*{{{ T --- power of ten */ case 'T': { int power; t[0] = 'd'; t[1] = 0; if (seen_mantissa) if (stored_power_base == 1.0) power = stored_power; else int_error(NO_CARET, "Format character mismatch: %%T is only valid with %%t"); else mant_exp(1.0, x, FALSE, NULL, &power, "%.0f"); snprintf(dest, remaining_space, temp, power); break; } /*}}} */ /*{{{ S --- power of 1000 / 'scientific' */ case 'S': { int power; t[0] = 'd'; t[1] = 0; if (seen_mantissa) if (stored_power_base == 1.0) power = stored_power; else int_error(NO_CARET, "Format character mismatch: %%S is only valid with %%s"); else mant_exp(1.0, x, TRUE, NULL, &power, "%.0f"); snprintf(dest, remaining_space, temp, power); break; } /*}}} */ /*{{{ c --- ISO decimal unit prefix letters */ case 'c': { int power; t[0] = 'c'; t[1] = 0; if (seen_mantissa) if (stored_power_base == 1.0) power = stored_power; else int_error(NO_CARET, "Format character mismatch: %%c is only valid with %%s"); else mant_exp(1.0, x, TRUE, NULL, &power, "%.0f"); if (power >= -24 && power <= 24) { /* -18 -> 0, 0 -> 6, +18 -> 12, ... */ /* HBB 20010121: avoid division of -ve ints! */ power = (power + 24) / 3; snprintf(dest, remaining_space, temp, "yzafpnum kMGTPEZY"[power]); } else { /* please extend the range ! */ /* name power name power ------------------------- yocto -24 yotta 24 zepto -21 zetta 21 atto -18 Exa 18 femto -15 Peta 15 pico -12 Tera 12 nano -9 Giga 9 micro -6 Mega 6 milli -3 kilo 3 */ /* fall back to simple exponential */ snprintf(dest, remaining_space, "e%+02d", power); } break; } /*}}} */ /*{{{ B --- IEC 60027-2 A.2 / ISO/IEC 80000 binary unit prefix letters */ case 'B': { int power; t[0] = 'c'; t[1] = 'i'; t[2] = 0; if (seen_mantissa) if (stored_power_base == log10(1024)) power = stored_power; else int_error(NO_CARET, "Format character mismatch: %%B is only valid with %%b"); else mant_exp(log10(1024), x, FALSE, NULL, &power, "%.0f"); if (power > 0 && power <= 8) { /* name power ----------- Yobi 8 Zebi 7 Exbi 9 Pebi 5 Tebi 4 Gibi 3 Mebi 2 kibi 1 */ snprintf(dest, remaining_space, temp, " kMGTPEZY"[power]); } else if (power > 8) { /* for the larger values, print x2^{10}Gi for example */ snprintf(dest, remaining_space, "x2^{%d}Yi", power-8); } else if (power < 0) { snprintf(dest, remaining_space, "x2^{%d}", power*10); } break; } /*}}} */ /*{{{ P --- multiple of pi */ case 'P': { t[0] = 'f'; t[1] = 0; snprintf(dest, remaining_space, temp, x / M_PI); break; } /*}}} */ default: reset_numeric_locale(); int_error(NO_CARET, "Bad format character"); } /* switch */ /*}}} */ if (got_hash && (format != strpbrk(format,"oeEfFgG"))) { reset_numeric_locale(); int_error(NO_CARET, "Bad format character"); } /* change decimal `.' to the actual entry in decimalsign */ if (decimalsign != NULL) { char *dotpos1 = dest, *dotpos2; size_t newlength = strlen(decimalsign); int dot; /* dot is the default decimalsign we will be replacing */ dot = *get_decimal_locale(); /* replace every dot by the contents of decimalsign */ while ((dotpos2 = strchr(dotpos1,dot)) != NULL) { size_t taillength = strlen(dotpos2); dotpos1 = dotpos2 + newlength; /* test if the new value for dest would be too long */ if (dotpos1 - dest + taillength > count) int_error(NO_CARET, "format too long due to long decimalsign string"); /* move tail end of string out of the way */ memmove(dotpos1, dotpos2 + 1, taillength); /* insert decimalsign */ memcpy(dotpos2, decimalsign, newlength); } /* clear temporary variables for safety */ dotpos1=NULL; dotpos2=NULL; } /* this was at the end of every single case, before: */ dest += strlen(dest); ++format; } /* for ever */ /* Copy as much as fits */ safe_strncpy(outstring, tempdest, count); reset_numeric_locale(); }
/* EAM Nov 2012: * Unbelievably, the count parameter has been silently ignored or * improperly applied ever since this routine was introduced back * in version 3.7. Now fixed to prevent buffer overflow. */ void gprintf( char *outstring, size_t count, char *format, double log10_base, double x) { char tempdest[MAX_LINE_LEN + 1]; char temp[MAX_LINE_LEN + 1]; char *t; TBOOLEAN seen_mantissa = FALSE; /* remember if mantissa was already output */ double stored_power_base = 0; /* base for the last mantissa output*/ int stored_power = 0; /* power matching the mantissa output earlier */ TBOOLEAN got_hash = FALSE; char *dest = &tempdest[0]; char *limit = &tempdest[MAX_LINE_LEN]; static double log10_of_1024; /* to avoid excess precision comparison in check of connection %b -- %B */ log10_of_1024 = log10(1024); #define remaining_space (size_t)(limit-dest) *dest = '\0'; set_numeric_locale(); /* Oct 2013 - default format is now expected to be "%h" */ if (((term->flags & TERM_IS_LATEX)) && !strcmp(format, DEF_FORMAT)) format = DEF_FORMAT_LATEX; for (;;) { /*{{{ copy to dest until % */ while (*format != '%') if (!(*dest++ = *format++) || (remaining_space == 0)) { goto done; } /*}}} */ /*{{{ check for %% */ if (format[1] == '%') { *dest++ = '%'; format += 2; continue; } /*}}} */ /*{{{ copy format part to temp, excluding conversion character */ t = temp; *t++ = '%'; if (format[1] == '#') { *t++ = '#'; format++; got_hash = TRUE; } /* dont put isdigit first since side effect in macro is bad */ while (*++format == '.' || isdigit((unsigned char) *format) || *format == '-' || *format == '+' || *format == ' ' || *format == '\'') *t++ = *format; /*}}} */ /*{{{ convert conversion character */ switch (*format) { /*{{{ x and o */ case 'x': case 'X': case 'o': case 'O': if (fabs(x) >= (double)INT_MAX) { t[0] = 'l'; t[1] = 'l'; t[2] = *format; t[3] = '\0'; snprintf(dest, remaining_space, temp, (long long) x); } else { t[0] = *format; t[1] = '\0'; snprintf(dest, remaining_space, temp, (int) x); } break; /*}}} */ /*{{{ e, f and g */ case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': t[0] = *format; t[1] = 0; snprintf(dest, remaining_space, temp, x); break; case 'h': case 'H': /* g/G with enhanced formating (if applicable) */ t[0] = (*format == 'h') ? 'g' : 'G'; t[1] = 0; if ((term->flags & (TERM_ENHANCED_TEXT | TERM_IS_LATEX)) == 0) { /* Not enhanced, not latex, just print it */ snprintf(dest, remaining_space, temp, x); } else if (table_mode) { /* Tabular output should contain no markup */ snprintf(dest, remaining_space, temp, x); } else { /* in enhanced mode -- convert E/e to x10^{foo} or *10^{foo} */ #define LOCAL_BUFFER_SIZE 256 char tmp[LOCAL_BUFFER_SIZE]; char tmp2[LOCAL_BUFFER_SIZE]; int i,j; TBOOLEAN bracket_flag = FALSE; snprintf(tmp, 240, temp, x); /* magic number alert: why 240? */ for (i=j=0; tmp[i] && (i < LOCAL_BUFFER_SIZE); i++) { if (tmp[i]=='E' || tmp[i]=='e') { if ((term-> flags & TERM_IS_LATEX)) { if (*format == 'h') { strcpy(&tmp2[j], "\\times"); j+= 6; } else { strcpy(&tmp2[j], "\\cdot"); j+= 5; } } else switch (encoding) { case S_ENC_UTF8: strcpy(&tmp2[j], "\xc3\x97"); /* UTF character '×' */ j+= 2; break; case S_ENC_CP1252: tmp2[j++] = (*format=='h') ? 0xd7 : 0xb7; break; case S_ENC_ISO8859_1: case S_ENC_ISO8859_2: case S_ENC_ISO8859_9: case S_ENC_ISO8859_15: tmp2[j++] = (*format=='h') ? 0xd7 : '*'; break; default: tmp2[j++] = (*format=='h') ? 'x' : '*'; break; } strcpy(&tmp2[j], "10^{"); j += 4; bracket_flag = TRUE; /* Skip + and leading 0 in exponent */ i++; /* skip E */ if (tmp[i] == '+') i++; else if (tmp[i] == '-') /* copy sign */ tmp2[j++] = tmp[i++]; while (tmp[i] == '0') i++; i--; /* undo following loop increment */ } else { tmp2[j++] = tmp[i]; } } if (bracket_flag) tmp2[j++] = '}'; tmp2[j] = '\0'; strncpy(dest, tmp2, remaining_space); #undef LOCAL_BUFFER_SIZE } break; /*}}} */ /*{{{ l --- mantissa to current log base */ case 'l': { double mantissa; t[0] = 'f'; t[1] = 0; stored_power_base = log10_base; mant_exp(stored_power_base, x, FALSE, &mantissa, &stored_power, temp); seen_mantissa = TRUE; snprintf(dest, remaining_space, temp, mantissa); break; } /*}}} */ /*{{{ t --- base-10 mantissa */ case 't': { double mantissa; t[0] = 'f'; t[1] = 0; stored_power_base = 1.0; mant_exp(stored_power_base, x, FALSE, &mantissa, &stored_power, temp); seen_mantissa = TRUE; snprintf(dest, remaining_space, temp, mantissa); break; } /*}}} */ /*{{{ s --- base-1000 / 'scientific' mantissa */ case 's': { double mantissa; t[0] = 'f'; t[1] = 0; stored_power_base = 1.0; mant_exp(stored_power_base, x, TRUE, &mantissa, &stored_power, temp); seen_mantissa = TRUE; snprintf(dest, remaining_space, temp, mantissa); break; } /*}}} */ /*{{{ b --- base-1024 mantissa */ case 'b': { double mantissa; t[0] = 'f'; t[1] = 0; stored_power_base = log10_of_1024; mant_exp(stored_power_base, x, FALSE, &mantissa, &stored_power, temp); seen_mantissa = TRUE; snprintf(dest, remaining_space, temp, mantissa); break; } /*}}} */ /*{{{ L --- power to current log base */ case 'L': { int power; t[0] = 'd'; t[1] = 0; if (seen_mantissa) { if (stored_power_base == log10_base) { power = stored_power; } else { int_error(NO_CARET, "Format character mismatch: %%L is only valid with %%l"); } } else { stored_power_base = log10_base; mant_exp(log10_base, x, FALSE, NULL, &power, "%.0f"); } snprintf(dest, remaining_space, temp, power); break; } /*}}} */ /*{{{ T --- power of ten */ case 'T': { int power; t[0] = 'd'; t[1] = 0; if (seen_mantissa) { if (stored_power_base == 1.0) { power = stored_power; } else { int_error(NO_CARET, "Format character mismatch: %%T is only valid with %%t"); } } else { mant_exp(1.0, x, FALSE, NULL, &power, "%.0f"); } snprintf(dest, remaining_space, temp, power); break; } /*}}} */ /*{{{ S --- power of 1000 / 'scientific' */ case 'S': { int power; t[0] = 'd'; t[1] = 0; if (seen_mantissa) { if (stored_power_base == 1.0) { power = stored_power; } else { int_error(NO_CARET, "Format character mismatch: %%S is only valid with %%s"); } } else { mant_exp(1.0, x, TRUE, NULL, &power, "%.0f"); } snprintf(dest, remaining_space, temp, power); break; } /*}}} */ /*{{{ c --- ISO decimal unit prefix letters */ case 'c': { int power; t[0] = 'c'; t[1] = 0; if (seen_mantissa) { if (stored_power_base == 1.0) { power = stored_power; } else { int_error(NO_CARET, "Format character mismatch: %%c is only valid with %%s"); } } else { mant_exp(1.0, x, TRUE, NULL, &power, "%.0f"); } if (power >= -24 && power <= 24) { /* name power name power ------------------------- yocto -24 yotta 24 zepto -21 zetta 21 atto -18 Exa 18 femto -15 Peta 15 pico -12 Tera 12 nano -9 Giga 9 micro -6 Mega 6 milli -3 kilo 3 */ /* -18 -> 0, 0 -> 6, +18 -> 12, ... */ /* HBB 20010121: avoid division of -ve ints! */ power = (power + 24) / 3; snprintf(dest, remaining_space, temp, "yzafpnum kMGTPEZY"[power]); } else { /* please extend the range ! */ /* fall back to simple exponential */ snprintf(dest, remaining_space, "e%+02d", power); } break; } /*}}} */ /*{{{ B --- IEC 60027-2 A.2 / ISO/IEC 80000 binary unit prefix letters */ case 'B': { int power; t[0] = 'c'; t[1] = 'i'; t[2] = '\0'; if (seen_mantissa) { if (stored_power_base == log10_of_1024) { power = stored_power; } else { int_error(NO_CARET, "Format character mismatch: %%B is only valid with %%b"); } } else { mant_exp(log10_of_1024, x, FALSE, NULL, &power, "%.0f"); } if (power > 0 && power <= 8) { /* name power ----------- Yobi 8 Zebi 7 Exbi 9 Pebi 5 Tebi 4 Gibi 3 Mebi 2 kibi 1 */ snprintf(dest, remaining_space, temp, " kMGTPEZY"[power]); } else if (power > 8) { /* for the larger values, print x2^{10}Gi for example */ snprintf(dest, remaining_space, "x2^{%d}Yi", power-8); } else if (power < 0) { snprintf(dest, remaining_space, "x2^{%d}", power*10); } else { snprintf(dest, remaining_space, " "); } break; } /*}}} */ /*{{{ P --- multiple of pi */ case 'P': { t[0] = 'f'; t[1] = 0; snprintf(dest, remaining_space, temp, x / M_PI); break; } /*}}} */ default: reset_numeric_locale(); int_error(NO_CARET, "Bad format character"); } /* switch */ /*}}} */ if (got_hash && (format != strpbrk(format,"oeEfFgG"))) { reset_numeric_locale(); int_error(NO_CARET, "Bad format character"); } /* change decimal '.' to the actual entry in decimalsign */ if (decimalsign != NULL) { char *dotpos1 = dest; char *dotpos2; size_t newlength = strlen(decimalsign); /* dot is the default decimalsign we will be replacing */ int dot = *get_decimal_locale(); /* replace every dot by the contents of decimalsign */ while ((dotpos2 = strchr(dotpos1,dot)) != NULL) { if (newlength == 1) { /* The normal case */ *dotpos2 = *decimalsign; dotpos1++; } else { /* Some multi-byte decimal marker */ size_t taillength = strlen(dotpos2); dotpos1 = dotpos2 + newlength; if (dotpos1 + taillength > limit) int_error(NO_CARET, "format too long due to decimalsign string"); /* move tail end of string out of the way */ memmove(dotpos1, dotpos2 + 1, taillength); /* insert decimalsign */ memcpy(dotpos2, decimalsign, newlength); } } } /* this was at the end of every single case, before: */ dest += strlen(dest); ++format; } /* for ever */ done: #if (0) /* Oct 2013 - Not safe because it fails to recognize LaTeX macros. */ /* For LaTeX terminals, if the user has not already provided a */ /* format in math mode, wrap whatever we got by default in $...$ */ if (((term->flags & TERM_IS_LATEX)) && !strchr(tempdest, '$')) { *(outstring++) = '$'; strcat(tempdest, "$"); count -= 2; } #endif /* Copy as much as fits */ safe_strncpy(outstring, tempdest, count); reset_numeric_locale(); }
void statsrequest(void) { int i; int columns; int columnsread; double v[2]; static char *file_name = NULL; /* Vars to hold data and results */ long n; /* number of records retained */ long max_n; static double *data_x = NULL; static double *data_y = NULL; /* values read from file */ long invalid; /* number of missing/invalid records */ long blanks; /* number of blank lines */ long doubleblanks; /* number of repeated blank lines */ long out_of_range; /* number pts rejected, because out of range */ struct file_stats res_file; struct sgl_column_stats res_x, res_y; struct two_column_stats res_xy; float *matrix; /* matrix data. This must be float. */ int nc, nr; /* matrix dimensions. */ int index; /* Vars for variable handling */ static char *prefix = NULL; /* prefix for user-defined vars names */ /* Vars that control output */ TBOOLEAN do_output = TRUE; /* Generate formatted output */ c_token++; /* Parse ranges */ AXIS_INIT2D(FIRST_X_AXIS, 0); AXIS_INIT2D(FIRST_Y_AXIS, 0); parse_range(FIRST_X_AXIS); parse_range(FIRST_Y_AXIS); /* Initialize */ columnsread = 2; invalid = 0; /* number of missing/invalid records */ blanks = 0; /* number of blank lines */ doubleblanks = 0; /* number of repeated blank lines */ out_of_range = 0; /* number pts rejected, because out of range */ n = 0; /* number of records retained */ nr = 0; /* Matrix dimensions */ nc = 0; max_n = INITIAL_DATA_SIZE; free(data_x); free(data_y); data_x = vec(max_n); /* start with max. value */ data_y = vec(max_n); if ( !data_x || !data_y ) int_error( NO_CARET, "Internal error: out of memory in stats" ); n = invalid = blanks = doubleblanks = out_of_range = nr = 0; /* Get filename */ free ( file_name ); file_name = try_to_get_string(); if ( !file_name ) int_error(c_token, "missing filename"); /* =========================================================== v923z: insertion for treating matrices EAM: only handles ascii matrix with uniform grid, and fails to apply any input data transforms =========================================================== */ if ( almost_equals(c_token, "mat$rix") ) { df_open(file_name, 3, NULL); index = df_num_bin_records - 1; /* We take these values as set by df_determine_matrix_info See line 1996 in datafile.c */ nc = df_bin_record[index].scan_dim[0]; nr = df_bin_record[index].scan_dim[1]; n = nc * nr; matrix = (float *)df_bin_record[index].memory_data; /* Fill up a vector, so that we can use the existing code. */ if ( !redim_vec(&data_x, n ) ) { int_error( NO_CARET, "Out of memory in stats: too many datapoints (%d)?", n ); } for( i=0; i < n; i++ ) { data_y[i] = (double)matrix[i]; } /* We can close the file here, there is nothing else to do */ df_close(); /* We will invoke single column statistics for the matrix */ columns = 1; } else { /* Not a matrix */ columns = df_open(file_name, 2, NULL); /* up to 2 using specs allowed */ if (columns < 0) int_error(NO_CARET, "Can't read data file"); if (columns > 2 ) int_error(c_token, "Need 0 to 2 using specs for stats command"); /* If the user has set an explicit locale for numeric input, apply it here so that it affects data fields read from the input file. */ /* v923z: where exactly should this be? here or before the matrix case? * I think, we should move everything here to before trying to open the file. * There is no point in trying to read anything, if the axis is logarithmic, e.g. */ set_numeric_locale(); /* For all these below: we could save the state, switch off, then restore */ if ( axis_array[FIRST_X_AXIS].log || axis_array[FIRST_Y_AXIS].log ) int_error( NO_CARET, "Stats command not available with logscale active"); if ( axis_array[FIRST_X_AXIS].datatype == DT_TIMEDATE || axis_array[FIRST_Y_AXIS].datatype == DT_TIMEDATE ) int_error( NO_CARET, "Stats command not available in timedata mode"); if ( polar ) int_error( NO_CARET, "Stats command not available in polar mode" ); if ( parametric ) int_error( NO_CARET, "Stats command not available in parametric mode" ); /* The way readline and friends work is as follows: - df_open will return the number of columns requested in the using spec so that "columns" will be 0, 1, or 2 (no using, using 1, using 1:2) - readline always returns the same number of columns (for us: 1 or 2) - using 1:2 = return two columns, skipping lines w/ bad data - using 1 = return sgl column (supply zeros (0) for the second col) - no using = return two columns (first two), fail on bad data We need to know how many columns to process. If columns==1 or ==2 (that is, if there was a using spec), all is clear and we use the value of columns. But: if columns is 0, then we need to figure out the number of cols read from the return value of readline. If readline ever returns 1, we take that; only if it always returns 2 do we assume two cols. */ while( (i = df_readline(v, 2)) != DF_EOF ) { columnsread = ( i > columnsread ? i : columnsread ); if ( n >= max_n ) { max_n = (max_n * 3) / 2; /* increase max_n by factor of 1.5 */ /* Some of the reallocations went bad: */ if ( 0 || !redim_vec(&data_x, max_n) || !redim_vec(&data_y, max_n) ) { df_close(); int_error( NO_CARET, "Out of memory in stats: too many datapoints (%d)?", max_n ); } } /* if (need to extend storage space) */ switch (i) { case DF_MISSING: case DF_UNDEFINED: /* Invalids are only recognized if the syntax is like this: stats "file" using ($1):($2) If the syntax is simply: stats "file" using 1:2 then df_readline simply skips invalid records (does not return anything!) Status: 2009-11-02 */ invalid += 1; continue; case DF_FIRST_BLANK: blanks += 1; continue; case DF_SECOND_BLANK: blanks += 1; doubleblanks += 1; continue; case 0: int_error( NO_CARET, "bad data on line %d of file %s", df_line_number, df_filename ? df_filename : "" ); break; case 1: /* Read single column successfully */ if ( validate_data(v[0], FIRST_Y_AXIS) ) { data_y[n] = v[0]; n++; } else { out_of_range++; } break; case 2: /* Read two columns successfully */ if ( validate_data(v[0], FIRST_X_AXIS) && validate_data(v[1], FIRST_Y_AXIS) ) { data_x[n] = v[0]; data_y[n] = v[1]; n++; } else { out_of_range++; } break; } } /* end-while : done reading file */ df_close(); /* now resize fields to actual length: */ redim_vec(&data_x, n); redim_vec(&data_y, n); /* figure out how many columns where really read... */ if ( columns == 0 ) columns = columnsread; } /* end of case when the data file is not a matrix */ /* Now finished reading user input; return to C locale for internal use*/ reset_numeric_locale(); /* PKJ - TODO: similar for logscale, polar/parametric, timedata */ /* No data! Try to explain why. */ if ( n == 0 ) { if ( out_of_range > 0 ) int_error( NO_CARET, "All points out of range" ); else int_error( NO_CARET, "No valid data points found in file" ); } /* Parse the remainder of the command line: 0 to 2 tokens possible */ while( !(END_OF_COMMAND) ) { if ( almost_equals( c_token, "out$put" ) ) { do_output = TRUE; c_token++; } else if ( almost_equals( c_token, "noout$put" ) ) { do_output = FALSE; c_token++; } else if ( almost_equals(c_token, "pre$fix") || equals(c_token, "name")) { c_token++; free ( prefix ); prefix = try_to_get_string(); if (!legal_identifier(prefix) || !strcmp ("GPVAL_", prefix)) int_error( --c_token, "illegal prefix" ); } else { int_error( c_token, "Expecting [no]output or prefix"); } } /* Set defaults if not explicitly set by user */ if (!prefix) prefix = gp_strdup("STATS_"); i = strlen(prefix); if (prefix[i-1] != '_') { prefix = gp_realloc(prefix, i+2, "prefix"); strcat(prefix,"_"); } /* Do the actual analysis */ res_file = analyze_file( n, out_of_range, invalid, blanks, doubleblanks ); if ( columns == 1 ) { res_y = analyze_sgl_column( data_y, n, nr ); } if ( columns == 2 ) { /* If there are two columns, then the data file is not a matrix */ res_x = analyze_sgl_column( data_x, n, 0 ); res_y = analyze_sgl_column( data_y, n, 0 ); res_xy = analyze_two_columns( data_x, data_y, res_x, res_y, n ); } /* Store results in user-accessible variables */ /* Clear out any previous use of these variables */ del_udv_by_name( prefix, TRUE ); file_variables( res_file, prefix ); if ( columns == 1 ) { sgl_column_variables( res_y, prefix, "" ); } if ( columns == 2 ) { sgl_column_variables( res_x, prefix, "_x" ); sgl_column_variables( res_y, prefix, "_y" ); two_column_variables( res_xy, prefix ); } /* Output */ if ( do_output ) { file_output( res_file ); if ( columns == 1 ) sgl_column_output( res_y, res_file.records ); else two_column_output( res_x, res_y, res_xy, res_file.records ); } /* Cleanup */ free(data_x); free(data_y); data_x = NULL; data_y = NULL; free( file_name ); file_name = NULL; free( prefix ); prefix = NULL; }