Avoid a crash when attempting to allocate very large matrices.
[pspp.git] / src / math / chart-geometry.c
blob8a14009aec8ac5f91e0660311c347e9607074b7a
1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2004, 2015 Free Software Foundation, Inc.
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
17 #include <config.h>
18 #include <math.h>
19 #include <float.h>
20 #include <assert.h>
22 #include "chart-geometry.h"
23 #include <stdlib.h>
25 #include "gl/xalloc.h"
26 #include "gl/minmax.h"
27 #include "gl/xvasprintf.h"
29 #include "gettext.h"
30 #define _(msgid) gettext (msgid)
33 Find a set {LOWER, INTERVAL, N_TICKS} such that:
35 LOWER <= LOWDBL,
36 LOWER + INTERVAL > LOWDBL,
38 LOWER + N_TICKS * INTERVAL < HIGHDBL
39 LOWER + (N_TICKS + 1) * INTERVAL >= HIGHDBL
41 INTERVAL = X * 10^N
42 where: N is integer
43 and X is an element of {1, 2, 5}
45 In other words:
47 INTERVAL
48 > <
49 |....+....+....+. .+....|
50 LOWER 1 2 3 N_TICKS
51 ^LOWDBL ^HIGHDBL
53 void
54 chart_get_scale (double high, double low,
55 double *lower, double *interval,
56 int *n_ticks)
58 assert (high >= low);
59 if ((high - low) < 10 * DBL_MIN)
61 *n_ticks = 0;
62 *lower = low;
63 *interval = 0.0;
64 return;
67 /* Round down the difference between HIGH and LOW to a power of 10, then
68 divide by 10. If HIGH - LOW is a power of 10, then BINTERVAL will be
69 (HIGH - LOW) / 10; otherwise, it will be less than (HIGH - LOW) / 10 but
70 more than (HIGH - LOW) / 100.
72 for a range of [5.5,9.2], binterval = 0.1;
73 For a range of [0,10], binterval = 1;
74 for a range of [55,92], binterval = 1;
75 for a range of [0,100], binterval = 10;
76 for a range of [555,922], binterval = 10;
77 and so on. */
78 double binterval = pow (10.0, floor (log10 (high - low)) - 1);
79 double fitness = DBL_MAX;
81 /* Find the most pleasing interval. */
82 static const double standard_tick[] = {1, 2, 5, 10};
83 for (int i = 0; i < sizeof standard_tick / sizeof *standard_tick; i++)
85 /* Take a multiple of the basic interval. */
86 double cinterval = standard_tick[i] * binterval;
88 /* Make a range by rounding LOW down to the next multiple of CINTERVAL,
89 and HIGH up to the next multiple of CINTERVAL. */
90 double clower = floor (low / cinterval) * cinterval;
91 int cnticks = ceil ((high - clower) / cinterval) - 1;
93 /* Compute a score based on considering 7.5 ticks to be ideal. */
94 double cfitness = fabs (7.5 - cnticks);
96 /* Choose the lowest score. */
97 if (cfitness < fitness)
99 fitness = cfitness;
100 *lower = clower;
101 *interval = cinterval;
102 *n_ticks = cnticks;
108 Generate a format string which can be passed to printf like functions,
109 which will produce a string in scientific notation representing a real
110 number. N_DECIMALS is the number of decimal places EXPONENT is the
111 value of the exponent.
113 static inline char *
114 gen_pango_markup_scientific_format_string (int n_decimals, int exponent)
116 /* TRANSLATORS: This is a format string which, when presented to
117 printf like functions, will create a pango markup string to
118 display real number in scientific notation.
120 In its untranslated form, it will display similar to "1.23 x 10^4". You
121 can leave it untranslated if this is how scientific notation is usually
122 presented in your language.
124 Some locales (such as German) prefer the centered dot rather than the
125 multiplication sign between the mantissa an exponent. In which
126 case, you can change "#215;" to "#8901;" or other unicode code
127 point as appropriate.
129 The . in this string does not and should not be changed, since
130 that is taken care of by the stdc library.
132 For information on Pango markup, see
133 http://developer.gnome.org/pango/stable/PangoMarkupFormat.html
135 For tables of unicode code points, see http://unicode.org/charts
137 return xasprintf(_("%%.%dlf&#215;10<sup>%d</sup>"), n_decimals, exponent);
141 * Compute the optimum format string and the scaling
142 * for the tick drawing on a chart axis
143 * Input: lower: the lowest tick
144 * interval:the interval between the ticks
145 * nticks: the number of tick intervals (bins) on the axis
146 * Return: fs: format string for printf to print the tick value
147 * scale: scaling factor for the tick value
148 * The format string has to be freed after usage.
149 * An example format string and scalefactor:
150 * Non Scientific: "%.3lf", scale=1.00
151 * Scientific: "%.2lfe3", scale = 0.001
152 * Usage example:
153 * fs = chart_get_ticks_format(-0.7,0.1,8,&scale);
154 * printf(fs,value*scale);
155 * free(fs);
157 char *
158 chart_get_ticks_format (const double lower, const double interval,
159 const unsigned int nticks, double *scale)
161 double logmax = log10(fmax(fabs(lower + (nticks+1)*interval),fabs(lower)));
162 double logintv = log10(interval);
163 int logshift = 0;
164 char *format_string = NULL;
165 int nrdecs = 0;
167 if (logmax > 0.0 && logintv < 0.0)
169 nrdecs = MIN(6,(int)(ceil(fabs(logintv))));
170 logshift = 0;
171 if (logmax < 12.0)
172 format_string = xasprintf("%%.%dlf",nrdecs);
173 else
174 format_string = xasprintf("%%lg");
176 else if (logmax > 0.0) /*logintv is > 0*/
178 if (logintv < 5.0 && logmax < 10.0)
180 logshift = 0; /* No scientific format */
181 nrdecs = 0;
182 format_string = xstrdup("%.0lf");
184 else
186 logshift = (int)logmax;
187 /* Possible intervals are 0.2Ex, 0.5Ex, 1.0Ex */
188 /* log10(0.2E9) = 8.30, log10(0.5E9) = 8.69, log10(1.0E9) = 9 */
189 /* 0.2 and 0.5 need one decimal more. For stability subtract 0.1 */
190 nrdecs = MIN(8,(int)(ceil(logshift-logintv-0.1)));
191 format_string = gen_pango_markup_scientific_format_string (nrdecs, logshift);
194 else /* logmax and logintv are < 0 */
196 if (logmax > -3.0)
198 logshift = 0; /* No scientific format */
199 nrdecs = MIN(8,(int)(ceil(-logintv)));
200 format_string = xasprintf("%%.%dlf",nrdecs);
202 else
204 logshift = (int)logmax-1;
205 nrdecs = MIN(8,(int)(ceil(logshift-logintv-0.1)));
206 format_string = gen_pango_markup_scientific_format_string (nrdecs, logshift);
209 *scale = pow(10.0,-(double)logshift);
210 return format_string;