Skip to content

Commit e9b6723

Browse files
committed
FnRound / FnRoundHalfToEven
fn:round() was half implemented, fn:round-half-to-even() implemented but untested. Write some tests, finish fn:round() and make both functions share all the common parts they can.
1 parent 12ae764 commit e9b6723

File tree

10 files changed

+359
-152
lines changed

10 files changed

+359
-152
lines changed

exist-core/src/main/java/org/exist/xquery/functions/fn/FnModule.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,10 @@ public class FnModule extends AbstractInternalModule {
179179
new FunctionDef(FunResolveURI.signatures[1], FunResolveURI.class),
180180
new FunctionDef(FunRoot.signatures[0], FunRoot.class),
181181
new FunctionDef(FunRoot.signatures[1], FunRoot.class),
182-
new FunctionDef(FunRound.signature, FunRound.class),
183-
new FunctionDef(FunRoundHalfToEven.signatures[0], FunRoundHalfToEven.class),
184-
new FunctionDef(FunRoundHalfToEven.signatures[1], FunRoundHalfToEven.class),
182+
new FunctionDef(FunRound.FN_ROUND_SIGNATURES[0], FunRound.class),
183+
new FunctionDef(FunRound.FN_ROUND_SIGNATURES[1], FunRound.class),
184+
new FunctionDef(FunRoundHalfToEven.FN_ROUND_HALF_TO_EVEN_SIGNATURES[0], FunRoundHalfToEven.class),
185+
new FunctionDef(FunRoundHalfToEven.FN_ROUND_HALF_TO_EVEN_SIGNATURES[1], FunRoundHalfToEven.class),
185186
new FunctionDef(FunSerialize.signatures[0], FunSerialize.class),
186187
new FunctionDef(FunSerialize.signatures[1], FunSerialize.class),
187188
new FunctionDef(FunStartsWith.signatures[0], FunStartsWith.class),

exist-core/src/main/java/org/exist/xquery/functions/fn/FunRound.java

Lines changed: 89 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -22,88 +22,105 @@
2222
package org.exist.xquery.functions.fn;
2323

2424
import org.exist.dom.QName;
25-
import org.exist.xquery.Cardinality;
26-
import org.exist.xquery.Dependency;
27-
import org.exist.xquery.Function;
28-
import org.exist.xquery.FunctionSignature;
29-
import org.exist.xquery.Profiler;
30-
import org.exist.xquery.XPathException;
31-
import org.exist.xquery.XQueryContext;
32-
import org.exist.xquery.value.FunctionParameterSequenceType;
33-
import org.exist.xquery.value.FunctionReturnSequenceType;
34-
import org.exist.xquery.value.Item;
35-
import org.exist.xquery.value.NumericValue;
36-
import org.exist.xquery.value.Sequence;
37-
import org.exist.xquery.value.SequenceType;
38-
import org.exist.xquery.value.Type;
25+
import org.exist.xquery.*;
26+
import org.exist.xquery.value.*;
3927

40-
public class FunRound extends Function {
28+
import java.math.RoundingMode;
29+
import java.util.Objects;
4130

42-
public final static FunctionSignature signature =
43-
new FunctionSignature(
44-
new QName("round", Function.BUILTIN_FUNCTION_NS),
45-
"Returns the number with no fractional part that is closest " +
46-
"to the argument $arg. If there are two such numbers, then the one " +
47-
"that is closest to positive infinity is returned. If type of " +
48-
"$arg is one of the four numeric types xs:float, xs:double, " +
49-
"xs:decimal or xs:integer the type of the result is the same " +
50-
"as the type of $arg. If the type of $arg is a type derived " +
51-
"from one of the numeric types, the result is an instance of " +
52-
"the base numeric type.\n\n" +
53-
"For xs:float and xs:double arguments, if the argument is " +
54-
"positive infinity, then positive infinity is returned. " +
55-
"If the argument is negative infinity, then negative infinity " +
56-
"is returned. If the argument is positive zero, then positive " +
57-
"zero is returned. If the argument is negative zero, then " +
58-
"negative zero is returned. If the argument is less than zero, " +
59-
"but greater than or equal to -0.5, then negative zero is returned. " +
60-
"In the cases where positive zero or negative zero is returned, " +
61-
"negative zero or positive zero may be returned as " +
62-
"[XML Schema Part 2: Datatypes Second Edition] does not " +
63-
"distinguish between the values positive zero and negative zero.",
64-
new SequenceType[] { new FunctionParameterSequenceType("arg", Type.NUMBER, Cardinality.ZERO_OR_ONE, "The input number") },
65-
new FunctionReturnSequenceType(Type.NUMBER, Cardinality.ZERO_OR_ONE, "the rounded value")
66-
);
67-
68-
public FunRound(XQueryContext context) {
31+
/**
32+
* Implement fn:round() function
33+
*
34+
* Shares a base class and evaluator with {@link FunRoundHalfToEven}
35+
* They differ only in the rounding mode used.
36+
*/
37+
public class FunRound extends FunRoundBase {
38+
39+
private static final QName qName = new QName("round", Function.BUILTIN_FUNCTION_NS);
40+
private static final String description = "The function returns the nearest (that is, numerically closest) " +
41+
"value to $arg that is a multiple of ten to the power of minus $precision. " +
42+
"If two such values are equally near (for example, if the fractional part in $arg is exactly .5), " +
43+
"the function returns the one that is closest to positive infinity. " +
44+
"For the four types xs:float, xs:double, xs:decimal and xs:integer, " +
45+
"it is guaranteed that if the type of $arg is an instance of type T " +
46+
"then the result will also be an instance of T. " +
47+
"The result may also be an instance of a type derived from one of these four by restriction. " +
48+
"For example, if $arg is an instance of xs:decimal and $precision is less than one, " +
49+
"then the result may be an instance of xs:integer. " +
50+
"The single-argument version of this function produces the same result " +
51+
"as the two-argument version with $precision=0 (that is, it rounds to a whole number). " +
52+
"When $arg is of type xs:float and xs:double: " +
53+
"If $arg is NaN, positive or negative zero, or positive or negative infinity, " +
54+
"then the result is the same as the argument. " +
55+
"For other values, the argument is cast to xs:decimal " +
56+
"using an implementation of xs:decimal that imposes no limits on the number of digits " +
57+
"that can be represented. The function is applied to this xs:decimal value, " +
58+
"and the resulting xs:decimal is cast back to xs:float or xs:double as appropriate " +
59+
"to form the function result. If the resulting xs:decimal value is zero, " +
60+
"then positive or negative zero is returned according to the sign of $arg.";
61+
private static final FunctionReturnSequenceType returnType = new FunctionReturnSequenceType(Type.NUMBER, Cardinality.ZERO_OR_ONE, "the rounded value");
62+
63+
public static final FunctionSignature[] FN_ROUND_SIGNATURES = {
64+
FunctionDSL.functionSignature(FunRound.qName, FunRound.description, FunRound.returnType,
65+
new FunctionParameterSequenceType("arg", Type.NUMBER, Cardinality.ZERO_OR_ONE, "The input number")),
66+
FunctionDSL.functionSignature(FunRound.qName, FunRound.description, FunRound.returnType,
67+
new FunctionParameterSequenceType("arg", Type.NUMBER, Cardinality.ZERO_OR_ONE, "The input number"),
68+
new FunctionParameterSequenceType("precision", Type.INTEGER, Cardinality.ZERO_OR_ONE, "The input number"))
69+
};
70+
71+
public FunRound(final XQueryContext context, final FunctionSignature signature) {
6972
super(context, signature);
7073
}
7174

7275
public int returnsType() {
7376
return Type.NUMBER;
7477
}
7578

76-
public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException {
77-
if (context.getProfiler().isEnabled()) {
78-
context.getProfiler().start(this);
79-
context.getProfiler().message(this, Profiler.DEPENDENCIES, "DEPENDENCIES", Dependency.getDependenciesName(this.getDependencies()));
80-
if (contextSequence != null)
81-
{context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT SEQUENCE", contextSequence);}
82-
if (contextItem != null)
83-
{context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT ITEM", contextItem.toSequence());}
84-
}
85-
86-
if (contextItem != null)
87-
{contextSequence = contextItem.toSequence();}
88-
89-
Sequence result;
90-
final Sequence seq = getArgument(0).eval(contextSequence, contextItem);
91-
if (seq.isEmpty())
92-
{result = Sequence.EMPTY_SEQUENCE;}
93-
else {
94-
final Item item = seq.itemAt(0);
95-
NumericValue value;
96-
if (item instanceof NumericValue) {
97-
value = (NumericValue) item;
98-
} else {
99-
value = (NumericValue) item.convertTo(Type.NUMBER);
79+
/**
80+
* Work out the rounding mode for a particular value using fn:round
81+
*
82+
* @param value that has to be rounded
83+
* @return the rounding mode to use on this value
84+
*/
85+
@Override protected final RoundingMode getFunctionRoundingMode(final NumericValue value) {
86+
87+
if (value.isNegative()) {
88+
return RoundingMode.HALF_DOWN;
89+
} else {
90+
return RoundingMode.HALF_UP;
91+
}
92+
}
93+
94+
@Override
95+
public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {
96+
97+
if (args[0].isEmpty()) {
98+
return Sequence.EMPTY_SEQUENCE;
99+
}
100+
101+
final Item item = args[0].itemAt(0);
102+
final NumericValue value;
103+
if (item instanceof NumericValue) {
104+
value = (NumericValue) item;
105+
} else {
106+
value = (NumericValue) item.convertTo(Type.NUMBER);
107+
}
108+
109+
final RoundingMode roundingMode;
110+
if (value.isNegative()) {
111+
roundingMode = RoundingMode.HALF_DOWN;
112+
} else {
113+
roundingMode = RoundingMode.HALF_UP;
114+
}
115+
116+
if (args.length > 1) {
117+
final Item precisionItem = args[1].itemAt(0);
118+
if (precisionItem instanceof IntegerValue) {
119+
final IntegerValue precision = (IntegerValue) precisionItem;
120+
return value.round(precision, roundingMode);
100121
}
101-
result = value.round();
102-
}
122+
}
103123

104-
if (context.getProfiler().isEnabled())
105-
{context.getProfiler().end(this, "", result);}
106-
107-
return result;
124+
return value.round(IntegerValue.ZERO, roundingMode);
108125
}
109126
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* eXist-db Open Source Native XML Database
3+
* Copyright (C) 2001 The eXist-db Authors
4+
*
5+
6+
* http://www.exist-db.org
7+
*
8+
* This library is free software; you can redistribute it and/or
9+
* modify it under the terms of the GNU Lesser General Public
10+
* License as published by the Free Software Foundation; either
11+
* version 2.1 of the License, or (at your option) any later version.
12+
*
13+
* This library is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16+
* Lesser General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Lesser General Public
19+
* License along with this library; if not, write to the Free Software
20+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21+
*/
22+
package org.exist.xquery.functions.fn;
23+
24+
import org.exist.xquery.BasicFunction;
25+
import org.exist.xquery.FunctionSignature;
26+
import org.exist.xquery.XPathException;
27+
import org.exist.xquery.XQueryContext;
28+
import org.exist.xquery.value.*;
29+
30+
import java.math.RoundingMode;
31+
32+
/**
33+
* Base class for rounding mode functions
34+
*
35+
* Implements the eval function which knows how to round,
36+
* but defers to the subclass for the {@link RoundingMode} to use.
37+
*/
38+
abstract class FunRoundBase extends BasicFunction {
39+
40+
public FunRoundBase(final XQueryContext context, final FunctionSignature signature) {
41+
super(context, signature);
42+
}
43+
44+
public int returnsType() {
45+
return Type.NUMBER;
46+
}
47+
48+
abstract protected RoundingMode getFunctionRoundingMode(NumericValue value);
49+
50+
@Override
51+
public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {
52+
53+
if (args[0].isEmpty()) {
54+
return Sequence.EMPTY_SEQUENCE;
55+
}
56+
57+
final Item item = args[0].itemAt(0);
58+
final NumericValue value;
59+
if (item instanceof NumericValue) {
60+
value = (NumericValue) item;
61+
} else {
62+
value = (NumericValue) item.convertTo(Type.NUMBER);
63+
}
64+
65+
final RoundingMode roundingMode = getFunctionRoundingMode(value);
66+
67+
if (args.length > 1) {
68+
final Item precisionItem = args[1].itemAt(0);
69+
if (precisionItem instanceof IntegerValue) {
70+
final IntegerValue precision = (IntegerValue) precisionItem;
71+
return value.round(precision, roundingMode);
72+
}
73+
}
74+
75+
return value.round(IntegerValue.ZERO, roundingMode);
76+
}
77+
78+
}

exist-core/src/main/java/org/exist/xquery/functions/fn/FunRoundHalfToEven.java

Lines changed: 28 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,7 @@
2222
package org.exist.xquery.functions.fn;
2323

2424
import org.exist.dom.QName;
25-
import org.exist.xquery.Cardinality;
26-
import org.exist.xquery.Dependency;
27-
import org.exist.xquery.Function;
28-
import org.exist.xquery.FunctionSignature;
29-
import org.exist.xquery.Profiler;
30-
import org.exist.xquery.XPathException;
31-
import org.exist.xquery.XQueryContext;
25+
import org.exist.xquery.*;
3226
import org.exist.xquery.value.FunctionParameterSequenceType;
3327
import org.exist.xquery.value.FunctionReturnSequenceType;
3428
import org.exist.xquery.value.IntegerValue;
@@ -38,14 +32,21 @@
3832
import org.exist.xquery.value.SequenceType;
3933
import org.exist.xquery.value.Type;
4034

35+
import java.math.RoundingMode;
36+
4137
/**
42-
* Implements the fn:roud-half-to-even() function.
38+
* Implements the fn:round-half-to-even() function.
39+
*
40+
* Shares a base class and evaluator with {@link FunRound}
41+
* They differ only in the rounding mode used.
4342
*
4443
* @author wolf
4544
*
4645
*/
47-
public class FunRoundHalfToEven extends Function {
48-
46+
public class FunRoundHalfToEven extends FunRoundBase {
47+
48+
private static final QName qName = new QName("round-half-to-even", Function.BUILTIN_FUNCTION_NS);
49+
4950
protected static final String FUNCTION_DESCRIPTION_1_PARAM =
5051
"The value returned is the nearest (that is, numerically closest) " +
5152
"value to $arg that is a multiple of ten to the power of minus 0. ";
@@ -81,66 +82,26 @@ public class FunRoundHalfToEven extends Function {
8182
protected static final FunctionParameterSequenceType PRECISION_PARAM = new FunctionParameterSequenceType("precision", Type.INTEGER, Cardinality.EXACTLY_ONE, "The precision factor");
8283
protected static final FunctionReturnSequenceType RETURN_TYPE = new FunctionReturnSequenceType(Type.NUMBER, Cardinality.ZERO_OR_ONE, "the rounded value");
8384

84-
public final static FunctionSignature[] signatures = {
85-
new FunctionSignature(
86-
new QName("round-half-to-even", Function.BUILTIN_FUNCTION_NS),
87-
FUNCTION_DESCRIPTION_1_PARAM + FUNCTION_DESCRIPTION_COMMON,
88-
new SequenceType[] { ARG_PARAM },
89-
RETURN_TYPE),
90-
91-
new FunctionSignature(new QName("round-half-to-even",
92-
Function.BUILTIN_FUNCTION_NS),
93-
FUNCTION_DESCRIPTION_2_PARAM + FUNCTION_DESCRIPTION_COMMON,
94-
new SequenceType[] { ARG_PARAM, PRECISION_PARAM },
95-
RETURN_TYPE )
96-
};
85+
public static final FunctionSignature[] FN_ROUND_HALF_TO_EVEN_SIGNATURES = {
86+
FunctionDSL.functionSignature(FunRoundHalfToEven.qName, FunRoundHalfToEven.FUNCTION_DESCRIPTION_1_PARAM + FunRoundHalfToEven.FUNCTION_DESCRIPTION_COMMON, FunRoundHalfToEven.RETURN_TYPE,
87+
new FunctionParameterSequenceType("arg", Type.NUMBER, Cardinality.ZERO_OR_ONE, "The input number")),
88+
FunctionDSL.functionSignature(FunRoundHalfToEven.qName, FunRoundHalfToEven.FUNCTION_DESCRIPTION_2_PARAM + FunRoundHalfToEven.FUNCTION_DESCRIPTION_COMMON, RETURN_TYPE,
89+
new FunctionParameterSequenceType("arg", Type.NUMBER, Cardinality.ZERO_OR_ONE, "The input number"),
90+
new FunctionParameterSequenceType("precision", Type.INTEGER, Cardinality.ZERO_OR_ONE, "The input number")) };
9791

98-
public FunRoundHalfToEven(XQueryContext context,
99-
FunctionSignature signatures) {
100-
super(context, signatures);
92+
public FunRoundHalfToEven(final XQueryContext context,
93+
final FunctionSignature signature) {
94+
super(context, signature);
10195
}
10296

103-
public int returnsType() {
104-
return Type.DOUBLE;
105-
}
97+
/**
98+
* Work out the rounding mode for a particular value using fn:round-half-to-even
99+
*
100+
* @param value that has to be rounded
101+
* @return the rounding mode to use on this value
102+
*/
103+
@Override protected final RoundingMode getFunctionRoundingMode(final NumericValue value) {
106104

107-
public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException {
108-
if (context.getProfiler().isEnabled()) {
109-
context.getProfiler().start(this);
110-
context.getProfiler().message(this, Profiler.DEPENDENCIES, "DEPENDENCIES", Dependency.getDependenciesName(this.getDependencies()));
111-
if (contextSequence != null)
112-
{context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT SEQUENCE", contextSequence);}
113-
if (contextItem != null)
114-
{context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT ITEM", contextItem.toSequence());}
115-
}
116-
117-
Sequence result;
118-
IntegerValue precision = null;
119-
final Sequence seq = getArgument(0).eval(contextSequence, contextItem);
120-
if (seq.isEmpty())
121-
{result = Sequence.EMPTY_SEQUENCE;}
122-
else {
123-
if (contextItem != null)
124-
{contextSequence = contextItem.toSequence();}
125-
126-
if (getSignature().getArgumentCount() > 1) {
127-
precision = (IntegerValue) getArgument(1).eval(contextSequence, contextItem).itemAt(0).convertTo(Type.INTEGER);
128-
}
129-
130-
final Item item = seq.itemAt(0);
131-
NumericValue value;
132-
if (item instanceof NumericValue) {
133-
value = (NumericValue) item;
134-
} else {
135-
value = (NumericValue) item.convertTo(Type.NUMBER);
136-
}
137-
138-
result = value.round(precision);
139-
}
140-
141-
if (context.getProfiler().isEnabled())
142-
{context.getProfiler().end(this, "", result);}
143-
144-
return result;
105+
return RoundingMode.HALF_EVEN;
145106
}
146107
}

0 commit comments

Comments
 (0)