Commit cec403d7 authored by Morten Welinder's avatar Morten Welinder

Funcs: add COUNTIFS and fix COUNTIF.

parent c82f0f27
2016-08-08 Morten Welinder <terra@gnome.org>
* src/criteria.c (criteria_inspect_values): Add flag for string
coercion. Only the equality test wants that.
* src/value.c (criteria_inspect_values): Floats don't match
errors.
......
......@@ -8,16 +8,12 @@ Jean:
Morten:
* Avoid gnome-common dependency.
* New function CONCAT.
* New function TEXTJOIN.
* New function IFS.
* New function SWITCH.
* New function SUMIFS.
* New function AVERAGEIFS.
* New function MINIFS.
* New function MAXIFS.
* New text functions CONCAT, TEXTJOIN.
* New selection functions IFS, SWITCH.
* New aggregation functions SUMIFS, AVERAGEIFS, MINIFS, MAXIFS, COUNTIFS.
* Fix criteria function issue with errors in the selector area.
* Fix corner case for MINA and MAXA.
* Fix criteria matching of numbers against strings.
--------------------------------------------------------------------------
Gnumeric 1.12.31
......
......@@ -3440,6 +3440,15 @@ The depreciation coefficient used is:
@EXCEL=This function is Excel compatible.
@SEEALSO=COUNT,SUMIF
@CATEGORY=Mathematics
@FUNCTION=COUNTIFS
@SHORTDESC=count of the cells meeting the given @{criteria}
@SYNTAX=COUNTIFS(range,criteria,…)
@ARGUMENTDESCRIPTION=@{range}: cell area
@{criteria}: condition for a cell to be counted
@EXCEL=This function is Excel compatible.
@SEEALSO=COUNT,SUMIF
@CATEGORY=Mathematics
@FUNCTION=CSC
@SHORTDESC=the cosecant of @{x}
......
......@@ -11261,6 +11261,39 @@
</para>
</refsect1>
</refentry>
<refentry id="gnumeric-function-COUNTIFS">
<refmeta>
<refentrytitle>
<function>COUNTIFS</function>
</refentrytitle>
</refmeta>
<refnamediv>
<refname>
<function>COUNTIFS</function>
</refname>
<refpurpose>
count of the cells meeting the given <parameter>criteria</parameter>
</refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis><function>COUNTIFS</function>(<parameter>range</parameter>,<parameter>criteria</parameter>,<parameter/>…)</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Arguments</title>
<para><parameter>range</parameter>: cell area</para>
<para><parameter>criteria</parameter>: condition for a cell to be counted</para>
</refsect1>
<refsect1>
<title>Microsoft Excel Compatibility</title>
<para>This function is Excel compatible.</para>
</refsect1>
<refsect1>
<title>See also</title>
<para><link linkend="gnumeric-function-COUNT"><function>COUNT</function></link>,
<link linkend="gnumeric-function-SUMIF"><function>SUMIF</function></link>.
</para>
</refsect1>
</refentry>
<refentry id="gnumeric-function-CSC">
<refmeta>
<refentrytitle>
......
......@@ -57,88 +57,10 @@ GNM_PLUGIN_MODULE_HEADER;
/***************************************************************************/
static GnmValue *
ifs_func (GPtrArray *data, GPtrArray *crits, GnmValue const *vals,
float_range_function_t fun, GnmStdError err,
GnmEvalPos const *ep, CollectFlags flags)
{
int sx, sy, x, y;
unsigned ui, N = 0, nalloc = 0;
gnm_float *xs = NULL;
GnmValue *res = NULL;
gnm_float fres;
g_return_val_if_fail (data->len == crits->len, NULL);
if (flags & ~(COLLECT_IGNORE_STRINGS |
COLLECT_IGNORE_BOOLS |
COLLECT_IGNORE_BLANKS |
COLLECT_IGNORE_ERRORS)) {
g_warning ("unsupported flags in ifs_func %x", flags);
}
sx = value_area_get_width (vals, ep);
sy = value_area_get_height (vals, ep);
for (ui = 0; ui < data->len; ui++) {
GnmValue const *datai = g_ptr_array_index (data, ui);
if (value_area_get_width (datai, ep) != sx ||
value_area_get_height (datai, ep) != sy)
return value_new_error_VALUE (ep);
}
for (y = 0; y < sy; y++) {
for (x = 0; x < sy; x++) {
GnmValue const *v;
gboolean match = TRUE;
for (ui = 0; match && ui < crits->len; ui++) {
GnmCriteria *crit = g_ptr_array_index (crits, ui);
GnmValue const *datai = g_ptr_array_index (data, ui);
v = value_area_get_x_y (datai, x, y, ep);
match = crit->fun (v, crit);
}
if (!match)
continue;
// Match. Maybe collect the data point.
v = value_area_get_x_y (vals, x, y, ep);
if ((flags & COLLECT_IGNORE_STRINGS) && VALUE_IS_STRING (v))
continue;
if ((flags & COLLECT_IGNORE_BOOLS) && VALUE_IS_BOOLEAN (v))
continue;
if ((flags & COLLECT_IGNORE_BLANKS) && VALUE_IS_EMPTY (v))
continue;
if ((flags & COLLECT_IGNORE_ERRORS) && VALUE_IS_ERROR (v))
continue;
if (VALUE_IS_ERROR (v)) {
res = value_dup (v);
goto out;
}
if (N >= nalloc) {
nalloc = (2 * nalloc) + 100;
xs = g_renew (gnm_float, xs, nalloc);
}
xs[N++] = value_get_as_float (v);
}
}
if (fun (xs, N, &fres)) {
res = value_new_error_std (ep, err);
} else
res = value_new_float (fres);
out:
g_free (xs);
return res;
}
static GnmValue *
oldstyle_if_func (GnmFuncEvalInfo *ei, GnmValue const * const *argv,
float_range_function_t fun, GnmStdError err)
float_range_function_t fun, GnmStdError err,
CollectFlags flags)
{
GPtrArray *crits = g_ptr_array_new_with_free_func ((GDestroyNotify)free_criteria);
GPtrArray *data = g_ptr_array_new ();
......@@ -171,11 +93,9 @@ oldstyle_if_func (GnmFuncEvalInfo *ei, GnmValue const * const *argv,
insanity = FALSE;
}
res = ifs_func (data, crits, vals,
fun, err, ei->pos,
COLLECT_IGNORE_STRINGS |
COLLECT_IGNORE_BLANKS |
COLLECT_IGNORE_BOOLS);
res = gnm_ifs_func (data, crits, vals,
fun, err, ei->pos,
flags);
out:
g_ptr_array_free (data, TRUE);
......@@ -186,7 +106,8 @@ out:
static GnmValue *
newstyle_if_func (GnmFuncEvalInfo *ei, int argc, GnmExprConstPtr const *argv,
float_range_function_t fun, GnmStdError err)
float_range_function_t fun, GnmStdError err,
gboolean no_data)
{
GPtrArray *crits = g_ptr_array_new_with_free_func ((GDestroyNotify)free_criteria);
GPtrArray *data = g_ptr_array_new_with_free_func ((GDestroyNotify)value_release);
......@@ -195,25 +116,28 @@ newstyle_if_func (GnmFuncEvalInfo *ei, int argc, GnmExprConstPtr const *argv,
GnmValue *res;
GnmValue *vals = NULL;
int i;
int cstart = no_data ? 0 : 1;
if ((argc & 1) == 0) {
if ((argc - cstart) & 1) {
res = value_new_error_VALUE (ei->pos);
goto out;
}
vals = gnm_expr_eval (argv[0], ei->pos,
GNM_EXPR_EVAL_PERMIT_NON_SCALAR |
GNM_EXPR_EVAL_WANT_REF);
if (VALUE_IS_ERROR (vals)) {
res = value_dup (vals);
goto out;
}
if (!VALUE_IS_CELLRANGE (vals)) {
res = value_new_error_VALUE (ei->pos);
goto out;
if (!no_data) {
vals = gnm_expr_eval (argv[0], ei->pos,
GNM_EXPR_EVAL_PERMIT_NON_SCALAR |
GNM_EXPR_EVAL_WANT_REF);
if (VALUE_IS_ERROR (vals)) {
res = value_dup (vals);
goto out;
}
if (!VALUE_IS_CELLRANGE (vals)) {
res = value_new_error_VALUE (ei->pos);
goto out;
}
}
for (i = 1; i + 1 < argc; i += 2) {
for (i = cstart; i + 1 < argc; i += 2) {
GnmValue *area, *crit;
area = gnm_expr_eval (argv[i], ei->pos,
......@@ -223,6 +147,8 @@ newstyle_if_func (GnmFuncEvalInfo *ei, int argc, GnmExprConstPtr const *argv,
res = area;
goto out;
}
if (no_data && !vals)
vals = value_dup (area);
g_ptr_array_add (data, area);
crit = gnm_expr_eval (argv[i + 1], ei->pos,
......@@ -236,11 +162,19 @@ newstyle_if_func (GnmFuncEvalInfo *ei, int argc, GnmExprConstPtr const *argv,
value_release (crit);
}
res = ifs_func (data, crits, vals,
fun, err, ei->pos,
COLLECT_IGNORE_STRINGS |
COLLECT_IGNORE_BLANKS |
COLLECT_IGNORE_BOOLS);
if (!vals) {
// COUNTIFS with no arguments.
res = value_new_error_VALUE (ei->pos);
goto out;
}
res = gnm_ifs_func (data, crits, vals,
fun, err, ei->pos,
(no_data
? 0
: COLLECT_IGNORE_STRINGS |
COLLECT_IGNORE_BLANKS |
COLLECT_IGNORE_BOOLS));
out:
g_ptr_array_free (data, TRUE);
......@@ -698,66 +632,36 @@ static GnmFuncHelp const help_countif[] = {
{ GNM_FUNC_HELP_END}
};
typedef struct {
GnmCriteria *crit;
int count;
} CountIfClosure;
static GnmValue *
cb_countif (GnmCellIter const *iter, CountIfClosure *res)
gnumeric_countif (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
{
GnmCell *cell = iter->cell;
GnmValue *v;
if (cell) {
gnm_cell_eval (cell);
v = cell->value;
} else
v = value_new_empty (); /* Never released */
GnmValue const * argv3[3];
if (!VALUE_IS_EMPTY (v) && !VALUE_IS_NUMBER (v) && !VALUE_IS_STRING (v))
return NULL;
argv3[0] = argv[0];
argv3[1] = argv[1];
argv3[2] = NULL;
if (!res->crit->fun (v, res->crit))
return NULL;
return oldstyle_if_func (ei, argv3, gnm_range_count, GNM_ERROR_DIV0,
0);
}
res->count++;
/***************************************************************************/
return NULL;
}
static GnmFuncHelp const help_countifs[] = {
{ GNM_FUNC_HELP_NAME, F_("COUNTIFS:count of the cells meeting the given @{criteria}")},
{ GNM_FUNC_HELP_ARG, F_("range:cell area")},
{ GNM_FUNC_HELP_ARG, F_("criteria:condition for a cell to be counted")},
{ GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
{ GNM_FUNC_HELP_SEEALSO, "COUNT,SUMIF"},
{ GNM_FUNC_HELP_END}
};
static GnmValue *
gnumeric_countif (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
gnumeric_countifs (GnmFuncEvalInfo *ei, int argc, GnmExprConstPtr const *argv)
{
GnmValueRange const *r = &argv[0]->v_range;
Sheet *sheet;
GnmValue *problem;
CountIfClosure res;
GODateConventions const *date_conv =
workbook_date_conv (ei->pos->sheet->workbook);
/* XL has some limitations on @range that we currently emulate, but do
* not need to.
* 1) @range must be a range, arrays are not supported
* 2) @range can not be 3d */
if (!VALUE_IS_CELLRANGE (argv[0]) ||
((sheet = eval_sheet (r->cell.a.sheet, ei->pos->sheet)) != r->cell.b.sheet &&
r->cell.b.sheet != NULL) ||
(!VALUE_IS_NUMBER (argv[1]) && !VALUE_IS_STRING (argv[1])))
return value_new_error_VALUE (ei->pos);
res.count = 0;
res.crit = parse_criteria (argv[1], date_conv, TRUE);
problem = sheet_foreach_cell_in_range
(sheet, res.crit->iter_flags,
r->cell.a.col, r->cell.a.row, r->cell.b.col, r->cell.b.row,
(CellIterFunc) &cb_countif, &res);
free_criteria (res.crit);
if (NULL != problem)
return value_new_error_VALUE (ei->pos);
return value_new_int (res.count);
return newstyle_if_func (ei, argc, argv,
gnm_range_count, GNM_ERROR_DIV0,
TRUE);
}
/***************************************************************************/
......@@ -780,7 +684,10 @@ static GnmFuncHelp const help_sumif[] = {
static GnmValue *
gnumeric_sumif (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
{
return oldstyle_if_func (ei, argv, gnm_range_sum, GNM_ERROR_DIV0);
return oldstyle_if_func (ei, argv, gnm_range_sum, GNM_ERROR_DIV0,
COLLECT_IGNORE_STRINGS |
COLLECT_IGNORE_BLANKS |
COLLECT_IGNORE_BOOLS);
}
/***************************************************************************/
......@@ -798,7 +705,9 @@ static GnmFuncHelp const help_sumifs[] = {
static GnmValue *
gnumeric_sumifs (GnmFuncEvalInfo *ei, int argc, GnmExprConstPtr const *argv)
{
return newstyle_if_func (ei, argc, argv, gnm_range_sum, GNM_ERROR_DIV0);
return newstyle_if_func (ei, argc, argv,
gnm_range_sum, GNM_ERROR_DIV0,
FALSE);
}
/***************************************************************************/
......@@ -816,7 +725,10 @@ static GnmFuncHelp const help_averageif[] = {
static GnmValue *
gnumeric_averageif (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
{
return oldstyle_if_func (ei, argv, gnm_range_average, GNM_ERROR_DIV0);
return oldstyle_if_func (ei, argv, gnm_range_average, GNM_ERROR_DIV0,
COLLECT_IGNORE_STRINGS |
COLLECT_IGNORE_BLANKS |
COLLECT_IGNORE_BOOLS);
}
/***************************************************************************/
......@@ -835,7 +747,8 @@ static GnmValue *
gnumeric_averageifs (GnmFuncEvalInfo *ei, int argc, GnmExprConstPtr const *argv)
{
return newstyle_if_func (ei, argc, argv,
gnm_range_average, GNM_ERROR_DIV0);
gnm_range_average, GNM_ERROR_DIV0,
FALSE);
}
/***************************************************************************/
......@@ -854,7 +767,8 @@ static GnmValue *
gnumeric_minifs (GnmFuncEvalInfo *ei, int argc, GnmExprConstPtr const *argv)
{
return newstyle_if_func (ei, argc, argv,
gnm_range_min, GNM_ERROR_DIV0);
gnm_range_min, GNM_ERROR_DIV0,
FALSE);
}
/***************************************************************************/
......@@ -873,7 +787,8 @@ static GnmValue *
gnumeric_maxifs (GnmFuncEvalInfo *ei, int argc, GnmExprConstPtr const *argv)
{
return newstyle_if_func (ei, argc, argv,
gnm_range_max, GNM_ERROR_DIV0);
gnm_range_max, GNM_ERROR_DIV0,
FALSE);
}
/***************************************************************************/
......@@ -3490,10 +3405,13 @@ GnmFuncDescriptor const math_functions[] = {
gnumeric_coth, NULL, NULL, NULL,
GNM_FUNC_SIMPLE, GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_NO_TESTSUITE },
/* MS Excel puts this in statistical */
{ "countif", "rS", help_countif,
gnumeric_countif, NULL, NULL, NULL,
GNM_FUNC_SIMPLE, GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
{ "countifs", NULL, help_countifs,
NULL, gnumeric_countifs, NULL, NULL,
GNM_FUNC_SIMPLE,
GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_NO_TESTSUITE },
{ "ceil", "f", help_ceil,
gnumeric_ceil, NULL, NULL, NULL,
......
......@@ -39,6 +39,7 @@
<function name="coth"/>
<function name="cotpi"/>
<function name="countif"/>
<function name="countifs"/>
<function name="csc"/>
<function name="csch"/>
<function name="degrees"/>
......
......@@ -4,9 +4,8 @@ typedef enum { CRIT_NULL, CRIT_FLOAT, CRIT_WRONGTYPE, CRIT_STRING } CritType;
static CritType
criteria_inspect_values (GnmValue const *x, gnm_float *xr, gnm_float *yr,
GnmCriteria *crit)
GnmCriteria *crit, gboolean coerce_to_float)
{
GnmValue *vx;
GnmValue const *y = crit->x;
if (x == NULL || y == NULL)
......@@ -34,7 +33,8 @@ criteria_inspect_values (GnmValue const *x, gnm_float *xr, gnm_float *yr,
g_warning ("This should not happen. Please report.");
return CRIT_WRONGTYPE;
case VALUE_FLOAT:
case VALUE_FLOAT: {
GnmValue *vx;
*yr = value_get_as_float (y);
if (VALUE_IS_BOOLEAN (x) || VALUE_IS_ERROR (x))
......@@ -44,6 +44,9 @@ criteria_inspect_values (GnmValue const *x, gnm_float *xr, gnm_float *yr,
return CRIT_FLOAT;
}
if (!coerce_to_float)
return CRIT_WRONGTYPE;
vx = format_match (value_peek_string (x), NULL, crit->date_conv);
if (VALUE_IS_EMPTY (vx) ||
VALUE_IS_BOOLEAN (y) != VALUE_IS_BOOLEAN (vx)) {
......@@ -55,6 +58,7 @@ criteria_inspect_values (GnmValue const *x, gnm_float *xr, gnm_float *yr,
value_release (vx);
return CRIT_FLOAT;
}
}
}
......@@ -64,7 +68,7 @@ criteria_test_equal (GnmValue const *x, GnmCriteria *crit)
gnm_float xf, yf;
GnmValue const *y = crit->x;
switch (criteria_inspect_values (x, &xf, &yf, crit)) {
switch (criteria_inspect_values (x, &xf, &yf, crit, TRUE)) {
default:
g_assert_not_reached ();
case CRIT_NULL:
......@@ -84,7 +88,7 @@ criteria_test_unequal (GnmValue const *x, GnmCriteria *crit)
{
gnm_float xf, yf;
switch (criteria_inspect_values (x, &xf, &yf, crit)) {
switch (criteria_inspect_values (x, &xf, &yf, crit, FALSE)) {
default:
g_assert_not_reached ();
case CRIT_NULL:
......@@ -105,7 +109,7 @@ criteria_test_less (GnmValue const *x, GnmCriteria *crit)
gnm_float xf, yf;
GnmValue const *y = crit->x;
switch (criteria_inspect_values (x, &xf, &yf, crit)) {
switch (criteria_inspect_values (x, &xf, &yf, crit, FALSE)) {
default:
g_assert_not_reached ();
case CRIT_NULL:
......@@ -125,7 +129,7 @@ criteria_test_greater (GnmValue const *x, GnmCriteria *crit)
gnm_float xf, yf;
GnmValue const *y = crit->x;
switch (criteria_inspect_values (x, &xf, &yf, crit)) {
switch (criteria_inspect_values (x, &xf, &yf, crit, FALSE)) {
default:
g_assert_not_reached ();
case CRIT_NULL:
......@@ -145,7 +149,7 @@ criteria_test_less_or_equal (GnmValue const *x, GnmCriteria *crit)
gnm_float xf, yf;
GnmValue const *y = crit->x;
switch (criteria_inspect_values (x, &xf, &yf, crit)) {
switch (criteria_inspect_values (x, &xf, &yf, crit, FALSE)) {
default:
g_assert_not_reached ();
case CRIT_NULL:
......@@ -165,7 +169,7 @@ criteria_test_greater_or_equal (GnmValue const *x, GnmCriteria *crit)
gnm_float xf, yf;
GnmValue const *y = crit->x;
switch (criteria_inspect_values (x, &xf, &yf, crit)) {
switch (criteria_inspect_values (x, &xf, &yf, crit, FALSE)) {
default:
g_assert_not_reached ();
case CRIT_NULL:
......@@ -569,3 +573,84 @@ filter_row:
}
/****************************************************************************/
GnmValue *
gnm_ifs_func (GPtrArray *data, GPtrArray *crits, GnmValue const *vals,
float_range_function_t fun, GnmStdError err,
GnmEvalPos const *ep, CollectFlags flags)
{
int sx, sy, x, y;
unsigned ui, N = 0, nalloc = 0;
gnm_float *xs = NULL;
GnmValue *res = NULL;
gnm_float fres;
g_return_val_if_fail (data->len == crits->len, NULL);
if (flags & ~(COLLECT_IGNORE_STRINGS |
COLLECT_IGNORE_BOOLS |
COLLECT_IGNORE_BLANKS |
COLLECT_IGNORE_ERRORS)) {
g_warning ("unsupported flags in gnm_ifs_func %x", flags);
}
sx = value_area_get_width (vals, ep);
sy = value_area_get_height (vals, ep);
for (ui = 0; ui < data->len; ui++) {
GnmValue const *datai = g_ptr_array_index (data, ui);
if (value_area_get_width (datai, ep) != sx ||
value_area_get_height (datai, ep) != sy)
return value_new_error_VALUE (ep);
}
for (y = 0; y < sy; y++) {
for (x = 0; x < sx; x++) {
GnmValue const *v;
gboolean match = TRUE;
for (ui = 0; match && ui < crits->len; ui++) {
GnmCriteria *crit = g_ptr_array_index (crits, ui);
GnmValue const *datai = g_ptr_array_index (data, ui);
v = value_area_get_x_y (datai, x, y, ep);
match = crit->fun (v, crit);
}
if (!match)
continue;
// Match. Maybe collect the data point.
v = value_area_get_x_y (vals, x, y, ep);
if ((flags & COLLECT_IGNORE_STRINGS) && VALUE_IS_STRING (v))
continue;
if ((flags & COLLECT_IGNORE_BOOLS) && VALUE_IS_BOOLEAN (v))
continue;
if ((flags & COLLECT_IGNORE_BLANKS) && VALUE_IS_EMPTY (v))
continue;
if ((flags & COLLECT_IGNORE_ERRORS) && VALUE_IS_ERROR (v))
continue;
if (VALUE_IS_ERROR (v)) {
res = value_dup (v);
goto out;
}
if (N >= nalloc) {
nalloc = (2 * nalloc) + 100;
xs = g_renew (gnm_float, xs, nalloc);
}
xs[N++] = value_get_as_float (v);
}
}
if (fun (xs, N, &fres)) {
res = value_new_error_std (ep, err);
} else
res = value_new_float (fres);
out:
g_free (xs);
return res;
}
/****************************************************************************/
......@@ -9,6 +9,7 @@
#include <cell.h>
#include <gutils.h>
#include <workbook.h>
#include <collect.h>
#include <goffice/goffice.h>
#include <string.h>
......@@ -48,6 +49,10 @@ GSList *parse_database_criteria (GnmEvalPos const *ep,
int find_column_of_field (GnmEvalPos const *ep,
GnmValue const *database, GnmValue const *field);
GnmValue *gnm_ifs_func (GPtrArray *data, GPtrArray *crits, GnmValue const *vals,
float_range_function_t fun, GnmStdError err,
GnmEvalPos const *ep, CollectFlags flags);
G_END_DECLS
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment