X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;ds=sidebyside;f=src%2Flanguage%2Fexpressions%2Fhelpers.c;h=7c3371f5db97eb9daf3455c943014a035d178408;hb=724be9e4720784aafeac395b3f6df47a89fc5e07;hp=3e28d8fe6129dc2f78f8ccd00cdcb4453b45f4c1;hpb=9c8c1170e3b3cb59f9bf8fe6b27ecba7067b8cfa;p=pspp
diff --git a/src/language/expressions/helpers.c b/src/language/expressions/helpers.c
index 3e28d8fe61..7c3371f5db 100644
--- a/src/language/expressions/helpers.c
+++ b/src/language/expressions/helpers.c
@@ -1,29 +1,31 @@
+/* PSPP - a program for statistical analysis.
+ Copyright (C) 2008, 2010, 2011 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 "helpers.h"
+
+#include "language/expressions/helpers.h"
+
#include
#include
-#include
-#include "private.h"
-const struct fixed_string empty_string = {NULL, 0};
+#include "language/expressions/private.h"
+#include "libpspp/assertion.h"
+#include "libpspp/pool.h"
-static void
-expr_error (void *aux UNUSED, const char *format, ...)
-{
- struct error e;
- va_list args;
-
- /* FIXME: we can do better about saying where the error
- occurred. */
- e.category = MSG_SYNTAX;
- e.severity = MSG_ERROR;
- err_location (&e.where);
- va_start (args, format);
- e.text = xvasprintf (format, args);
- va_end (args);
-
- err_msg (&e);
-}
+const struct substring empty_string = {NULL, 0};
double
expr_ymd_to_ofs (double year, double month, double day)
@@ -31,15 +33,23 @@ expr_ymd_to_ofs (double year, double month, double day)
int y = year;
int m = month;
int d = day;
+ char *error;
+ double ofs;
- if (y != year || m != month || d != day)
- {
+ if (y != year || m != month || d != day)
+ {
msg (SE, _("One of the arguments to a DATE function is not an integer. "
"The result will be system-missing."));
return SYSMIS;
}
- return calendar_gregorian_to_offset (y, m, d, expr_error, NULL);
+ ofs = calendar_gregorian_to_offset (y, m, d, &error);
+ if (error != NULL)
+ {
+ msg (SE, "%s", error);
+ free (error);
+ }
+ return ofs;
}
double
@@ -50,24 +60,24 @@ expr_ymd_to_date (double year, double month, double day)
}
double
-expr_wkyr_to_date (double week, double year)
+expr_wkyr_to_date (double week, double year)
{
int w = week;
- if (w != week)
+ if (w != week)
{
msg (SE, _("The week argument to DATE.WKYR is not an integer. "
"The result will be system-missing."));
return SYSMIS;
}
- else if (w < 1 || w > 53)
+ else if (w < 1 || w > 53)
{
msg (SE, _("The week argument to DATE.WKYR is outside the acceptable "
"range of 1 to 53. "
"The result will be system-missing."));
return SYSMIS;
}
- else
+ else
{
double yr_1_1 = expr_ymd_to_ofs (year, 1, 1);
if (yr_1_1 != SYSMIS)
@@ -78,24 +88,24 @@ expr_wkyr_to_date (double week, double year)
}
double
-expr_yrday_to_date (double year, double yday)
+expr_yrday_to_date (double year, double yday)
{
int yd = yday;
- if (yd != yday)
+ if (yd != yday)
{
msg (SE, _("The day argument to DATE.YRDAY is not an integer. "
"The result will be system-missing."));
return SYSMIS;
}
- else if (yd < 1 || yd > 366)
+ else if (yd < 1 || yd > 366)
{
msg (SE, _("The day argument to DATE.YRDAY is outside the acceptable "
"range of 1 to 366. "
"The result will be system-missing."));
return SYSMIS;
}
- else
+ else
{
double yr_1_1 = expr_ymd_to_ofs (year, 1, 1);
if (yr_1_1 != SYSMIS)
@@ -107,10 +117,10 @@ expr_yrday_to_date (double year, double yday)
double
expr_yrmoda (double year, double month, double day)
-{
+{
if (year >= 0 && year <= 99)
year += 1900;
- else if (year != (int) year && year > 47516)
+ else if (year != (int) year && year > 47516)
{
msg (SE, _("The year argument to YRMODA is greater than 47516. "
"The result will be system-missing."));
@@ -119,14 +129,291 @@ expr_yrmoda (double year, double month, double day)
return expr_ymd_to_ofs (year, month, day);
}
+
+/* A date unit. */
+enum date_unit
+ {
+ DATE_YEARS,
+ DATE_QUARTERS,
+ DATE_MONTHS,
+ DATE_WEEKS,
+ DATE_DAYS,
+ DATE_HOURS,
+ DATE_MINUTES,
+ DATE_SECONDS
+ };
+
+/* Stores in *UNIT the unit whose name is NAME.
+ Return success. */
+static enum date_unit
+recognize_unit (struct substring name, enum date_unit *unit)
+{
+ struct unit_name
+ {
+ enum date_unit unit;
+ const struct substring name;
+ };
+ static const struct unit_name unit_names[] =
+ {
+ { DATE_YEARS, SS_LITERAL_INITIALIZER ("years") },
+ { DATE_QUARTERS, SS_LITERAL_INITIALIZER ("quarters") },
+ { DATE_MONTHS, SS_LITERAL_INITIALIZER ("months") },
+ { DATE_WEEKS, SS_LITERAL_INITIALIZER ("weeks") },
+ { DATE_DAYS, SS_LITERAL_INITIALIZER ("days") },
+ { DATE_HOURS, SS_LITERAL_INITIALIZER ("hours") },
+ { DATE_MINUTES, SS_LITERAL_INITIALIZER ("minutes") },
+ { DATE_SECONDS, SS_LITERAL_INITIALIZER ("seconds") },
+ };
+ const int unit_name_cnt = sizeof unit_names / sizeof *unit_names;
+
+ const struct unit_name *un;
+
+ for (un = unit_names; un < &unit_names[unit_name_cnt]; un++)
+ if (ss_equals_case (un->name, name))
+ {
+ *unit = un->unit;
+ return true;
+ }
+
+ /* TRANSLATORS: Don't translate the the actual unit names `weeks', `days' etc
+ They must remain in their original English. */
+ msg (SE, _("Unrecognized date unit `%.*s'. "
+ "Valid date units are `years', `quarters', `months', "
+ "`weeks', `days', `hours', `minutes', and `seconds'."),
+ (int) ss_length (name), ss_data (name));
+ return false;
+}
+
+/* Returns the number of whole years from DATE1 to DATE2,
+ where a year is defined as the same or later month, day, and
+ time of day. */
+static int
+year_diff (double date1, double date2)
+{
+ int y1, m1, d1, yd1;
+ int y2, m2, d2, yd2;
+ int diff;
+
+ assert (date2 >= date1);
+ calendar_offset_to_gregorian (date1 / DAY_S, &y1, &m1, &d1, &yd1);
+ calendar_offset_to_gregorian (date2 / DAY_S, &y2, &m2, &d2, &yd2);
+
+ diff = y2 - y1;
+ if (diff > 0)
+ {
+ int yd1 = 32 * m1 + d1;
+ int yd2 = 32 * m2 + d2;
+ if (yd2 < yd1
+ || (yd2 == yd1 && fmod (date2, DAY_S) < fmod (date1, DAY_S)))
+ diff--;
+ }
+ return diff;
+}
+
+/* Returns the number of whole months from DATE1 to DATE2,
+ where a month is defined as the same or later day and time of
+ day. */
+static int
+month_diff (double date1, double date2)
+{
+ int y1, m1, d1, yd1;
+ int y2, m2, d2, yd2;
+ int diff;
+
+ assert (date2 >= date1);
+ calendar_offset_to_gregorian (date1 / DAY_S, &y1, &m1, &d1, &yd1);
+ calendar_offset_to_gregorian (date2 / DAY_S, &y2, &m2, &d2, &yd2);
+
+ diff = ((y2 * 12) + m2) - ((y1 * 12) + m1);
+ if (diff > 0
+ && (d2 < d1
+ || (d2 == d1 && fmod (date2, DAY_S) < fmod (date1, DAY_S))))
+ diff--;
+ return diff;
+}
+
+/* Returns the number of whole quarter from DATE1 to DATE2,
+ where a quarter is defined as three months. */
+static int
+quarter_diff (double date1, double date2)
+{
+ return month_diff (date1, date2) / 3;
+}
+
+/* Returns the number of seconds in the given UNIT. */
+static int
+date_unit_duration (enum date_unit unit)
+{
+ switch (unit)
+ {
+ case DATE_WEEKS:
+ return WEEK_S;
+
+ case DATE_DAYS:
+ return DAY_S;
+
+ case DATE_HOURS:
+ return H_S;
+
+ case DATE_MINUTES:
+ return MIN_S;
+
+ case DATE_SECONDS:
+ return 1;
+
+ default:
+ NOT_REACHED ();
+ }
+}
+
+/* Returns the span from DATE1 to DATE2 in terms of UNIT_NAME. */
+double
+expr_date_difference (double date1, double date2, struct substring unit_name)
+{
+ enum date_unit unit;
+
+ if (!recognize_unit (unit_name, &unit))
+ return SYSMIS;
+
+ switch (unit)
+ {
+ case DATE_YEARS:
+ return (date2 >= date1
+ ? year_diff (date1, date2)
+ : -year_diff (date2, date1));
+
+ case DATE_QUARTERS:
+ return (date2 >= date1
+ ? quarter_diff (date1, date2)
+ : -quarter_diff (date2, date1));
+
+ case DATE_MONTHS:
+ return (date2 >= date1
+ ? month_diff (date1, date2)
+ : -month_diff (date2, date1));
+
+ case DATE_WEEKS:
+ case DATE_DAYS:
+ case DATE_HOURS:
+ case DATE_MINUTES:
+ case DATE_SECONDS:
+ return trunc ((date2 - date1) / date_unit_duration (unit));
+ }
+
+ NOT_REACHED ();
+}
+
+/* How to deal with days out of range for a given month. */
+enum date_sum_method
+ {
+ SUM_ROLLOVER, /* Roll them over to the next month. */
+ SUM_CLOSEST /* Use the last day of the month. */
+ };
+
+/* Stores in *METHOD the method whose name is NAME.
+ Return success. */
+static bool
+recognize_method (struct substring method_name, enum date_sum_method *method)
+{
+ if (ss_equals_case (method_name, ss_cstr ("closest")))
+ {
+ *method = SUM_CLOSEST;
+ return true;
+ }
+ else if (ss_equals_case (method_name, ss_cstr ("rollover")))
+ {
+ *method = SUM_ROLLOVER;
+ return true;
+ }
+ else
+ {
+ msg (SE, _("Invalid DATESUM method. "
+ "Valid choices are `closest' and `rollover'."));
+ return false;
+ }
+}
+
+/* Returns DATE advanced by the given number of MONTHS, with
+ day-of-month overflow resolved using METHOD. */
+static double
+add_months (double date, int months, enum date_sum_method method)
+{
+ int y, m, d, yd;
+ double output;
+ char *error;
+
+ calendar_offset_to_gregorian (date / DAY_S, &y, &m, &d, &yd);
+ y += months / 12;
+ m += months % 12;
+ if (m < 1)
+ {
+ m += 12;
+ y--;
+ }
+ else if (m > 12)
+ {
+ m -= 12;
+ y++;
+ }
+ assert (m >= 1 && m <= 12);
+
+ if (method == SUM_CLOSEST && d > calendar_days_in_month (y, m))
+ d = calendar_days_in_month (y, m);
+
+ output = calendar_gregorian_to_offset (y, m, d, &error);
+ if (output != SYSMIS)
+ output = (output * DAY_S) + fmod (date, DAY_S);
+ else
+ {
+ msg (SE, "%s", error);
+ free (error);
+ }
+ return output;
+}
+
+/* Returns DATE advanced by the given QUANTITY of units given in
+ UNIT_NAME, with day-of-month overflow resolved using
+ METHOD_NAME. */
+double
+expr_date_sum (double date, double quantity, struct substring unit_name,
+ struct substring method_name)
+{
+ enum date_unit unit;
+ enum date_sum_method method;
+
+ if (!recognize_unit (unit_name, &unit)
+ || !recognize_method (method_name, &method))
+ return SYSMIS;
+
+ switch (unit)
+ {
+ case DATE_YEARS:
+ return add_months (date, trunc (quantity) * 12, method);
+
+ case DATE_QUARTERS:
+ return add_months (date, trunc (quantity) * 3, method);
+
+ case DATE_MONTHS:
+ return add_months (date, trunc (quantity), method);
+
+ case DATE_WEEKS:
+ case DATE_DAYS:
+ case DATE_HOURS:
+ case DATE_MINUTES:
+ case DATE_SECONDS:
+ return date + quantity * date_unit_duration (unit);
+ }
+
+ NOT_REACHED ();
+}
int
-compare_string (const struct fixed_string *a, const struct fixed_string *b)
+compare_string_3way (const struct substring *a, const struct substring *b)
{
size_t i;
for (i = 0; i < a->length && i < b->length; i++)
- if (a->string[i] != b->string[i])
+ if (a->string[i] != b->string[i])
return a->string[i] < b->string[i] ? -1 : 1;
for (; i < a->length; i++)
if (a->string[i] != ' ')
@@ -138,7 +425,7 @@ compare_string (const struct fixed_string *a, const struct fixed_string *b)
}
size_t
-count_valid (double *d, size_t d_cnt)
+count_valid (double *d, size_t d_cnt)
{
size_t valid_cnt;
size_t i;
@@ -149,19 +436,19 @@ count_valid (double *d, size_t d_cnt)
return valid_cnt;
}
-struct fixed_string
-alloc_string (struct expression *e, size_t length)
+struct substring
+alloc_string (struct expression *e, size_t length)
{
- struct fixed_string s;
+ struct substring s;
s.length = length;
s.string = pool_alloc (e->eval_pool, length);
return s;
}
-struct fixed_string
-copy_string (struct expression *e, const char *old, size_t length)
+struct substring
+copy_string (struct expression *e, const char *old, size_t length)
{
- struct fixed_string s = alloc_string (e, length);
+ struct substring s = alloc_string (e, length);
memcpy (s.string, old, length);
return s;
}
@@ -173,10 +460,10 @@ copy_string (struct expression *e, const char *old, size_t length)
satisfactory. We only match the example values given in AS
310 to the first 5 significant digits. */
double
-ncdf_beta (double x, double a, double b, double lambda)
+ncdf_beta (double x, double a, double b, double lambda)
{
double c;
-
+
if (x <= 0. || x >= 1. || a <= 0. || b <= 0. || lambda <= 0.)
return SYSMIS;
@@ -208,7 +495,7 @@ ncdf_beta (double x, double a, double b, double lambda)
sum = ax;
iter = 0;
- do
+ do
{
iter++;
temp -= gx;
@@ -221,10 +508,10 @@ ncdf_beta (double x, double a, double b, double lambda)
err_bound = (temp - gx) * sumq;
}
while (iter < iter_max && err_bound > err_max);
-
+
return sum;
}
- else
+ else
{
/* Algorithm AS 310. */
double m, m_sqrt;
@@ -232,14 +519,14 @@ ncdf_beta (double x, double a, double b, double lambda)
double t, q, r, psum, beta, s1, gx, fx, temp, ftemp, t0, s0, sum, s;
double err_bound;
double err_max = 2 * DBL_EPSILON;
-
+
iter = 0;
-
+
m = floor (c + .5);
m_sqrt = sqrt (m);
iter_lower = m - 5. * m_sqrt;
iter_upper = m + 5. * m_sqrt;
-
+
t = -c + m * log (c) - gsl_sf_lngamma (m + 1.);
q = exp (t);
r = q;
@@ -254,7 +541,7 @@ ncdf_beta (double x, double a, double b, double lambda)
sum = q * temp;
iter1 = m;
- while (iter1 >= iter_lower && q >= err_max)
+ while (iter1 >= iter_lower && q >= err_max)
{
q = q * iter1 / c;
iter++;
@@ -271,7 +558,7 @@ ncdf_beta (double x, double a, double b, double lambda)
s0 = a * log (x) + b * log (1. - x);
s = 0.;
- for (j = 0; j < iter1; j++)
+ for (j = 0; j < iter1; j++)
{
double t1;
s += exp (t0 + s0 + j * log (x));
@@ -284,7 +571,7 @@ ncdf_beta (double x, double a, double b, double lambda)
temp = ftemp;
gx = fx;
iter2 = m;
- for (;;)
+ for (;;)
{
double ebd = err_bound + (1. - psum) * temp;
if (ebd < err_max || iter >= iter_upper)
@@ -304,16 +591,16 @@ ncdf_beta (double x, double a, double b, double lambda)
}
double
-cdf_bvnor (double x0, double x1, double r)
+cdf_bvnor (double x0, double x1, double r)
{
- double z = x0 * x0 - 2. * r * x0 * x1 + x1 * x1;
+ double z = pow2 (x0) - 2. * r * x0 * x1 + pow2 (x1);
return exp (-z / (2. * (1 - r * r))) * (2. * M_PI * sqrt (1 - r * r));
}
double
-idf_fdist (double P, double df1, double df2)
+idf_fdist (double P, double df1, double df2)
{
- double temp = gslextras_cdf_beta_Pinv (P, df1 / 2, df2 / 2);
+ double temp = gsl_cdf_beta_Pinv (P, df1 / 2, df2 / 2);
return temp * df2 / ((1. - temp) * df1);
}
@@ -347,13 +634,13 @@ idf_fdist (double P, double df1, double df2)
/* Returns the density of the noncentral beta distribution with
noncentrality parameter LAMBDA. */
double
-npdf_beta (double x, double a, double b, double lambda)
+npdf_beta (double x, double a, double b, double lambda)
{
if (lambda < 0. || a <= 0. || b <= 0.)
return SYSMIS;
else if (lambda == 0.)
return gsl_ran_beta_pdf (x, a, b);
- else
+ else
{
double max_error = 2 * DBL_EPSILON;
int max_iter = 200;
@@ -363,13 +650,13 @@ npdf_beta (double x, double a, double b, double lambda)
double sum = weight * term;
double psum = weight;
int k;
- for (k = 1; k <= max_iter && 1 - psum < max_error; k++) {
+ for (k = 1; k <= max_iter && 1 - psum < max_error; k++) {
weight *= lambda2 / k;
term *= x * (a + b) / a;
sum += weight * term;
psum += weight;
a += 1;
- }
+ }
return sum;
}
}