From ef1b71f112a60a47edb553c1a81c72ef5020dc17 Mon Sep 17 00:00:00 2001 From: Dave Challis Date: Tue, 14 Jun 2011 16:50:43 +0100 Subject: [PATCH] Added support for ROUND(), CEIL(), FLOOR() functions, including tests. --- data/numbers.ttl | 25 ++++ src/frontend/decimal.h | 4 + src/frontend/filter.c | 176 ++++++++++++++++++++++++++- src/frontend/filter.h | 7 +- src/frontend/results.c | 6 + tests/httpd/exemplar/functions-abs | 61 +++++++--- tests/httpd/exemplar/functions-ceil | 45 +++++++ tests/httpd/exemplar/functions-floor | 45 +++++++ tests/httpd/exemplar/functions-round | 45 +++++++ tests/httpd/scripts/functions-abs | 2 +- tests/httpd/scripts/functions-ceil | 9 ++ tests/httpd/scripts/functions-floor | 9 ++ tests/httpd/scripts/functions-round | 9 ++ 13 files changed, 420 insertions(+), 23 deletions(-) create mode 100644 tests/httpd/exemplar/functions-ceil create mode 100644 tests/httpd/exemplar/functions-floor create mode 100644 tests/httpd/exemplar/functions-round create mode 100755 tests/httpd/scripts/functions-ceil create mode 100755 tests/httpd/scripts/functions-floor create mode 100755 tests/httpd/scripts/functions-round diff --git a/data/numbers.ttl b/data/numbers.ttl index a8be635..1ba33d8 100644 --- a/data/numbers.ttl +++ b/data/numbers.ttl @@ -23,3 +23,28 @@

"12345678901234567890.23239300"^^xsd:float . "-12345678901234567890.23239300"^^xsd:float . "+12345678901234567890.23239300"^^xsd:float . + + "2.4999"^^xsd:decimal . + "2.5"^^xsd:decimal . + "-2.5"^^xsd:decimal . + "-2.500001"^^xsd:decimal . + "2.500001"^^xsd:decimal . + "-2.499999"^^xsd:decimal . + + "0000.0004999000"^^xsd:decimal . + "2.000050000"^^xsd:decimal . + "-2.00050000"^^xsd:decimal . + + "2.4999"^^xsd:float . + "2.5"^^xsd:float . + "-2.5"^^xsd:float . + "-2.500001"^^xsd:float . + "2.500001"^^xsd:float . + "-2.499999"^^xsd:float . + + "2.4999"^^xsd:double . + "2.5"^^xsd:double . + "-2.5"^^xsd:double . + "-2.500001"^^xsd:double . + "2.500001"^^xsd:double . + "-2.499999"^^xsd:double . diff --git a/src/frontend/decimal.h b/src/frontend/decimal.h index 6e37de0..1bd7435 100644 --- a/src/frontend/decimal.h +++ b/src/frontend/decimal.h @@ -45,6 +45,10 @@ int fs_decimal_to_double(const fs_decimal *d, double *fp); /* convert decimal value into nearest int represenation, non 0 on failure */ int fs_decimal_to_int64(const fs_decimal *d, int64_t *in); +/* copy one decimal to another */ +void fs_decimal_copy(const fs_decimal *from, fs_decimal *to); + + /* comparison functions */ /* return true if the two decimals compare equal */ diff --git a/src/frontend/filter.c b/src/frontend/filter.c index de3f368..5d7c426 100644 --- a/src/frontend/filter.c +++ b/src/frontend/filter.c @@ -279,7 +279,7 @@ fs_value fn_minus(fs_query *q, fs_value a) fs_value fn_numeric_abs(fs_query *q, fs_value a) { if (!fs_is_numeric(&a)) { - return fs_value_error(FS_ERROR_INVALID_TYPE, "non-numeric arguments to fn:abs"); + return fs_value_error(FS_ERROR_INVALID_TYPE, "non-numeric argument to fn:abs"); } if (a.attr == fs_c.xsd_double || a.attr == fs_c.xsd_float) { @@ -303,6 +303,180 @@ fs_value fn_numeric_abs(fs_query *q, fs_value a) return a; } +fs_value fn_numeric_floor(fs_query *q, fs_value a) +{ + if (!fs_is_numeric(&a)) { + return fs_value_error(FS_ERROR_INVALID_TYPE, "non-numeric argument to fn:floor"); + } + + if (a.attr == fs_c.xsd_integer) { + /* do nothing for integers */ + return a; + } + + if (a.attr == fs_c.xsd_double || a.attr == fs_c.xsd_float) { + a.fp = floor(a.fp); + } else if (a.attr == fs_c.xsd_decimal) { + fs_decimal r; + + if (fs_decimal_less_than(&a.de, fs_decimal_zero)) { + fs_decimal one; + fs_decimal_init_from_double(&one, (double)1.0); + fs_decimal_subtract(&a.de, &one, &r); + } + else { + fs_decimal_copy(&a.de, &r); + } + + for (int i=FS_D_OVER_DIGITS+FS_D_INT_DIGITS; i < FS_D_DIGITS; i++) { + r.digit[i] = 0; + } + + fs_decimal_copy(&r, &a.de); + } else { + return fs_value_error(FS_ERROR_INVALID_TYPE, "bad arguments to fn:floor"); + } + + if (a.lex != NULL) { + a.lex = NULL; + } + + return a; +} + +fs_value fn_numeric_ceil(fs_query *q, fs_value a) +{ + if (!fs_is_numeric(&a)) { + return fs_value_error(FS_ERROR_INVALID_TYPE, "non-numeric argument to fn:ceil"); + } + + if (a.attr == fs_c.xsd_integer) { + /* do nothing for integers */ + return a; + } + + if (a.attr == fs_c.xsd_double || a.attr == fs_c.xsd_float) { + a.fp = ceil(a.fp); + } else if (a.attr == fs_c.xsd_decimal) { + fs_decimal r; + + if (fs_decimal_greater_than(&a.de, fs_decimal_zero)) { + fs_decimal one; + fs_decimal_init_from_double(&one, (double)1.0); + fs_decimal_add(&a.de, &one, &r); + } + else { + fs_decimal_copy(&a.de, &r); + } + + for (int i=FS_D_OVER_DIGITS+FS_D_INT_DIGITS; i < FS_D_DIGITS; i++) { + r.digit[i] = 0; + } + + fs_decimal_copy(&r, &a.de); + } else { + return fs_value_error(FS_ERROR_INVALID_TYPE, "bad arguments to fn:ceil"); + } + + if (a.lex != NULL) { + a.lex = NULL; + } + + return a; +} + +fs_value fn_numeric_round(fs_query *q, fs_value a) +{ + if (!fs_is_numeric(&a)) { + return fs_value_error(FS_ERROR_INVALID_TYPE, "non-numeric argument to fn:round"); + } + + if (a.attr == fs_c.xsd_integer) { + /* do nothing for integers */ + return a; + } + + if (a.attr == fs_c.xsd_double || a.attr == fs_c.xsd_float) { + if (a.fp > 0.0) { + /* rounding +ve numbers same as c round */ + a.fp = round(a.fp); + } + else if (a.fp < 0.0) { + /* -2.5 should round to -2.0 */ + double i; + double f = modf(a.fp, &i); + a.fp -= f; + if (f < -0.5) { + a.fp -= 1.0; + } + } + } else if (a.attr == fs_c.xsd_decimal) { + /* 0 = round to num, e.g. 2.x -> 2.0, -2.x -> -2.0 + 1 = round away from num, e.g. 2.x -> 3.0, -2.x -> -3.0 */ + int round_dir = -1; + int positive = fs_decimal_greater_than_equal(&a.de, fs_decimal_zero); + + int start_pos = FS_D_OVER_DIGITS+FS_D_INT_DIGITS; + if ((&a.de)->digit[start_pos] > 5) { + round_dir = 1; + } else if ((&a.de)->digit[start_pos] < 5) { + round_dir = 0; + } else { + /* first digit should be a 5 */ + for (int i=start_pos+1; i < FS_D_DIGITS; i++) { + if ((&a.de)->digit[i] > 0) { + /* abs(fractional part) is > 0.5 */ + round_dir = 1; + break; + } + } + + if (round_dir == -1) { + /* abs(fractional part) == 0.5, round depending on sign */ + if (positive) { + round_dir = 1; + } + else { + round_dir = 0; + } + } + } + + fs_decimal one; + fs_decimal_init_from_double(&one, (double)1.0); + fs_decimal r; + + if (round_dir == 1) { + /* Round integral part of number away from zero */ + if (positive) { + fs_decimal_add(&a.de, &one, &r); + } + else { + fs_decimal_subtract(&a.de, &one, &r); + } + } + else { + /* Keep integral part of number unchanged */ + fs_decimal_copy(&a.de, &r); + } + + /* Zero all fractional digits of number */ + for (int i=start_pos; i < FS_D_DIGITS; i++) { + r.digit[i] = 0; + } + + fs_decimal_copy(&r, &a.de); + } else { + return fs_value_error(FS_ERROR_INVALID_TYPE, "bad arguments to fn:round"); + } + + if (a.lex != NULL) { + a.lex = NULL; + } + + return a; +} + fs_value fn_numeric_add(fs_query *q, fs_value a, fs_value b) { #if 0 diff --git a/src/frontend/filter.h b/src/frontend/filter.h index f5de060..ea41a08 100644 --- a/src/frontend/filter.h +++ b/src/frontend/filter.h @@ -22,8 +22,13 @@ fs_value fn_greater_than(fs_query *q, fs_value a, fs_value b); fs_value fn_less_than_equal(fs_query *q, fs_value a, fs_value b); fs_value fn_greater_than_equal(fs_query *q, fs_value a, fs_value b); -/* binary maths operators */ +/* unary maths operators */ fs_value fn_numeric_abs(fs_query *q, fs_value a); +fs_value fn_numeric_round(fs_query *q, fs_value a); +fs_value fn_numeric_ceil(fs_query *q, fs_value a); +fs_value fn_numeric_floor(fs_query *q, fs_value a); + +/* binary maths operators */ fs_value fn_numeric_add(fs_query *q, fs_value a, fs_value b); fs_value fn_numeric_subtract(fs_query *q, fs_value a, fs_value b); fs_value fn_numeric_multiply(fs_query *q, fs_value a, fs_value b); diff --git a/src/frontend/results.c b/src/frontend/results.c index d7aff70..a961a30 100644 --- a/src/frontend/results.c +++ b/src/frontend/results.c @@ -243,6 +243,12 @@ fs_value fs_expression_eval(fs_query *q, int row, int block, rasqal_expression * #if RASQAL_VERSION >= 925 case RASQAL_EXPR_ABS: return fn_numeric_abs(q, fs_expression_eval(q, row, block, e->arg1)); + case RASQAL_EXPR_ROUND: + return fn_numeric_round(q, fs_expression_eval(q, row, block, e->arg1)); + case RASQAL_EXPR_CEIL: + return fn_numeric_ceil(q, fs_expression_eval(q, row, block, e->arg1)); + case RASQAL_EXPR_FLOOR: + return fn_numeric_floor(q, fs_expression_eval(q, row, block, e->arg1)); #endif case RASQAL_EXPR_AND: return fn_logical_and(q, fs_expression_eval(q, row, block, e->arg1), diff --git a/tests/httpd/exemplar/functions-abs b/tests/httpd/exemplar/functions-abs index d771bd1..399d1b9 100644 --- a/tests/httpd/exemplar/functions-abs +++ b/tests/httpd/exemplar/functions-abs @@ -1,24 +1,45 @@ 201 imported successfully This is a 4store SPARQL server [VERSION] -Query: SELECT ?s (ABS(?d) AS ?abs_d) WHERE { ?s ?d . } ORDER BY ?s -?s ?abs_d - "0"^^ - "0"^^ - "0"^^ - "1234"^^ - "1234"^^ - "1234"^^ - "1234567890123456789"^^ - "1234567890123456789"^^ - "1234567890123456789"^^ - "1234567890.232393"^^ - "1234567890.232393"^^ - "1234567890.232393"^^ - "inf"^^ - "inf"^^ - "inf"^^ - "1.23457e+19"^^ - "1.23457e+19"^^ - "1.23457e+19"^^ +Query: SELECT ?s ?d (ABS(?d) AS ?abs_d) WHERE { ?s ?d . } ORDER BY ?s +?s ?d ?abs_d + "0"^^ "0"^^ + "-0"^^ "0"^^ + "+0"^^ "0"^^ + "1234"^^ "1234"^^ + "-1234"^^ "1234"^^ + "+1234"^^ "1234"^^ + "1234567890123456789"^^ "1234567890123456789"^^ + "-1234567890123456789"^^ "1234567890123456789"^^ + "+1234567890123456789"^^ "1234567890123456789"^^ + "1234567890.23239300"^^ "1234567890.232393"^^ + "-1234567890.23239300"^^ "1234567890.232393"^^ + "+1234567890.23239300"^^ "1234567890.232393"^^ + "23.0e999999999999999999999999"^^ "inf"^^ + "+23.0e999999999999999999999999"^^ "inf"^^ + "-23.0e999999999999999999999999"^^ "inf"^^ + "12345678901234567890.23239300"^^ "1.23457e+19"^^ + "-12345678901234567890.23239300"^^ "1.23457e+19"^^ + "+12345678901234567890.23239300"^^ "1.23457e+19"^^ + "2.4999"^^ "2.4999"^^ + "2.5"^^ "2.5"^^ + "-2.5"^^ "2.5"^^ + "-2.500001"^^ "2.500001"^^ + "2.500001"^^ "2.500001"^^ + "-2.499999"^^ "2.499999"^^ + "0000.0004999000"^^ "0.0004999"^^ + "2.000050000"^^ "2.00005"^^ + "-2.00050000"^^ "2.0005"^^ + "2.4999"^^ "2.4999"^^ + "2.5"^^ "2.5"^^ + "-2.5"^^ "2.5"^^ + "-2.500001"^^ "2.5"^^ + "2.500001"^^ "2.5"^^ + "-2.499999"^^ "2.5"^^ + "2.4999"^^ "2.4999"^^ + "2.5"^^ "2.5"^^ + "-2.5"^^ "2.5"^^ + "-2.500001"^^ "2.5"^^ + "2.500001"^^ "2.5"^^ + "-2.499999"^^ "2.5"^^ 200 deleted successfully This is a 4store SPARQL server [VERSION] diff --git a/tests/httpd/exemplar/functions-ceil b/tests/httpd/exemplar/functions-ceil new file mode 100644 index 0000000..66fe5b9 --- /dev/null +++ b/tests/httpd/exemplar/functions-ceil @@ -0,0 +1,45 @@ +201 imported successfully +This is a 4store SPARQL server [VERSION] +Query: SELECT ?s ?d (CEIL(?d) AS ?abs_d) WHERE { ?s ?d . } ORDER BY ?s +?s ?d ?abs_d + "0"^^ "0"^^ + "-0"^^ "-0"^^ + "+0"^^ "+0"^^ + "1234"^^ "1234"^^ + "-1234"^^ "-1234"^^ + "+1234"^^ "+1234"^^ + "1234567890123456789"^^ "1234567890123456789"^^ + "-1234567890123456789"^^ "-1234567890123456789"^^ + "+1234567890123456789"^^ "+1234567890123456789"^^ + "1234567890.23239300"^^ "1234567891"^^ + "-1234567890.23239300"^^ "-1234567890"^^ + "+1234567890.23239300"^^ "1234567891"^^ + "23.0e999999999999999999999999"^^ "inf"^^ + "+23.0e999999999999999999999999"^^ "inf"^^ + "-23.0e999999999999999999999999"^^ "-inf"^^ + "12345678901234567890.23239300"^^ "1.23457e+19"^^ + "-12345678901234567890.23239300"^^ "-1.23457e+19"^^ + "+12345678901234567890.23239300"^^ "1.23457e+19"^^ + "2.4999"^^ "3"^^ + "2.5"^^ "3"^^ + "-2.5"^^ "-2"^^ + "-2.500001"^^ "-2"^^ + "2.500001"^^ "3"^^ + "-2.499999"^^ "-2"^^ + "0000.0004999000"^^ "1"^^ + "2.000050000"^^ "3"^^ + "-2.00050000"^^ "-2"^^ + "2.4999"^^ "3"^^ + "2.5"^^ "3"^^ + "-2.5"^^ "-2"^^ + "-2.500001"^^ "-2"^^ + "2.500001"^^ "3"^^ + "-2.499999"^^ "-2"^^ + "2.4999"^^ "3"^^ + "2.5"^^ "3"^^ + "-2.5"^^ "-2"^^ + "-2.500001"^^ "-2"^^ + "2.500001"^^ "3"^^ + "-2.499999"^^ "-2"^^ +200 deleted successfully +This is a 4store SPARQL server [VERSION] diff --git a/tests/httpd/exemplar/functions-floor b/tests/httpd/exemplar/functions-floor new file mode 100644 index 0000000..59eae3e --- /dev/null +++ b/tests/httpd/exemplar/functions-floor @@ -0,0 +1,45 @@ +201 imported successfully +This is a 4store SPARQL server [VERSION] +Query: SELECT ?s ?d (FLOOR(?d) AS ?abs_d) WHERE { ?s ?d . } ORDER BY ?s +?s ?d ?abs_d + "0"^^ "0"^^ + "-0"^^ "-0"^^ + "+0"^^ "+0"^^ + "1234"^^ "1234"^^ + "-1234"^^ "-1234"^^ + "+1234"^^ "+1234"^^ + "1234567890123456789"^^ "1234567890123456789"^^ + "-1234567890123456789"^^ "-1234567890123456789"^^ + "+1234567890123456789"^^ "+1234567890123456789"^^ + "1234567890.23239300"^^ "1234567890"^^ + "-1234567890.23239300"^^ "-1234567891"^^ + "+1234567890.23239300"^^ "1234567890"^^ + "23.0e999999999999999999999999"^^ "inf"^^ + "+23.0e999999999999999999999999"^^ "inf"^^ + "-23.0e999999999999999999999999"^^ "-inf"^^ + "12345678901234567890.23239300"^^ "1.23457e+19"^^ + "-12345678901234567890.23239300"^^ "-1.23457e+19"^^ + "+12345678901234567890.23239300"^^ "1.23457e+19"^^ + "2.4999"^^ "2"^^ + "2.5"^^ "2"^^ + "-2.5"^^ "-3"^^ + "-2.500001"^^ "-3"^^ + "2.500001"^^ "2"^^ + "-2.499999"^^ "-3"^^ + "0000.0004999000"^^ "0"^^ + "2.000050000"^^ "2"^^ + "-2.00050000"^^ "-3"^^ + "2.4999"^^ "2"^^ + "2.5"^^ "2"^^ + "-2.5"^^ "-3"^^ + "-2.500001"^^ "-3"^^ + "2.500001"^^ "2"^^ + "-2.499999"^^ "-3"^^ + "2.4999"^^ "2"^^ + "2.5"^^ "2"^^ + "-2.5"^^ "-3"^^ + "-2.500001"^^ "-3"^^ + "2.500001"^^ "2"^^ + "-2.499999"^^ "-3"^^ +200 deleted successfully +This is a 4store SPARQL server [VERSION] diff --git a/tests/httpd/exemplar/functions-round b/tests/httpd/exemplar/functions-round new file mode 100644 index 0000000..a23c67a --- /dev/null +++ b/tests/httpd/exemplar/functions-round @@ -0,0 +1,45 @@ +201 imported successfully +This is a 4store SPARQL server [VERSION] +Query: SELECT ?s ?d (ROUND(?d) AS ?abs_d) WHERE { ?s ?d . } ORDER BY ?s +?s ?d ?abs_d + "0"^^ "0"^^ + "-0"^^ "-0"^^ + "+0"^^ "+0"^^ + "1234"^^ "1234"^^ + "-1234"^^ "-1234"^^ + "+1234"^^ "+1234"^^ + "1234567890123456789"^^ "1234567890123456789"^^ + "-1234567890123456789"^^ "-1234567890123456789"^^ + "+1234567890123456789"^^ "+1234567890123456789"^^ + "1234567890.23239300"^^ "1234567890"^^ + "-1234567890.23239300"^^ "-1234567890"^^ + "+1234567890.23239300"^^ "1234567890"^^ + "23.0e999999999999999999999999"^^ "inf"^^ + "+23.0e999999999999999999999999"^^ "inf"^^ + "-23.0e999999999999999999999999"^^ "-inf"^^ + "12345678901234567890.23239300"^^ "1.23457e+19"^^ + "-12345678901234567890.23239300"^^ "-1.23457e+19"^^ + "+12345678901234567890.23239300"^^ "1.23457e+19"^^ + "2.4999"^^ "2"^^ + "2.5"^^ "3"^^ + "-2.5"^^ "-2"^^ + "-2.500001"^^ "-3"^^ + "2.500001"^^ "3"^^ + "-2.499999"^^ "-2"^^ + "0000.0004999000"^^ "0"^^ + "2.000050000"^^ "2"^^ + "-2.00050000"^^ "-2"^^ + "2.4999"^^ "2"^^ + "2.5"^^ "3"^^ + "-2.5"^^ "-2"^^ + "-2.500001"^^ "-3"^^ + "2.500001"^^ "3"^^ + "-2.499999"^^ "-2"^^ + "2.4999"^^ "2"^^ + "2.5"^^ "3"^^ + "-2.5"^^ "-2"^^ + "-2.500001"^^ "-3"^^ + "2.500001"^^ "3"^^ + "-2.499999"^^ "-2"^^ +200 deleted successfully +This is a 4store SPARQL server [VERSION] diff --git a/tests/httpd/scripts/functions-abs b/tests/httpd/scripts/functions-abs index b3753cf..4752c5c 100755 --- a/tests/httpd/scripts/functions-abs +++ b/tests/httpd/scripts/functions-abs @@ -4,6 +4,6 @@ source sparql.sh put "$EPR" ../../data/numbers.ttl 'text/turtle' 'http://example.com/numbers.ttl' -sparql "$EPR" 'SELECT ?s (ABS(?d) AS ?abs_d) WHERE { ?s ?d . } ORDER BY ?s' +sparql "$EPR" 'SELECT ?s ?d (ABS(?d) AS ?abs_d) WHERE { ?s ?d . } ORDER BY ?s' delete "$EPR" 'http://example.com/numbers.ttl' diff --git a/tests/httpd/scripts/functions-ceil b/tests/httpd/scripts/functions-ceil new file mode 100755 index 0000000..139ae69 --- /dev/null +++ b/tests/httpd/scripts/functions-ceil @@ -0,0 +1,9 @@ +#!/bin/bash + +source sparql.sh + +put "$EPR" ../../data/numbers.ttl 'text/turtle' 'http://example.com/numbers.ttl' + +sparql "$EPR" 'SELECT ?s ?d (CEIL(?d) AS ?abs_d) WHERE { ?s ?d . } ORDER BY ?s' + +delete "$EPR" 'http://example.com/numbers.ttl' diff --git a/tests/httpd/scripts/functions-floor b/tests/httpd/scripts/functions-floor new file mode 100755 index 0000000..0d1e059 --- /dev/null +++ b/tests/httpd/scripts/functions-floor @@ -0,0 +1,9 @@ +#!/bin/bash + +source sparql.sh + +put "$EPR" ../../data/numbers.ttl 'text/turtle' 'http://example.com/numbers.ttl' + +sparql "$EPR" 'SELECT ?s ?d (FLOOR(?d) AS ?abs_d) WHERE { ?s ?d . } ORDER BY ?s' + +delete "$EPR" 'http://example.com/numbers.ttl' diff --git a/tests/httpd/scripts/functions-round b/tests/httpd/scripts/functions-round new file mode 100755 index 0000000..7207aba --- /dev/null +++ b/tests/httpd/scripts/functions-round @@ -0,0 +1,9 @@ +#!/bin/bash + +source sparql.sh + +put "$EPR" ../../data/numbers.ttl 'text/turtle' 'http://example.com/numbers.ttl' + +sparql "$EPR" 'SELECT ?s ?d (ROUND(?d) AS ?abs_d) WHERE { ?s ?d . } ORDER BY ?s' + +delete "$EPR" 'http://example.com/numbers.ttl'