|
21 | 21 | */ |
22 | 22 | package org.exist.xquery.functions.fn; |
23 | 23 |
|
24 | | -import org.exist.dom.QName; |
25 | 24 | import org.exist.xquery.Cardinality; |
26 | | -import org.exist.xquery.Dependency; |
27 | | -import org.exist.xquery.Function; |
28 | 25 | import org.exist.xquery.FunctionSignature; |
29 | | -import org.exist.xquery.Profiler; |
30 | 26 | import org.exist.xquery.XPathException; |
31 | 27 | 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; |
| 28 | +import org.exist.xquery.value.*; |
39 | 29 |
|
40 | | -public class FunRound extends Function { |
| 30 | +import java.math.RoundingMode; |
41 | 31 |
|
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) { |
| 32 | +import static org.exist.xquery.FunctionDSL.optParam; |
| 33 | +import static org.exist.xquery.functions.fn.FnModule.functionSignature; |
| 34 | + |
| 35 | +/** |
| 36 | + * Implement fn:round() function |
| 37 | + * |
| 38 | + * Shares a base class and evaluator with {@link FunRoundHalfToEven} |
| 39 | + * They differ only in the rounding mode used. |
| 40 | + */ |
| 41 | +public class FunRound extends FunRoundBase { |
| 42 | + |
| 43 | + private static final String FN_NAME = "round"; |
| 44 | + private static final String description = "The function returns the nearest (that is, numerically closest) " + |
| 45 | + "value to $arg that is a multiple of ten to the power of minus $precision. " + |
| 46 | + "If two such values are equally near (for example, if the fractional part in $arg is exactly .5), " + |
| 47 | + "the function returns the one that is closest to positive infinity. " + |
| 48 | + "For the four types xs:float, xs:double, xs:decimal and xs:integer, " + |
| 49 | + "it is guaranteed that if the type of $arg is an instance of type T " + |
| 50 | + "then the result will also be an instance of T. " + |
| 51 | + "The result may also be an instance of a type derived from one of these four by restriction. " + |
| 52 | + "For example, if $arg is an instance of xs:decimal and $precision is less than one, " + |
| 53 | + "then the result may be an instance of xs:integer. " + |
| 54 | + "The single-argument version of this function produces the same result " + |
| 55 | + "as the two-argument version with $precision=0 (that is, it rounds to a whole number). " + |
| 56 | + "When $arg is of type xs:float and xs:double: " + |
| 57 | + "If $arg is NaN, positive or negative zero, or positive or negative infinity, " + |
| 58 | + "then the result is the same as the argument. " + |
| 59 | + "For other values, the argument is cast to xs:decimal " + |
| 60 | + "using an implementation of xs:decimal that imposes no limits on the number of digits " + |
| 61 | + "that can be represented. The function is applied to this xs:decimal value, " + |
| 62 | + "and the resulting xs:decimal is cast back to xs:float or xs:double as appropriate " + |
| 63 | + "to form the function result. If the resulting xs:decimal value is zero, " + |
| 64 | + "then positive or negative zero is returned according to the sign of $arg."; |
| 65 | + private static final FunctionReturnSequenceType returnType = new FunctionReturnSequenceType(Type.NUMBER, Cardinality.ZERO_OR_ONE, "the rounded value"); |
| 66 | + |
| 67 | + public static final FunctionSignature[] FN_ROUND_SIGNATURES = { |
| 68 | + functionSignature(FN_NAME, FunRound.description, FunRound.returnType, |
| 69 | + optParam("arg", Type.NUMBER, "The input number")), |
| 70 | + functionSignature(FN_NAME, FunRound.description, FunRound.returnType, |
| 71 | + optParam("arg", Type.NUMBER, "The input number"), |
| 72 | + optParam("precision", Type.INTEGER, "The input number")) |
| 73 | + }; |
| 74 | + |
| 75 | + public FunRound(final XQueryContext context, final FunctionSignature signature) { |
69 | 76 | super(context, signature); |
70 | 77 | } |
71 | 78 |
|
72 | 79 | public int returnsType() { |
73 | 80 | return Type.NUMBER; |
74 | 81 | } |
75 | 82 |
|
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); |
100 | | - } |
101 | | - result = value.round(); |
102 | | - } |
| 83 | + /** |
| 84 | + * Work out the rounding mode for a particular value using fn:round |
| 85 | + * |
| 86 | + * @param value that has to be rounded |
| 87 | + * @return the rounding mode to use on this value |
| 88 | + */ |
| 89 | + @Override protected final RoundingMode getFunctionRoundingMode(final NumericValue value) { |
103 | 90 |
|
104 | | - if (context.getProfiler().isEnabled()) |
105 | | - {context.getProfiler().end(this, "", result);} |
106 | | - |
107 | | - return result; |
| 91 | + if (value.isNegative()) { |
| 92 | + return RoundingMode.HALF_DOWN; |
| 93 | + } else { |
| 94 | + return RoundingMode.HALF_UP; |
| 95 | + } |
108 | 96 | } |
109 | 97 | } |
0 commit comments