From 6b6ed1de7e41fe0683ec458bf8f455a159a2a653 Mon Sep 17 00:00:00 2001 From: Friedrich Beckmann Date: Sun, 7 Jun 2015 23:10:07 +0200 Subject: [PATCH] replaced decimal module, xrchart_scale with autoformat, histogram x-axis ticks changed. During gtk3 osx debugging the decimal unit crashed. I replaced the decimal computations with traditional tick interval computation plus a printf format string generation which is adapted to the chosen tick style. The xrchart scales writing are adapted to use this mechanism. If the tick text is too long to fit in the interval, then the tick label uses the existing rotation by 45 degrees. I added some space below the x-axis to make the tick labels visible. Maybe the tick label placement could also be done in two rows for the even and odd ticks. The histogram x-axis tick generation is changed from a tick per bin to the usual normal x-axis tick generation. This allows easier visual bin border identification. The bin width is adapted to the chosen x-axis tick scale. Any number of bins can now be visualized. This is according to the spss examples I found for the histograms. --- src/math/automake.mk | 1 - src/math/chart-geometry.c | 147 ++---- src/math/chart-geometry.h | 7 +- src/math/decimal.c | 572 ----------------------- src/math/decimal.h | 114 ----- src/math/histogram.c | 192 ++------ src/output/cairo-chart.c | 103 ++-- src/output/cairo-chart.h | 5 - src/output/charts/plot-hist-cairo.c | 84 ++-- tests/automake.mk | 21 +- tests/math/chart-geometry-test.c | 61 --- tests/math/chart-geometry.at | 83 ++-- tests/math/chart-get-scale-test.c | 61 +-- tests/math/chart-get-ticks-format-test.c | 46 +- tests/math/decimal-test.c | 348 -------------- tests/math/decimal.at | 7 - 16 files changed, 257 insertions(+), 1595 deletions(-) delete mode 100644 src/math/decimal.c delete mode 100644 src/math/decimal.h delete mode 100644 tests/math/chart-geometry-test.c delete mode 100644 tests/math/decimal-test.c delete mode 100644 tests/math/decimal.at diff --git a/src/math/automake.mk b/src/math/automake.mk index f40aac57e1..cdcc2c8cb7 100644 --- a/src/math/automake.mk +++ b/src/math/automake.mk @@ -17,7 +17,6 @@ src_math_libpspp_math_la_SOURCES = \ src/math/covariance.h \ src/math/correlation.c \ src/math/correlation.h \ - src/math/decimal.c src/math/decimal.h \ src/math/extrema.c src/math/extrema.h \ src/math/histogram.c src/math/histogram.h \ src/math/interaction.c src/math/interaction.h \ diff --git a/src/math/chart-geometry.c b/src/math/chart-geometry.c index 49b1ed6a8d..1265a61eb8 100644 --- a/src/math/chart-geometry.c +++ b/src/math/chart-geometry.c @@ -20,7 +20,6 @@ #include #include "chart-geometry.h" -#include "decimal.h" #include #include "gl/xalloc.h" @@ -29,47 +28,6 @@ static const double standard_tick[] = {1, 2, 5, 10}; -/* Adjust tick to be a sensible value - ie: ... 0.1,0.2,0.5, 1,2,5, 10,20,50 ... */ -void -chart_rounded_tick (double tick, struct decimal *result) -{ - int i; - - struct decimal ddif = {1, 1000}; - - /* Avoid arithmetic problems with very small values */ - if (fabs (tick) < DBL_EPSILON) - { - result->ordinate = 0; - result->mantissa = 0; - return; - } - - struct decimal dt; - decimal_from_double (&dt, tick); - - double expd = dec_log10 (&dt) - 1; - - for (i = 0 ; i < 4 ; ++i) - { - struct decimal candidate; - struct decimal delta; - - decimal_init (&candidate, standard_tick[i], expd); - - delta = dt; - decimal_subtract (&delta, &candidate); - delta.ordinate = llabs (delta.ordinate); - - if (decimal_cmp (&delta, &ddif) < 0) - { - ddif = delta; - *result = candidate; - } - } -} - /* Find a set {LOWER, INTERVAL, N_TICKS} such that: @@ -92,73 +50,48 @@ chart_rounded_tick (double tick, struct decimal *result) ^LOWDBL ^HIGHDBL */ void -chart_get_scale (double highdbl, double lowdbl, - struct decimal *lower, - struct decimal *interval, +chart_get_scale (double high, double low, + double *lower, double *interval, int *n_ticks) { int i; double fitness = DBL_MAX; + double logrange; *n_ticks = 0; - struct decimal high; - struct decimal low; - assert (highdbl >= lowdbl); + assert (high >= low); - decimal_from_double (&high, highdbl); - decimal_from_double (&low, lowdbl); - - struct decimal diff = high; - decimal_subtract (&diff, &low); + if ((high - low) < 10 * DBL_MIN) { + *n_ticks = 0; + *lower = low; + *interval = 0.0; + return; + } - double expd = dec_log10 (&diff) - 2; + logrange = floor(log10(high-low)); /* Find the most pleasing interval */ for (i = 1; i < 4; ++i) { - struct decimal clbound = low; - struct decimal cubound = high; - struct decimal candidate; - decimal_init (&candidate, standard_tick[i], expd); - - decimal_divide (&clbound, &candidate); - int fl = decimal_floor (&clbound); - decimal_int_multiply (&candidate, fl); - clbound = candidate; - - - decimal_init (&candidate, standard_tick[i], expd); - decimal_divide (&cubound, &candidate); - int fu = decimal_ceil (&cubound); - decimal_int_multiply (&candidate, fu); - - cubound = candidate; - - decimal_init (&candidate, standard_tick[i], expd); - decimal_subtract (&cubound, &clbound); - decimal_divide (&cubound, &candidate); - - - ord_t n_int = decimal_floor (&cubound); - - /* We prefer to have between 5 and 10 tick marks on a scale */ - double f = 1 - exp (-0.2 * fabs (n_int - 7.5) / 7.5); - - if (f < fitness) - { - fitness = f; - *lower = clbound; - *interval = candidate; - *n_ticks = n_int; - } + double cinterval = standard_tick[i] * pow(10.0,logrange-1); + double clower = floor(low/cinterval) * cinterval; + int cnticks = ceil((high - clower) / cinterval)-1; + double cfitness = fabs(7.5 - cnticks); + + if (cfitness < fitness) { + fitness = cfitness; + *lower = clower; + *interval = cinterval; + *n_ticks = cnticks; + } } } /* * Compute the optimum format string and the scaling * for the tick drawing on a chart axis - * Input: max: the maximum value of the range - * min: the minimum value of the range + * Input: lower: the lowest tick + * interval:the interval between the ticks * nticks: the number of tick intervals (bins) on the axis * Return: fs: format string for printf to print the tick value * scale: scaling factor for the tick value @@ -167,17 +100,15 @@ chart_get_scale (double highdbl, double lowdbl, * Non Scientific: "%.3lf", scale=1.00 * Scientific: "%.2lfe3", scale = 0.001 * Usage example: - * fs = chart_get_ticks_format(95359943.3,34434.9,8,&scale); + * fs = chart_get_ticks_format(-0.7,0.1,8,&scale); * printf(fs,value*scale); * free(fs); */ char * -chart_get_ticks_format (const double max, const double min, +chart_get_ticks_format (const double lower, const double interval, const unsigned int nticks, double *scale) { - assert(max > min); - double interval = (max - min)/nticks; - double logmax = log10(fmax(fabs(max),fabs(min))); + double logmax = log10(fmax(fabs(lower + (nticks+1)*interval),fabs(lower))); double logintv = log10(interval); int logshift = 0; char *format_string = NULL; @@ -185,13 +116,16 @@ chart_get_ticks_format (const double max, const double min, if (logmax > 0.0 && logintv < 0.0) { - nrdecs = MIN(6,(int)(fabs(logintv))+1); + nrdecs = MIN(6,(int)(ceil(fabs(logintv)))); logshift = 0; - format_string = xasprintf("%%.%dlf",nrdecs); + if (logmax < 12.0) + format_string = xasprintf("%%.%dlf",nrdecs); + else + format_string = xasprintf("%%lg"); } else if (logmax > 0.0) /*logintv is > 0*/ { - if (logintv < 5.0) + if (logintv < 5.0 && logmax < 10.0) { logshift = 0; /* No scientific format */ nrdecs = 0; @@ -200,23 +134,26 @@ chart_get_ticks_format (const double max, const double min, else { logshift = (int)logmax; - nrdecs = MIN(6,(int)(logmax-logintv)+1); - format_string = xasprintf("%%.%dlf∙10%d",nrdecs,logshift); + /* Possible intervals are 0.2Ex, 0.5Ex, 1.0Ex */ + /* log10(0.2E9) = 8.30, log10(0.5E9) = 8.69, log10(1.0E9) = 9 */ + /* 0.2 and 0.5 need one decimal more. For stability subtract 0.1 */ + nrdecs = MIN(8,(int)(ceil(logshift-logintv-0.1))); + format_string = xasprintf("%%.%dlf⋅10%d",nrdecs,logshift); } } else /* logmax and logintv are < 0 */ { - if (logmax > -4.0) + if (logmax > -3.0) { logshift = 0; /* No scientific format */ - nrdecs = (int)(-logintv) + 1; + nrdecs = MIN(8,(int)(ceil(-logintv))); format_string = xasprintf("%%.%dlf",nrdecs); } else { logshift = (int)logmax-1; - nrdecs = MIN(6,(int)(logmax-logintv)+1); - format_string = xasprintf("%%.%dlf∙10%d",nrdecs,logshift); + nrdecs = MIN(8,(int)(ceil(logshift-logintv-0.1))); + format_string = xasprintf("%%.%dlf⋅10%d",nrdecs,logshift); } } *scale = pow(10.0,-(double)logshift); diff --git a/src/math/chart-geometry.h b/src/math/chart-geometry.h index 67d35bb62f..3d3955e75e 100644 --- a/src/math/chart-geometry.h +++ b/src/math/chart-geometry.h @@ -18,14 +18,11 @@ #ifndef CHART_GEOMETRY_H #define CHART_GEOMETRY_H -struct decimal; -void chart_rounded_tick (double tick, struct decimal *); - void chart_get_scale (double high, double low, - struct decimal *lower, struct decimal *interval, int *n_ticks); + double *lower, double *interval, int *n_ticks); char * -chart_get_ticks_format (const double max, const double min, const unsigned int nticks, +chart_get_ticks_format (const double lower, const double interval, const unsigned int nticks, double *scale); #endif diff --git a/src/math/decimal.c b/src/math/decimal.c deleted file mode 100644 index 0655834225..0000000000 --- a/src/math/decimal.c +++ /dev/null @@ -1,572 +0,0 @@ -/* PSPP - a program for statistical analysis. - Copyright (C) 2015 Free Software Foundation, Inc. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . */ - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "libpspp/i18n.h" - -#include "decimal.h" - -int dec_warning; - -static bool -down (struct decimal *dec) -{ - if (dec->ordinate % 10 == 0 && dec->mantissa < MANT_MAX - 1) - { - dec->ordinate /= 10; - dec->mantissa++; - return true; - } - - return false; -} - -static bool -up (struct decimal *dec) -{ - if (llabs (dec->ordinate) < ORD_MAX / 10 && dec->mantissa > MANT_MIN) - { - dec->ordinate *= 10; - dec->mantissa--; - return true; - } - return false; -} - - -/* Reduce the absolute value of the ordinate to the smallest possible, - without loosing precision */ -static void -reduce (struct decimal *dec) -{ - if (dec->ordinate == 0) - { - dec->mantissa = 0; - return; - } - - while (dec->ordinate % 10 == 0) - { - if (! down (dec)) - break; - } -} - -/* Attempt to make the mantissas of BM and SM equal. - Prerequisite: the mantissa SM must be no greater than that of BM. - */ -static void -normalisebs (struct decimal *sm, struct decimal *bm) -{ - while (sm->mantissa < bm->mantissa) - { - if (down (sm)) - ; - else if (up (bm)) - ; - else - { - dec_warning = DEC_PREC; - break; - } - } - - while (sm->mantissa < bm->mantissa) - { - sm->ordinate /= 10; - sm->mantissa++; - } -} - - -/* arrange d1 and d2 such that thier mantissas are equal */ -void -normalise (struct decimal *d1, struct decimal *d2) -{ - normalisebs (d1, d2); - normalisebs (d2, d1); -} - - - -/* Return log base 10 of D */ -mant_t -dec_log10 (const struct decimal *d_) -{ - struct decimal d = *d_; - - while (llabs (d.ordinate) > 0) - { - d.ordinate /= 10; - d.mantissa++; - } - - return d.mantissa; -} - - - -/* Return the smallest integer >= d */ -static ord_t -decimal_ceil_pos (const struct decimal *d) -{ - mant_t m = d->mantissa; - ord_t o = d->ordinate; - - assert (d->ordinate >= 0); - - while (m > 0) - { - o *= 10; - m--; - } - - while (m < 0) - { - bool flag = o % 10; - o /= 10; - if (flag) - o++; - m++; - } - - return o; -} - -/* Return the largest integer <= d */ -static ord_t -decimal_floor_pos (const struct decimal *d) -{ - mant_t m = d->mantissa; - ord_t o = d->ordinate; - - assert (d->ordinate >= 0); - - while (m > 0) - { - m--; - o *= 10; - } - - while (m < 0) - { - m++; - o /= 10; - } - - - return o; -} - -/* Return the smallest integer which is no less than D. - (round towards minus infinity) */ -ord_t -decimal_floor (const struct decimal *d) -{ - if (d->ordinate >= 0) - return decimal_floor_pos (d); - else - { - struct decimal dd = *d; - dd.ordinate = llabs (dd.ordinate); - return -decimal_ceil_pos (&dd); - } -} - -/* Return the largest integer which is no greater than D. - (round towards plus infinity) */ -ord_t -decimal_ceil (const struct decimal *d) -{ - if (d->ordinate >= 0) - return decimal_ceil_pos (d); - else - { - struct decimal dd = *d; - dd.ordinate = llabs (dd.ordinate); - return -decimal_floor_pos (&dd); - } -} - -/* Add SRC onto DEST */ -void -decimal_add (struct decimal *dest, const struct decimal *src_) -{ - struct decimal src = *src_; - - src.ordinate = -src.ordinate; - - decimal_subtract (dest, &src); -} - -/* Subtract SRC from DEST */ -void -decimal_subtract (struct decimal *dest, const struct decimal *src_) -{ - struct decimal src = *src_; - - normalise (dest, &src); - - bool dest_neg = dest->ordinate < 0; - bool src_neg = src.ordinate < 0; - - bool expected_neg = dest_neg * src_neg; - - if (dest->ordinate == src.ordinate) - { - expected_neg = 0; - } - else if (llabs (src.ordinate) > llabs (dest->ordinate)) - { - if (dest_neg == src_neg) - expected_neg = !expected_neg; - } - - dest->ordinate -= src.ordinate; - - bool result_neg = dest->ordinate < 0; - - if (expected_neg != result_neg) - { - /* The operation has resulted in an overflow. - To resolve this, undo the operation, - reduce the precision and try again */ - - dest->ordinate += src.ordinate; - - dest->ordinate /= 10; - src.ordinate /= 10; - - dest->mantissa ++; - src.mantissa ++; - - dest->ordinate -= src.ordinate; - } - - reduce (dest); - -} - -/* Initialise DEC with ordinate ORD and mantissa MANT */ -void -decimal_init (struct decimal *dec, ord_t ord, mant_t mant) -{ - dec->ordinate = ord; - dec->mantissa = mant; - reduce (dec); -} - - -/* - Compare D1 and D2. - - Returns zero if equal, +1 if D1 > D2 and -1 if D1 < D2 -*/ -int -decimal_cmp (const struct decimal *d1, const struct decimal *d2) -{ - struct decimal td1 = *d1; - struct decimal td2 = *d2; - - normalise (&td1, &td2); - - if (td1.ordinate < td2.ordinate) - return -1; - - return (td1.ordinate > td2.ordinate); -} - - -/* Multiply DEST by M */ -void -decimal_int_multiply (struct decimal *dest, ord_t m) -{ - if (m != 0) - while (llabs (dest->ordinate) > llabs (ORD_MAX / m)) - { - dest->ordinate /= 10; - dest->mantissa++; - } - - dest->ordinate *= m; - - reduce (dest); -} - - -/* Divide DEST by M */ -void -decimal_int_divide (struct decimal *dest, ord_t m) -{ - while (dest->ordinate % m) - { - if (labs (dest->ordinate) > ORD_MAX / 10) - { - dec_warning = DEC_PREC; - break; - } - up (dest); - } - dest->ordinate /= m; -} - -/* Divide DEST by SRC */ -void -decimal_divide (struct decimal *dest, const struct decimal *src) -{ - while (dest->ordinate % src->ordinate) - { - if (labs (dest->ordinate) > ORD_MAX / 10) - { - dec_warning = DEC_PREC; - break; - } - up (dest); - } - - dest->ordinate /= src->ordinate; - dest->mantissa -= src->mantissa; -} - -/* Print the value of DEC to F. Probably useful only for debugging */ -void -decimal_show (const struct decimal *dec, FILE *f) -{ - fprintf (f, PR_ORD " x 10^" PR_MANT "\n", dec->ordinate, dec->mantissa); -} - - -/* Reverse the characters in string S which has length LEN */ -static void -reverse (char *s, int len) -{ - int i; - for (i = 0; i < len / 2; ++i) - { - char temp = s[len - i - 1]; - s[len - i - 1] = s[i]; - s[i] = temp; - } -} - -/* Return a string representation of DEC on the heap. - The caller is responsible for freeing the string */ -char * -decimal_to_string (const struct decimal *dec) -{ - int cap = 16; - int len = 0; - char *s = calloc (cap, 1); - ord_t ordinate = dec->ordinate; - - while (len < dec->mantissa) - { - s[len++] = '0'; - if (len >= cap) s = realloc (s, cap <<= 1); - } - - while (ordinate) - { - s[len++] = labs (ordinate % 10) + '0'; - if (len >= cap) s = realloc (s, cap <<= 1); - ordinate /= 10; - } - - if (ordinate < 0) - ordinate = -ordinate; - - while (len < -dec->mantissa) - { - s[len++] = '0'; - if (len >= cap) s = realloc (s, cap <<= 1); - } - - if (dec->mantissa < 0 ) - { - if (len <= -dec->mantissa) - { - s[len++] = get_system_decimal (); - if (len >= cap) s = realloc (s, cap <<= 1); - s[len++] = '0'; - if (len >= cap) s = realloc (s, cap <<= 1); - } - else - { - int i; - if (len >= cap) s = realloc (s, cap <<= 1); - for (i = len - 1 ; i >= -dec->mantissa ; --i) - s[i + 1] = s[i]; - s[i + 1] = get_system_decimal (); - len++; - } - } - - if (dec->ordinate < 0) - { - s[len++] = '-'; - if (len >= cap) s = realloc (s, cap <<= 1); - } - - - reverse (s, len); - - { - int abs_len = len; - if (dec->ordinate < 0) - abs_len--; - - while (abs_len++ <= dec->mantissa) - { - s[len++] = '0'; - if (len >= cap) s = realloc (s, cap <<= 1); - } - } - - return s; -} - - -/* Initialise DECIMAL from INPUT. - INPUT should be a convential decimal representation. - */ -void -decimal_init_from_string (struct decimal *decimal, const char *input) -{ - ord_t ordinate = 0; - - int point = -1; - int lsd = -1; - int fsd = -1; - int i = 0; - int len = 0; - int sign = 1; - - const char *p; - - for (p = input; *p ; p++) - { - if (*p == '-') - { - sign = -1; - } - else if (*p == get_system_decimal ()) - { - assert (point == -1); - point = i; - } - else if (*p > '0' && *p <= '9') - { - lsd = i; - if (fsd == -1) - fsd = i; - } - else if (*p == '0') - /* ignore */ - ; - else - { - fprintf (stderr, "Error: invalid character %c\n", *p); - return; - } - - i++; - } - len = i; - - if (point == -1) - point = len; - - mant_t mantissa = 0; - if (fsd >= 0) - { - mant_t m = 1; - for (i = lsd ; i >= fsd ; --i) - { - if (input[i] != get_system_decimal ()) - { - if (ordinate > ORD_MAX - m * (input[i] - '0')) - { - fprintf (stderr, "Overflow reading value %s\n", input); - break; - } - ordinate += m * (input[i] - '0'); - m *= 10; - } - } - - if (lsd > point) - mantissa = point - lsd; - else - mantissa = point - lsd - 1; - } - - decimal->ordinate = ordinate * sign; - decimal->mantissa = mantissa; -} - - - -/* Initialise DEC from the binary fp value X */ -void -decimal_from_double (struct decimal *dec, double x) -{ - dec->mantissa = 0; - - while (trunc (x) != x) - { - if (fabs (x) > ORD_MAX / 10.0) - { - dec_warning = DEC_PREC; - break; - } - x *= 10.0; - dec->mantissa--; - } - - dec->ordinate = x; -} - -/* Return a binary floating point value - approximating DEC */ -double -decimal_to_double (const struct decimal *dec) -{ - double x = dec->ordinate; - int mult = dec->mantissa; - - while (mult < 0) - { - x /= 10.0; - mult++; - } - - while (mult > 0) - { - x *= 10.0; - mult--; - } - - return x; -} diff --git a/src/math/decimal.h b/src/math/decimal.h deleted file mode 100644 index b5740f60ac..0000000000 --- a/src/math/decimal.h +++ /dev/null @@ -1,114 +0,0 @@ -/* PSPP - a program for statistical analysis. - Copyright (C) 2015 Free Software Foundation, Inc. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . */ - - -#ifndef DECIMAL_H -#define DECIMAL_H - -/* This module provides a rudimentary floating point implementation using a decimal - base. It can be used for floating point calculations where it is desirable that - the result is representable in decimal base. - - Any of the functions may set the static variable dec_warning to non-zero if a - loss of precision or other issue occurs. - - This does not purport to be efficient, either in time or space. - */ - -#include -#include - -#include - -#define DEC_PREC 1 /* operation resulted in a loss of precision */ -extern int dec_warning; - - -#define ORDINATE_LONG -#define MANTISSA_LONG - -#ifdef ORDINATE_SHORT -typedef short ord_t; -static const short ORD_MAX = SHRT_MAX; -#define PR_ORD "%d" -#endif - -#ifdef ORDINATE_INT -typedef int ord_t; -static const int ORD_MAX = INT_MAX; -#define PR_ORD "%d" -#endif - -#ifdef ORDINATE_LONG -typedef long ord_t; -static const long ORD_MAX = LONG_MAX; -#define PR_ORD "%ld" -#endif - - - -#ifdef MANTISSA_SHORT -typedef short mant_t; -static const short MANT_MAX = SHRT_MAX; -#define PR_MANT "%d" -#endif - -#ifdef MANTISSA_INT -typedef int mant_t; -static const int MANT_MAX = INT_MAX; -#define PR_MANT "%d" -#endif - -#ifdef MANTISSA_LONG -typedef long mant_t; -static const long MANT_MAX = LONG_MAX; -#define PR_MANT "%ld" -#endif - - - -#define MANT_MIN (-MANT_MAX - 1) -#define ORD_MIN (-ORD_MAX - 1) - -struct decimal -{ - ord_t ordinate; - mant_t mantissa; -}; - -void normalise (struct decimal *d1, struct decimal *d2); -void decimal_init (struct decimal *dec, ord_t ord, mant_t mant); -void decimal_init_from_string (struct decimal *dec, const char *s); -int decimal_cmp (const struct decimal *d1, const struct decimal *d2); -void decimal_int_multiply (struct decimal *dest, ord_t m); -void decimal_int_divide (struct decimal *dest, ord_t m); -void decimal_divide (struct decimal *dest, const struct decimal *src); -void decimal_show (const struct decimal *dec, FILE *f); -char *decimal_to_string (const struct decimal *dec); - -void decimal_add (struct decimal *dest, const struct decimal *); -void decimal_subtract (struct decimal *dest, const struct decimal *); -ord_t decimal_ceil (const struct decimal *d); -ord_t decimal_floor (const struct decimal *d); -mant_t dec_log10 (const struct decimal *d); - - -void decimal_from_double (struct decimal *dec, double x); -double decimal_to_double (const struct decimal *dec); - - - -#endif diff --git a/src/math/histogram.c b/src/math/histogram.c index 3c324eff24..9158590dd7 100644 --- a/src/math/histogram.c +++ b/src/math/histogram.c @@ -17,7 +17,6 @@ #include #include "math/histogram.h" -#include "math/decimal.h" #include #include @@ -59,163 +58,74 @@ destroy (struct statistic *s) } -static -double get_slack (double limit, double half_bin_width, int *n_half_bins) -{ - double ipart, remainder; - - assert (half_bin_width > 0); - - remainder = modf (limit / half_bin_width, &ipart); +/* Find a bin width which is adapted to the scaling of the x axis +In the example here, the binwidth is half of the tick interval. - /* In C modf and % behave in an unexpected (to me at any rate) manner - when presented with a negative value + binwidth + > < + |....+....+....+. .+....| + LOWER 1 2 3 N_TICKS + ^LOWDBL ^HIGHDBL - For example, modf (-7.0 / 3.0) returns -2.0 R -0.3333 - */ - - - *n_half_bins = ipart; - - return remainder * half_bin_width; -} - - -/* This functions adjusts the upper and lower range of the histogram to make them fit BIN_WIDTH - MIN and MAX are the lowest and highest data to be plotted in the histogram. - ADJ_MIN and ADJ_MAX are locations of the adjusted values of MIN and MAX (the range will - always be equal or slightly larger). - Returns the number of bins. - - The "testing_assert" expressions in this function should be algebraically correct. - However, due to floating point rounding they could fail, especially when small numbers - are involved. In normal use, therefore, testing_assert does nothing. - */ +This only works, when the min and max value for the histogram are adapted +such that (max-min) is a multiple of the binwidth. Then the location of the +first bin has to be aligned to the ticks. +*/ static int -adjust_bin_ranges (double bin_width, double min, double max, double *adj_min, double *adj_max) +hist_find_pretty_no_of_bins(double bin_width_in, double min, double max, + double *adjusted_min, double *adjusted_max) { - const double half_bin_width = bin_width / 2.0; - - /* The lower and upper limits of the histogram, in units of half - bin widths */ - int lower_limit, upper_limit; - - double lower_slack = get_slack (min, half_bin_width, &lower_limit); - double upper_slack = -get_slack (max, half_bin_width, &upper_limit); + double lower, interval; + int n_ticks; + double binwidth; + int nbins; - testing_assert (max > min); + chart_get_scale (max, min, &lower, &interval, &n_ticks); - /* If min is negative, then lower_slack may be less than zero. - In this case, the lower bound must be extended in the negative direction - so that it is less than OR EQUAL to min. - */ - if (lower_slack < 0) + if (bin_width_in >= 2 * interval) { - lower_limit--; - lower_slack += half_bin_width; + binwidth = floor(bin_width_in/interval) * interval; + *adjusted_min = lower; } - testing_assert (lower_limit * half_bin_width <= min); - - /* However, the upper bound must be extended regardless, because histogram bins - span the range [lower, upper). In other words, the upper bound must be - greater than max. - */ - upper_limit++;; - upper_slack += half_bin_width; - testing_assert (upper_limit * half_bin_width > max); - - /* The range must be an EVEN number of half bin_widths */ - if ( (upper_limit - lower_limit) % 2) + else if (bin_width_in >= 1.5 * interval) { - /* Extend the range at the end which gives the least unused space */ - if (upper_slack > lower_slack) - { - lower_limit--; - lower_slack += half_bin_width; - } + binwidth = 1.5 * interval; + if (min < (lower + 0.5 * interval)) + *adjusted_min = lower; else - { - upper_limit++; - upper_slack += half_bin_width; - } + *adjusted_min = lower + 0.5 * interval; } - - /* But the range should be aligned to an ODD number of - half bin widths, so that the labels are aesthetically pleasing ones. - Otherwise we are likely to get labels such as -3 -1 1 3 instead of -2 0 2 4 - */ - if ( lower_limit % 2 == 0) + else if (bin_width_in >= interval) { - /* If there is not enough slack at either end to perform a shift, - then we must extend the range so that there is. We must extend - by two half bin widths in order to preserve the EVEN condition - established above. Also, we extend on the end with the least - slack, in order to keep things as balanced as possible. */ - if ( upper_slack > lower_slack && upper_slack <= half_bin_width) - { - lower_limit -= 2; - lower_slack += 2 * half_bin_width; - } - - if (lower_slack > upper_slack && lower_slack < half_bin_width) - { - upper_limit += 2; - upper_slack += 2 * half_bin_width; - } - - if (upper_slack > lower_slack) - { - testing_assert (upper_slack > half_bin_width); - - /* Adjust the range to the left */ - lower_limit --; - upper_limit --; - upper_slack -= half_bin_width; - lower_slack += half_bin_width; - } - else - { - testing_assert (lower_slack >= half_bin_width); - - /* Adjust the range to the right */ - lower_limit ++; - upper_limit ++; - lower_slack -= half_bin_width; - upper_slack += half_bin_width; - } + binwidth = interval; + *adjusted_min = lower; } - - /* If there are any completely empty bins, then remove them, - since empty bins don't really add much information to the histogram. - */ - if (upper_slack > 2 * half_bin_width) + else if (bin_width_in >= (2.0/3.0 * interval)) { - upper_slack -= 2 * half_bin_width; - upper_limit -=2; + binwidth = (2.0/3.0 * interval); + if (min >= lower + binwidth) + *adjusted_min = lower + binwidth; + else + *adjusted_min = lower; } - - if (lower_slack >= 2 * half_bin_width) + else { - lower_slack -= 2 * half_bin_width; - lower_limit +=2; + int i; + for(i = 2; bin_width_in < interval/i; i++); + binwidth = interval/i; + *adjusted_min = floor((min - lower)/binwidth)*binwidth + lower; } - *adj_min = lower_limit * half_bin_width; - *adj_max = upper_limit * half_bin_width; - - testing_assert (*adj_max > max); - testing_assert (*adj_min <= min); + nbins = ceil((max-*adjusted_min)/binwidth); + *adjusted_max = nbins*binwidth + *adjusted_min; - return (upper_limit - lower_limit) / 2.0; + return nbins; } - struct histogram * histogram_create (double bin_width_in, double min, double max) { - struct decimal bin_width; - const int MAX_BINS = 25; struct histogram *h; struct statistic *stat; int bins; @@ -229,21 +139,7 @@ histogram_create (double bin_width_in, double min, double max) assert (bin_width_in > 0); - chart_rounded_tick (bin_width_in, &bin_width); - bins = adjust_bin_ranges (decimal_to_double (&bin_width), - min, max, &adjusted_min, &adjusted_max); - - /* Force the number of bins to lie in a sensible range. */ - if (bins > MAX_BINS) - { - chart_rounded_tick ((max - min) / (double) (MAX_BINS - 1), &bin_width); - bins = adjust_bin_ranges (decimal_to_double (&bin_width), - min, max, &adjusted_min, &adjusted_max); - } - - /* Can this ever happen? */ - if (bins < 1) - bins = 1; + bins = hist_find_pretty_no_of_bins(bin_width_in, min, max, &adjusted_min, &adjusted_max); h = xmalloc (sizeof *h); diff --git a/src/output/cairo-chart.c b/src/output/cairo-chart.c index a0d88376e2..9c68eb13f8 100644 --- a/src/output/cairo-chart.c +++ b/src/output/cairo-chart.c @@ -17,7 +17,6 @@ #include #include "output/cairo-chart.h" -#include "math/decimal.h" #include "math/chart-geometry.h" #include @@ -48,7 +47,7 @@ xrchart_geometry_init (cairo_t *cr, struct xrchart_geometry *geom, { /* Set default chart geometry. */ geom->axis[SCALE_ORDINATE].data_max = 0.900 * length; - geom->axis[SCALE_ORDINATE].data_min = 0.120 * length; + geom->axis[SCALE_ORDINATE].data_min = 0.200 * length; geom->axis[SCALE_ABSCISSA].data_min = 0.150 * width; geom->axis[SCALE_ABSCISSA].data_max = 0.800 * width; @@ -317,8 +316,6 @@ draw_tick_internal (cairo_t *cr, const struct xrchart_geometry *geom, } else if (orientation == SCALE_ORDINATE) { - if (fabs (position) < DBL_EPSILON) - cairo_rel_move_to (cr, 0, 10); xrchart_label (cr, 'r', 'c', geom->font_size, s); } } @@ -345,7 +342,29 @@ xrchart_write_title (cairo_t *cr, const struct xrchart_geometry *geom, cairo_restore (cr); } +static void +xrchart_text_extents (cairo_t *cr, const struct xrchart_geometry *geom, + const char *utf8, + double *width, double *height) +{ + PangoFontDescription *desc; + PangoLayout *layout; + int width_pango; + int height_pango; + desc = pango_font_description_from_string ("sans serif"); + if (desc == NULL) + return; + pango_font_description_set_absolute_size (desc, geom->font_size * PANGO_SCALE); + layout = pango_cairo_create_layout (cr); + pango_layout_set_font_description (layout, desc); + pango_layout_set_markup (layout, utf8, -1); + pango_layout_get_size (layout, &width_pango, &height_pango); + *width = (double) width_pango / PANGO_SCALE; + *height = (double) height_pango / PANGO_SCALE; + g_object_unref (layout); + pango_font_description_free (desc); +} static void xrchart_write_scale (cairo_t *cr, struct xrchart_geometry *geom, @@ -354,36 +373,50 @@ xrchart_write_scale (cairo_t *cr, struct xrchart_geometry *geom, int s; int ticks; - struct decimal dinterval; - struct decimal dlower; - struct decimal dupper; + double interval; + double lower; + double upper; + double tickscale; + char *tick_format_string; + bool tickoversize = false; - chart_get_scale (smax, smin, &dlower, &dinterval, &ticks); + chart_get_scale (smax, smin, &lower, &interval, &ticks); - dupper = dinterval; - decimal_int_multiply (&dupper, ticks); - decimal_add (&dupper, &dlower); + tick_format_string = chart_get_ticks_format (lower, interval, ticks, &tickscale); - double tick_interval = decimal_to_double (&dinterval); + upper = lower + interval * (ticks+1); - geom->axis[orient].max = decimal_to_double (&dupper); - geom->axis[orient].min = decimal_to_double (&dlower); + geom->axis[orient].max = upper; + geom->axis[orient].min = lower; geom->axis[orient].scale = (fabs (geom->axis[orient].data_max - geom->axis[orient].data_min) / fabs (geom->axis[orient].max - geom->axis[orient].min)); + + if (orient == SCALE_ABSCISSA) + { + char *test_text; + double lower_txt_width, upper_txt_width, unused, width; + test_text = xasprintf(tick_format_string, upper*tickscale); + xrchart_text_extents (cr, geom, test_text, &upper_txt_width, &unused); + free(test_text); + test_text = xasprintf(tick_format_string, lower*tickscale); + xrchart_text_extents (cr, geom, test_text, &lower_txt_width, &unused); + free(test_text); + width = MAX(lower_txt_width, upper_txt_width); + tickoversize = width > 0.9 * + ((double)(geom->axis[SCALE_ABSCISSA].data_max - geom->axis[SCALE_ABSCISSA].data_min))/(ticks+1); + } - struct decimal pos = dlower; + double pos = lower; - for (s = 0 ; s < ticks; ++s) + for (s = 0 ; s <= ticks; ++s) { - char *str = decimal_to_string (&pos); - draw_tick (cr, geom, orient, false, - s * tick_interval * geom->axis[orient].scale, - "%s", str); - free (str); - - decimal_add (&pos, &dinterval); + draw_tick (cr, geom, orient, tickoversize, + s * interval * geom->axis[orient].scale, + tick_format_string, pos*tickscale); + pos += interval; } + free(tick_format_string); } /* Set the scale for the ordinate */ @@ -569,27 +602,3 @@ xrchart_line(cairo_t *cr, const struct xrchart_geometry *geom, cairo_line_to (cr, x2, y2); cairo_stroke (cr); } - -void -xrchart_text_extents (cairo_t *cr, const struct xrchart_geometry *geom, - const char *utf8, - double *width, double *height) -{ - PangoFontDescription *desc; - PangoLayout *layout; - int width_pango; - int height_pango; - - desc = pango_font_description_from_string ("sans serif"); - if (desc == NULL) - return; - pango_font_description_set_absolute_size (desc, geom->font_size * PANGO_SCALE); - layout = pango_cairo_create_layout (cr); - pango_layout_set_font_description (layout, desc); - pango_layout_set_markup (layout, utf8, -1); - pango_layout_get_size (layout, &width_pango, &height_pango); - *width = (double) width_pango / PANGO_SCALE; - *height = (double) height_pango / PANGO_SCALE; - g_object_unref (layout); - pango_font_description_free (desc); -} diff --git a/src/output/cairo-chart.h b/src/output/cairo-chart.h index 116a8c9901..60ef282928 100644 --- a/src/output/cairo-chart.h +++ b/src/output/cairo-chart.h @@ -178,9 +178,4 @@ void xrchart_draw_spreadlevel (const struct chart_item *, cairo_t *, void xrchart_draw_scatterplot (const struct chart_item *, cairo_t *, struct xrchart_geometry *); -/* Get the width and height of rendered label text */ -void xrchart_text_extents (cairo_t *cr, const struct xrchart_geometry *geom, - const char *utf8, - double *width, double *height); - #endif /* output/cairo-chart.h */ diff --git a/src/output/charts/plot-hist-cairo.c b/src/output/charts/plot-hist-cairo.c index 7e2afee9b0..a85471962f 100644 --- a/src/output/charts/plot-hist-cairo.c +++ b/src/output/charts/plot-hist-cairo.c @@ -69,30 +69,24 @@ histogram_write_legend (cairo_t *cr, const struct xrchart_geometry *geom, static void hist_draw_bar (cairo_t *cr, const struct xrchart_geometry *geom, - const gsl_histogram *h, int bar, const char *tick_format_string, - const double tickscale, const bool tickoversize) + const gsl_histogram *h, int bar) { double upper; double lower; double height; - const size_t bins = gsl_histogram_bins (h); - - const double x_pos = - (geom->axis[SCALE_ABSCISSA].data_max - geom->axis[SCALE_ABSCISSA].data_min) * - bar / (double) bins ; - - const double width = - (geom->axis[SCALE_ABSCISSA].data_max - geom->axis[SCALE_ABSCISSA].data_min) / (double) bins ; - assert ( 0 == gsl_histogram_get_range (h, bar, &lower, &upper)); - assert ( upper >= lower); + const double x_pos = + (lower - geom->axis[SCALE_ABSCISSA].min) * geom->axis[SCALE_ABSCISSA].scale + + geom->axis[SCALE_ABSCISSA].data_min; + const double width = (upper - lower) * geom->axis[SCALE_ABSCISSA].scale; + height = geom->axis[SCALE_ORDINATE].scale * gsl_histogram_get (h, bar); cairo_rectangle (cr, - geom->axis[SCALE_ABSCISSA].data_min + x_pos, + x_pos, geom->axis[SCALE_ORDINATE].data_min, width, height); cairo_save (cr); @@ -103,10 +97,6 @@ hist_draw_bar (cairo_t *cr, const struct xrchart_geometry *geom, cairo_fill_preserve (cr); cairo_restore (cr); cairo_stroke (cr); - - draw_tick (cr, geom, SCALE_ABSCISSA, tickoversize, - x_pos + width / 2.0, tick_format_string, (upper+lower)/2.0*tickscale); - } void @@ -116,11 +106,6 @@ xrchart_draw_histogram (const struct chart_item *chart_item, cairo_t *cr, struct histogram_chart *h = to_histogram_chart (chart_item); int i; int bins; - char *tick_format_string; - char *test_text; - double width, left_width, right_width, unused; - double tickscale; - bool tickoversize; xrchart_write_title (cr, geom, _("HISTOGRAM")); @@ -134,27 +119,17 @@ xrchart_draw_histogram (const struct chart_item *chart_item, cairo_t *cr, } xrchart_write_yscale (cr, geom, 0, gsl_histogram_max_val (h->gsl_hist)); + xrchart_write_xscale (cr, geom, gsl_histogram_min (h->gsl_hist), + gsl_histogram_max (h->gsl_hist)); + /* Draw the ticks and compute if the rendered tick text is wider than the bin */ bins = gsl_histogram_bins (h->gsl_hist); - tick_format_string = chart_get_ticks_format (gsl_histogram_max (h->gsl_hist), - gsl_histogram_min (h->gsl_hist), - bins, - &tickscale); - test_text = xasprintf(tick_format_string, gsl_histogram_max (h->gsl_hist)*tickscale); - xrchart_text_extents (cr, geom, test_text, &right_width, &unused); - free(test_text); - test_text = xasprintf(tick_format_string, gsl_histogram_min (h->gsl_hist)*tickscale); - xrchart_text_extents (cr, geom, test_text, &left_width, &unused); - free(test_text); - width = MAX(left_width, right_width); - tickoversize = width > 0.9 * - ((double)(geom->axis[SCALE_ABSCISSA].data_max - geom->axis[SCALE_ABSCISSA].data_min))/bins; + for (i = 0; i < bins; i++) { - hist_draw_bar (cr, geom, h->gsl_hist, i, tick_format_string, tickscale, tickoversize); + hist_draw_bar (cr, geom, h->gsl_hist, i); } - free(tick_format_string); histogram_write_legend (cr, geom, h->n, h->mean, h->stddev); @@ -162,31 +137,28 @@ xrchart_draw_histogram (const struct chart_item *chart_item, cairo_t *cr, && h->n != SYSMIS && h->mean != SYSMIS && h->stddev != SYSMIS) { /* Draw the normal curve */ - double d; - double x_min, x_max, not_used; - double abscissa_scale; + double x_min, x_max; double ordinate_scale; - double range; + double binwidth; + double x; - gsl_histogram_get_range (h->gsl_hist, 0, &x_min, ¬_used); - range = not_used - x_min; - gsl_histogram_get_range (h->gsl_hist, bins - 1, ¬_used, &x_max); + gsl_histogram_get_range (h->gsl_hist, 0, &x_min, &x_max); + binwidth = x_max - x_min; - abscissa_scale = (geom->axis[SCALE_ABSCISSA].data_max - geom->axis[SCALE_ABSCISSA].data_min) / (x_max - x_min); - ordinate_scale = (geom->axis[SCALE_ORDINATE].data_max - geom->axis[SCALE_ORDINATE].data_min) / - gsl_histogram_max_val (h->gsl_hist); + /* The integral over the histogram is binwidth * sum(bin_i), while the integral over the pdf is 1 */ + /* Therefore the pdf has to be scaled accordingly such that the integrals are equal */ + ordinate_scale = binwidth * gsl_histogram_sum(h->gsl_hist); cairo_move_to (cr, geom->axis[SCALE_ABSCISSA].data_min, geom->axis[SCALE_ORDINATE].data_min); - for (d = geom->axis[SCALE_ABSCISSA].data_min; - d <= geom->axis[SCALE_ABSCISSA].data_max; - d += (geom->axis[SCALE_ABSCISSA].data_max - geom->axis[SCALE_ABSCISSA].data_min) / 100.0) + for (x = geom->axis[SCALE_ABSCISSA].min; + x <= geom->axis[SCALE_ABSCISSA].max; + x += (geom->axis[SCALE_ABSCISSA].max - geom->axis[SCALE_ABSCISSA].min) / 100.0) { - const double x = (d - geom->axis[SCALE_ABSCISSA].data_min) / abscissa_scale + x_min; - const double y = h->n * range * - gsl_ran_gaussian_pdf (x - h->mean, h->stddev); - - cairo_line_to (cr, d, geom->axis[SCALE_ORDINATE].data_min + y * ordinate_scale); - + const double y = gsl_ran_gaussian_pdf (x - h->mean, h->stddev) * ordinate_scale; + /* Transform to drawing coordinates */ + const double x_pos = (x - geom->axis[SCALE_ABSCISSA].min) * geom->axis[SCALE_ABSCISSA].scale + geom->axis[SCALE_ABSCISSA].data_min; + const double y_pos = (y - geom->axis[SCALE_ORDINATE].min) * geom->axis[SCALE_ORDINATE].scale + geom->axis[SCALE_ORDINATE].data_min; + cairo_line_to (cr, x_pos, y_pos); } cairo_stroke (cr); } diff --git a/tests/automake.mk b/tests/automake.mk index be44b342e9..3b9a553503 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -31,9 +31,8 @@ check_PROGRAMS += \ tests/libpspp/tower-test \ tests/libpspp/u8-istream-test \ tests/libpspp/zip-test \ - tests/math/chart-geometry-test \ + tests/math/chart-get-ticks-format-test \ tests/math/chart-get-scale-test \ - tests/math/decimal-test \ tests/output/render-test \ tests/ui/syntax-gen-test @@ -212,14 +211,6 @@ tests_libpspp_zip_test_LDADD = \ src/libpspp-core.la \ gl/libgl.la -check_PROGRAMS += tests/math/chart-geometry-test -tests_math_chart_geometry_test_SOURCES = tests/math/chart-geometry-test.c -tests_math_chart_geometry_test_LDADD = \ - src/math/libpspp-math.la \ - src/libpspp/liblibpspp.la \ - src/libpspp-core.la \ - gl/libgl.la - check_PROGRAMS += tests/math/chart-get-scale-test tests_math_chart_get_scale_test_SOURCES = tests/math/chart-get-scale-test.c tests_math_chart_get_scale_test_LDADD = \ @@ -236,15 +227,6 @@ tests_math_chart_get_ticks_format_test_LDADD = \ src/libpspp-core.la \ gl/libgl.la -check_PROGRAMS += tests/math/decimal-test -tests_math_decimal_test_SOURCES = tests/math/decimal-test.c -tests_math_decimal_test_LDADD = \ - src/math/libpspp-math.la \ - src/libpspp/liblibpspp.la \ - src/libpspp-core.la \ - gl/libgl.la - - check_PROGRAMS += tests/output/render-test tests_output_render_test_SOURCES = tests/output/render-test.c tests_output_render_test_LDADD = \ @@ -404,7 +386,6 @@ TESTSUITE_AT = \ tests/libpspp/u8-istream.at \ tests/libpspp/zip.at \ tests/math/chart-geometry.at \ - tests/math/decimal.at \ tests/math/moments.at \ tests/math/randist.at \ tests/output/ascii.at \ diff --git a/tests/math/chart-geometry-test.c b/tests/math/chart-geometry-test.c deleted file mode 100644 index 1cf67c3d96..0000000000 --- a/tests/math/chart-geometry-test.c +++ /dev/null @@ -1,61 +0,0 @@ -/* PSPP - a program for statistical analysis. - Copyright (C) 2015 Free Software Foundation, Inc. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . */ - -#include -#include -#include "math/chart-geometry.h" -#include "math/decimal.h" -#include "libpspp/compiler.h" - -const double in[20] = - { - 0.00648687, - 728815, - 8.14431e-07, - 77611.4, - 3.33497, - 180.426, - 0.676168, - 2.00744e+08, - 14099.3, - 19.5186, - 1.17473e-07, - 166337, - 0.00163644, - 1.94724e-09, - 2.31564e-06, - 3.10674e+06, - 5.10314e-05, - 1.95101, - 1.40884e+09, - 78217.6 - }; - -int -main (int argc UNUSED, char **argv UNUSED) -{ - int i; - for (i = 0; i < 20; ++i) - { - struct decimal dout; - chart_rounded_tick (in[i], &dout); - - printf ("%g %s\n", in[i], decimal_to_string (&dout)); - } - - return 0; -} - diff --git a/tests/math/chart-geometry.at b/tests/math/chart-geometry.at index 96bf02db73..9bbd0cf070 100644 --- a/tests/math/chart-geometry.at +++ b/tests/math/chart-geometry.at @@ -1,66 +1,37 @@ AT_BANNER([Chart Geometry]) -AT_SETUP([Chart Rounding]) - -AT_CHECK([../../math/chart-geometry-test], [0], [dnl -0.00648687 0.005 -728815 500000 -8.14431e-07 0.000001 -77611.4 100000 -3.33497 2 -180.426 200 -0.676168 0.5 -2.00744e+08 200000000 -14099.3 10000 -19.5186 20 -1.17473e-07 0.0000001 -166337 200000 -0.00163644 0.002 -1.94724e-09 0.000000002 -2.31564e-06 0.000002 -3.10674e+06 2000000 -5.10314e-05 0.00005 -1.95101 2 -1.40884e+09 1000000000 -78217.6 100000 -]) - -AT_CLEANUP - - AT_SETUP([Chart Scale]) - AT_CHECK([../../math/chart-get-scale-test], [0], [ignore]) - AT_CLEANUP - AT_SETUP([Chart Ticks Format]) - AT_CHECK([../../math/chart-get-ticks-format-test], [0], [dnl -max: 1000, min: 10, nticks: 10, fs: %.0lf, scale: 1, example: 505 -max: 10000, min: 10, nticks: 10, fs: %.0lf, scale: 1, example: 5005 -max: 100000, min: 10, nticks: 10, fs: %.0lf, scale: 1, example: 50005 -max: 1e+06, min: 10, nticks: 10, fs: %.0lf, scale: 1, example: 500005 -max: 1e+07, min: 10, nticks: 10, fs: %.2lf∙107, scale: 1e-07, example: 0.50∙107 -max: 1e+08, min: 10, nticks: 10, fs: %.2lf∙108, scale: 1e-08, example: 0.50∙108 -max: 0.1, min: 0.01, nticks: 10, fs: %.3lf, scale: 1, example: 0.055 -max: 0.01, min: 0.001, nticks: 10, fs: %.4lf, scale: 1, example: 0.0055 -max: 0.001, min: 0.0001, nticks: 10, fs: %.5lf, scale: 1, example: 0.00055 -max: 0.0001, min: 1e-05, nticks: 10, fs: %.2lf∙10-5, scale: 100000, example: 5.50∙10-5 -max: 1e-05, min: 1e-07, nticks: 10, fs: %.2lf∙10-6, scale: 1e+06, example: 5.05∙10-6 -max: 1e-07, min: 1e-08, nticks: 10, fs: %.2lf∙10-8, scale: 1e+08, example: 5.50∙10-8 -max: 1.00001e-05, min: 1e-05, nticks: 10, fs: %.6lf∙10-5, scale: 100000, example: 1.000005∙10-5 -max: 1e+08, min: 1e+08, nticks: 10, fs: %.0lf, scale: 1, example: 100000005 -max: 100000, min: -500000, nticks: 10, fs: %.0lf, scale: 1, example: -200000 -max: 5, min: -5, nticks: 10, fs: %.0lf, scale: 1, example: 0 -max: 5, min: -4.999, nticks: 10, fs: %.1lf, scale: 1, example: 0.0 -max: 5, min: -4.999, nticks: 9, fs: %.0lf, scale: 1, example: 0 -max: 5, min: 0, nticks: 10, fs: %.1lf, scale: 1, example: 2.5 -max: 0, min: -5, nticks: 9, fs: %.1lf, scale: 1, example: -2.5 -max: 1.001e-95, min: 1e-95, nticks: 10, fs: %.5lf∙10-95, scale: 1e+95, example: 1.00050∙10-95 -max: 1.001e+98, min: 1e+98, nticks: 10, fs: %.5lf∙1098, scale: 1e-98, example: 1.00050∙1098 -max: 1.001e+33, min: 1e-22, nticks: 10, fs: %.2lf∙1033, scale: 1e-33, example: 0.50∙1033 +lower: 1000, interval: 10, nticks: 10, fs: %.0lf, scale: 1, ex: 1030, ex 2: 1040 +lower: 10000, interval: 10, nticks: 10, fs: %.0lf, scale: 1, ex: 10030, ex 2: 10040 +lower: 100000, interval: 10, nticks: 10, fs: %.0lf, scale: 1, ex: 100030, ex 2: 100040 +lower: 1e+06, interval: 10, nticks: 10, fs: %.0lf, scale: 1, ex: 1000030, ex 2: 1000040 +lower: 1e+07, interval: 10, nticks: 10, fs: %.0lf, scale: 1, ex: 10000030, ex 2: 10000040 +lower: 1e+08, interval: 10, nticks: 10, fs: %.0lf, scale: 1, ex: 100000030, ex 2: 100000040 +lower: 0.1, interval: 0.01, nticks: 10, fs: %.2lf, scale: 1, ex: 0.13, ex 2: 0.14 +lower: 0.01, interval: 0.001, nticks: 10, fs: %.3lf, scale: 1, ex: 0.013, ex 2: 0.014 +lower: 0.001, interval: 0.0001, nticks: 10, fs: %.4lf, scale: 1, ex: 0.0013, ex 2: 0.0014 +lower: 0.0001, interval: 1e-05, nticks: 10, fs: %.1lf⋅10-4, scale: 10000, ex: 1.3⋅10-4, ex 2: 1.4⋅10-4 +lower: 1e-05, interval: 1e-07, nticks: 10, fs: %.2lf⋅10-5, scale: 100000, ex: 1.03⋅10-5, ex 2: 1.04⋅10-5 +lower: 1e-07, interval: 1e-08, nticks: 10, fs: %.1lf⋅10-7, scale: 1e+07, ex: 1.3⋅10-7, ex 2: 1.4⋅10-7 +lower: -5, interval: 1, nticks: 10, fs: %.0lf, scale: 1, ex: -2, ex 2: -1 +lower: -5, interval: 0.5, nticks: 10, fs: %.1lf, scale: 1, ex: -3.5, ex 2: -3.0 +lower: -5, interval: 0.2, nticks: 9, fs: %.1lf, scale: 1, ex: -4.4, ex 2: -4.2 +lower: -5, interval: 2, nticks: 10, fs: %.0lf, scale: 1, ex: 1, ex 2: 3 +lower: -0.5, interval: 0.1, nticks: 9, fs: %.1lf, scale: 1, ex: -0.2, ex 2: -0.1 +lower: 9.75e+08, interval: 5e+06, nticks: 9, fs: %.3lf⋅109, scale: 1e-09, ex: 0.990⋅109, ex 2: 0.995⋅109 +lower: 9.7e+08, interval: 1e+07, nticks: 9, fs: %.2lf⋅109, scale: 1e-09, ex: 1.00⋅109, ex 2: 1.01⋅109 +lower: -4e+07, interval: 1e+07, nticks: 9, fs: %.0lf⋅107, scale: 1e-07, ex: -1⋅107, ex 2: 0⋅107 +lower: -3e+07, interval: 5e+06, nticks: 9, fs: %.1lf⋅107, scale: 1e-07, ex: -1.5⋅107, ex 2: -1.0⋅107 +lower: 1.001e-95, interval: 2e-99, nticks: 10, fs: %.4lf⋅10-95, scale: 1e+95, ex: 1.0016⋅10-95, ex 2: 1.0018⋅10-95 +lower: 1.001e+98, interval: 2e+94, nticks: 10, fs: %.4lf⋅1098, scale: 1e-98, ex: 1.0016⋅1098, ex 2: 1.0018⋅1098 +lower: 5984, interval: 1e-05, nticks: 10, fs: %.5lf, scale: 1, ex: 5984.00003, ex 2: 5984.00004 +lower: 3e+33, interval: 1e-22, nticks: 10, fs: %lg, scale: 1, ex: 3e+33, ex 2: 3e+33 +lower: 3e+33, interval: 1000, nticks: 10, fs: %.8lf⋅1033, scale: 1e-33, ex: 3.00000000⋅1033, ex 2: 3.00000000⋅1033 +lower: 0.1, interval: 2e-42, nticks: 10, fs: %.8lf, scale: 1, ex: 0.10000000, ex 2: 0.10000000 ]) - AT_CLEANUP diff --git a/tests/math/chart-get-scale-test.c b/tests/math/chart-get-scale-test.c index d4cbe18eb0..6f2ca711d0 100644 --- a/tests/math/chart-get-scale-test.c +++ b/tests/math/chart-get-scale-test.c @@ -23,7 +23,6 @@ #include "libpspp/compiler.h" -#include "math/decimal.h" #include "math/chart-geometry.h" #include #include @@ -31,14 +30,14 @@ #if 0 static void -dump_scale (const struct decimal *low, const struct decimal *interval, int n_ticks) +dump_scale (const double low, const double interval, int n_ticks) { int i; - struct decimal tick = *low; + double tick = low; for (i = 0; i <= n_ticks; ++i) { - printf ("Tick %d: %s (%g)\n", i, decimal_to_string (&tick), decimal_to_double (&tick)); - decimal_add (&tick, interval); + printf ("Tick %d: %g\n", i, tick); + tick += interval; } } #endif @@ -48,50 +47,52 @@ static void test_range (double low, double high) { int n_ticks = 0; - struct decimal interval; - struct decimal lower; + double interval; + double lower; chart_get_scale (high, low, &lower, &interval, &n_ticks); - assert (n_ticks > 0); - assert (n_ticks < 12); - - // dump_scale (&lower, &interval, n_ticks); - - assert (decimal_to_double (&lower) <= low); - - { - struct decimal l = lower; - decimal_add (&l, &interval); - assert (decimal_to_double (&l) > low); + if ((high - low) < 10 * DBL_MIN){ + assert (n_ticks == 0); + assert (lower == low); + assert (interval <= 10 * DBL_MIN); } + else + assert (n_ticks > 4); - { - struct decimal i = interval; - - decimal_int_multiply (&i, n_ticks - 1); - - decimal_add (&i, &lower); + assert (n_ticks <= 10); - /* i now contains the upper bound minus one tick */ - assert (decimal_to_double (&i) < high); - - decimal_add (&i, &interval); +#if 0 + printf("%s: high: %lg, low %lg, interval: %lg, nticks: %d\n", + __FUNCTION__, high, low, interval, n_ticks); + dump_scale (lower, interval, n_ticks); +#endif - assert (decimal_to_double (&i) >= high); + if ((high - low) > 10 * DBL_MIN) { + assert (lower <= low); + assert (lower + interval > low); + assert (lower + n_ticks * interval < high); + assert (lower + (n_ticks + 1) * interval >= high); } - } int main (int argc UNUSED, char **argv UNUSED) { + test_range (0, 0); + test_range (5, 5); + test_range (-5, -5); + test_range (0, 7); test_range (0.2, 11); test_range (-0.2, 11); test_range (-10, 0.2); test_range (-10, -0.2); + test_range (-10000, 10003); + test_range (50042,50053); + test_range (-50010, -49999); + test_range (0.000100002, 0.000100010); test_range (102, 50030); test_range (0.00102, 0.0050030); diff --git a/tests/math/chart-get-ticks-format-test.c b/tests/math/chart-get-ticks-format-test.c index 6dcab50774..3dbeac6557 100644 --- a/tests/math/chart-get-ticks-format-test.c +++ b/tests/math/chart-get-ticks-format-test.c @@ -21,8 +21,8 @@ #include "libpspp/compiler.h" struct range { - double max; - double min; + double lower; + double interval; int nticks; }; @@ -39,17 +39,21 @@ struct range tv[] = { { 0.0001, 0.00001, 10}, { 0.00001, 0.0000001, 10}, { 0.0000001, 0.00000001, 10}, - { 0.0000100001, 0.00001, 10}, - { 100000010.0, 100000000.0, 10}, - { 100000.0, -500000.0, 10}, - { 5.0, -5.0, 10}, - { 5.0, -4.999, 10}, - { 5.0, -4.999, 9}, - { 5.0, 0.0, 10}, - { 0.0, -5.0, 9}, - { 1.001E-95, 1.0E-95, 10}, - { 1.001E98, 1.0E98, 10}, - { 1.001E33, 1.0E-22, 10}, + { -5.0, 1.0, 10}, + { -5.0, 0.5, 10}, + { -5.0, 0.2, 9}, + { -5.0, 2.0, 10}, + { -0.5, 0.1, 9}, + { 0.975E9, 0.005E9, 9}, + { 0.970E9, 0.01E9, 9}, + { -4E7, 1E7, 9}, + { -3E7, 0.5E7, 9}, + { 1.001E-95, 0.0002E-95, 10}, + { 1.001E98, 0.0002E98, 10}, + { 5984, 0.00001, 10}, + { 3E33, 1E-22, 10}, + { 3E33, 1000, 10}, + { 0.1, 2E-42, 10}, { 0.0, 0.0, -1} }; @@ -59,18 +63,20 @@ main (int argc UNUSED, char **argv UNUSED) char *fs; double scale; int i = 0; - double max, min; + double lower, interval; int nticks; for(i=0;tv[i].nticks > 0;i++) { - max = tv[i].max; - min = tv[i].min; + lower = tv[i].lower; + interval = tv[i].interval; nticks = tv[i].nticks; - fs = chart_get_ticks_format (max, min, nticks, &scale); - printf("max: %lg, min: %lg, nticks: %d, fs: %s, scale: %lg, example: ", - max, min, nticks, fs, scale); - printf(fs,((max-min)/2.0+min)*scale); + fs = chart_get_ticks_format (lower, interval, nticks, &scale); + printf("lower: %lg, interval: %lg, nticks: %d, fs: %s, scale: %lg, ex: ", + lower, interval, nticks, fs, scale); + printf(fs,(lower + 3 * interval)*scale); + printf(", ex 2: "); + printf(fs,(lower + 4 * interval)*scale); printf("\n"); free(fs); } diff --git a/tests/math/decimal-test.c b/tests/math/decimal-test.c deleted file mode 100644 index 791483efa6..0000000000 --- a/tests/math/decimal-test.c +++ /dev/null @@ -1,348 +0,0 @@ -/* PSPP - a program for statistical analysis. - Copyright (C) 2015 Free Software Foundation, Inc. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . */ - -#include - -#include -#include -#include -#include - -#include "libpspp/compiler.h" -#include "math/decimal.h" -#include -#include -#include - -/* Canonicalise a string holding the decimal representation of a number. - For example, leading zeros left of the decimal point are removed, as are - trailing zeros to the right. - - This function is used purely for testing, and need not and is not intended - to be efficient. - */ -static char * -canonicalise_string (const char *s) -{ - char *out = NULL; - char *dot = NULL; - bool negative = false; - char *p; - char *temp = malloc (strlen (s) + 3); - char *last_leading_zero = NULL; - - /* Strip leading - if present */ - if (*s == '-') - { - negative = true; - s++; - } - - strcpy (temp, "00"); - strcat (temp, s); - - char *first_trailing_zero = NULL; - char *significant_digit = NULL; - for (p = temp; *p; p++) - { - if (*p == '0' && dot == NULL && significant_digit == NULL) - last_leading_zero = p; - - if (*p == '0' && first_trailing_zero == NULL) - first_trailing_zero = p; - - if (*p == '.') - { - dot = p; - first_trailing_zero = NULL; - } - - if (*p >= '1' && *p <= '9') - { - significant_digit = p; - first_trailing_zero = NULL; - } - } - - if (first_trailing_zero && dot) - *first_trailing_zero = '\0'; - - if (last_leading_zero) - { - /* Strip leading zeros */ - out = last_leading_zero + 1; - - /* But if we now start with . put a zero back again */ - if (dot == last_leading_zero + 1) - out--; - } - - - if (negative) - { - out--; - out[0] = '-'; - } - - if (!significant_digit) - { - *out = '0'; - *(out+1) = '\0'; - } - - - return out; -} - - -/* Tests both the decimal_to_string function, and the decimal_input_from_string - function */ -static void -test_run (const char *input) - { - struct decimal test; - struct decimal number; - decimal_init_from_string (&number, input); - - char *s = decimal_to_string (&number); - char *canon = canonicalise_string (input); - if (0 != strcmp (canon, s)) - { - fprintf (stdout, "\"%s\" does not match \n\"%s\"\n", canon, s); - exit (1); - } - - decimal_init_from_string (&test, s); - assert (0 == decimal_cmp (&test, &number)); - - free (s); - } - - -static void -test_can (const char *in, const char *soll) -{ - char *ist = canonicalise_string (in); - if (0 != strcmp (soll, ist)) - { - printf ("\"%s\" canonicalises to \"%s\" (should be \"%s\")\n", in, ist, soll); - } -} - - -#if 0 -static void -dump_scale (const struct decimal *low, const struct decimal *interval, int n_ticks) -{ - int i; - struct decimal tick = *interval; - printf ("Lowest: %s\n", decimal_to_string (low)); - for (i = 0; i <= n_ticks; ++i) - { - printf ("Tick %d: %s (%g)\n", i, decimal_to_string (&tick), decimal_to_double (&tick)); - decimal_add (&tick, interval); - } -} -#endif - - - -static void -test_ceil (double x) -{ - struct decimal dx; - decimal_from_double (&dx, x); - int act = decimal_ceil (&dx); - int expected = ceil (x); - - assert (act == expected); -} - -static void -test_floor (double x) -{ - struct decimal dx; - decimal_from_double (&dx, x); - int act = decimal_floor (&dx); - int expected = floor (x); - - assert (act == expected); -} - - -static void -test_addition (const struct decimal *one_, const struct decimal *two) -{ - struct decimal one = *one_; - - decimal_add (&one, two); - - double dsum = decimal_to_double (&one); - - char sdsum1[256]; - char sdsum2[256]; - - snprintf (sdsum1, 256, "%s", decimal_to_string (&one)); - snprintf (sdsum2, 256, "%g", dsum); - - assert (strcmp (sdsum1, sdsum2) == 0); -} - - -static void -test_multiplication (const struct decimal *d, int m) -{ - char b1[256]; - char b2[256]; - struct decimal dest = *d; - double x = decimal_to_double (&dest); - - decimal_int_multiply (&dest, m); - - double y = decimal_to_double (&dest); - - snprintf (b1, 256, "%g", m * x); - snprintf (b2, 256, "%g", y); - assert (0 == strcmp (b1, b2)); -} - - - -int -main (int argc UNUSED, char **argv UNUSED) -{ - /* Test that our canonicalise function works for all corner cases we - can think of. */ - - test_can ("500", "500"); - test_can ("5", "5"); - test_can ("-3", "-3"); - test_can ("-3.001", "-3.001"); - test_can ("-03.001", "-3.001"); - test_can ("-.0301", "-0.0301"); - test_can ("0314.09", "314.09"); - test_can ("0314.090", "314.09"); - test_can ("0314.0900340", "314.090034"); - test_can ("0.0", "0"); - test_can ("0.", "0"); - test_can (".0", "0"); - test_can ("-.1", "-0.1"); - test_can (".090", "0.09"); - test_can ("03410.098700", "3410.0987"); - test_can ("-03410.098700", "-3410.0987"); - - /* Test the conversion functions */ - test_run ("-90000"); - test_run ("-3"); - test_run ("50001"); - test_run ("500"); - test_run ("350"); - test_run ("050"); - test_run ("4"); - test_run ("0"); - test_run (".45"); - test_run ("-.45"); - test_run ("666666666"); - test_run ("6000000000"); - test_run ("0.000000005"); - test_run ("0.00000000000000000000000000000000000000005"); - test_run ("0.0234"); - test_run ("0.234"); - test_run ("-0123.45600"); - - test_ceil (5.21); - test_ceil (-4.32); - test_ceil (0); - test_ceil (0.0009); - - test_floor (4.09); - test_floor (-4.09); - test_floor (0); - test_floor (0.004); - - - { - struct decimal high = {2, 0}; - struct decimal low = {2, -1}; - - test_addition (&high, &low); - } - - - { - struct decimal high = {10, 0}; - struct decimal low = {2, -1}; - - test_addition (&high, &low); - } - - - { - struct decimal high = {10, 0}; - struct decimal low = {-2, -1}; - - test_addition (&high, &low); - } - - { - struct decimal high = {12, -5}; - struct decimal low = {-2, -1}; - - test_addition (&high, &low); - } - - { - struct decimal high = {-112, -1}; - struct decimal low = {2, -1}; - - test_addition (&high, &low); - } - - - { - struct decimal m = {10, 0}; - - test_multiplication (&m, 11); - } - - - { - struct decimal m = {ORD_MAX - 2, 0}; - - test_multiplication (&m, 11); - } - - - { - struct decimal m = {34, 0}; - - test_multiplication (&m, 0); - } - - { - struct decimal m = {34, -20}; - - test_multiplication (&m, 33); - } - - { - struct decimal m = {304, 2}; - - test_multiplication (&m, -33); - } - - return 0; -} diff --git a/tests/math/decimal.at b/tests/math/decimal.at deleted file mode 100644 index b3e71d1fab..0000000000 --- a/tests/math/decimal.at +++ /dev/null @@ -1,7 +0,0 @@ -AT_BANNER([Decimal floating point]) - -AT_SETUP([Decimal float]) - -AT_CHECK([../../math/decimal-test], [0], [ignore]) - -AT_CLEANUP -- 2.30.2