You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Sumproduct.java 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.ss.formula.functions;
  16. import java.util.Arrays;
  17. import org.apache.poi.ss.formula.TwoDEval;
  18. import org.apache.poi.ss.formula.eval.AreaEval;
  19. import org.apache.poi.ss.formula.eval.BlankEval;
  20. import org.apache.poi.ss.formula.eval.ErrorEval;
  21. import org.apache.poi.ss.formula.eval.EvaluationException;
  22. import org.apache.poi.ss.formula.eval.NumberEval;
  23. import org.apache.poi.ss.formula.eval.NumericValueEval;
  24. import org.apache.poi.ss.formula.eval.RefEval;
  25. import org.apache.poi.ss.formula.eval.StringEval;
  26. import org.apache.poi.ss.formula.eval.ValueEval;
  27. /**
  28. * Implementation for the Excel function SUMPRODUCT<p>
  29. *
  30. * Syntax : <br>
  31. * SUMPRODUCT ( array1[, array2[, array3[, ...]]])
  32. * <table>
  33. * <caption>Parameter descriptions</caption>
  34. * <tr><th>array1, ... arrayN&nbsp;&nbsp;</th><td>typically area references,
  35. * possibly cell references or scalar values</td></tr>
  36. * </table><br>
  37. *
  38. * Let A<b>n</b><sub>(<b>i</b>,<b>j</b>)</sub> represent the element in the <b>i</b>th row <b>j</b>th column
  39. * of the <b>n</b>th array<br>
  40. * Assuming each array has the same dimensions (W, H), the result is defined as:<br>
  41. * SUMPRODUCT = &Sigma;<sub><b>i</b>: 1..H</sub> &nbsp;
  42. * (&nbsp; &Sigma;<sub><b>j</b>: 1..W</sub> &nbsp;
  43. * (&nbsp; &Pi;<sub><b>n</b>: 1..N</sub>
  44. * A<b>n</b><sub>(<b>i</b>,<b>j</b>)</sub>&nbsp;
  45. * )&nbsp;
  46. * )
  47. */
  48. public final class Sumproduct implements Function {
  49. @Override
  50. public ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) {
  51. int maxN = args.length;
  52. if(maxN < 1) {
  53. return ErrorEval.VALUE_INVALID;
  54. }
  55. ValueEval firstArg = args[0];
  56. try {
  57. if(firstArg instanceof NumericValueEval) {
  58. return evaluateSingleProduct(args);
  59. }
  60. if(firstArg instanceof RefEval) {
  61. return evaluateSingleProduct(args);
  62. }
  63. if (firstArg instanceof TwoDEval) {
  64. TwoDEval ae = (TwoDEval) firstArg;
  65. if(ae.isRow() && ae.isColumn()) {
  66. return evaluateSingleProduct(args);
  67. }
  68. return evaluateAreaSumProduct(args);
  69. }
  70. } catch (EvaluationException e) {
  71. return e.getErrorEval();
  72. }
  73. throw new RuntimeException("Invalid arg type for SUMPRODUCT: ("
  74. + firstArg.getClass().getName() + ")");
  75. }
  76. private static ValueEval evaluateSingleProduct(ValueEval[] evalArgs) throws EvaluationException {
  77. int maxN = evalArgs.length;
  78. double term = 1D;
  79. for (ValueEval evalArg : evalArgs) {
  80. double val = getScalarValue(evalArg);
  81. term *= val;
  82. }
  83. return new NumberEval(term);
  84. }
  85. private static double getScalarValue(ValueEval arg) throws EvaluationException {
  86. ValueEval eval;
  87. if (arg instanceof RefEval) {
  88. RefEval re = (RefEval) arg;
  89. if (re.getNumberOfSheets() > 1) {
  90. throw new EvaluationException(ErrorEval.VALUE_INVALID);
  91. }
  92. eval = re.getInnerValueEval(re.getFirstSheetIndex());
  93. } else {
  94. eval = arg;
  95. }
  96. if (eval == null) {
  97. throw new RuntimeException("parameter may not be null");
  98. }
  99. if (eval instanceof AreaEval) {
  100. AreaEval ae = (AreaEval) eval;
  101. // an area ref can work as a scalar value if it is 1x1
  102. if(!ae.isColumn() || !ae.isRow()) {
  103. throw new EvaluationException(ErrorEval.VALUE_INVALID);
  104. }
  105. eval = ae.getRelativeValue(0, 0);
  106. }
  107. return getProductTerm(eval, true);
  108. }
  109. private static ValueEval evaluateAreaSumProduct(ValueEval[] evalArgs) throws EvaluationException {
  110. int maxN = evalArgs.length;
  111. TwoDEval[] args;
  112. try {
  113. args = Arrays.copyOf(evalArgs, maxN, TwoDEval[].class);
  114. } catch (ArrayStoreException e) {
  115. // one of the other args was not an AreaRef
  116. return ErrorEval.VALUE_INVALID;
  117. }
  118. TwoDEval firstArg = args[0];
  119. int height = firstArg.getHeight();
  120. int width = firstArg.getWidth(); // TODO - junit
  121. // first check dimensions
  122. if (!areasAllSameSize(args, height, width)) {
  123. // normally this results in #VALUE!,
  124. // but errors in individual cells take precedence
  125. for (int i = 1; i < args.length; i++) {
  126. throwFirstError(args[i]);
  127. }
  128. return ErrorEval.VALUE_INVALID;
  129. }
  130. double acc = 0;
  131. for (int rrIx=0; rrIx<height; rrIx++) {
  132. for (int rcIx=0; rcIx<width; rcIx++) {
  133. double term = 1D;
  134. for(int n=0; n<maxN; n++) {
  135. double val = getProductTerm(args[n].getValue(rrIx, rcIx), false);
  136. term *= val;
  137. }
  138. acc += term;
  139. }
  140. }
  141. return new NumberEval(acc);
  142. }
  143. private static void throwFirstError(TwoDEval areaEval) throws EvaluationException {
  144. int height = areaEval.getHeight();
  145. int width = areaEval.getWidth();
  146. for (int rrIx=0; rrIx<height; rrIx++) {
  147. for (int rcIx=0; rcIx<width; rcIx++) {
  148. ValueEval ve = areaEval.getValue(rrIx, rcIx);
  149. if (ve instanceof ErrorEval) {
  150. throw new EvaluationException((ErrorEval) ve);
  151. }
  152. }
  153. }
  154. }
  155. private static boolean areasAllSameSize(TwoDEval[] args, int height, int width) {
  156. for (TwoDEval areaEval : args) {
  157. // check that height and width match
  158. if (areaEval.getHeight() != height) {
  159. return false;
  160. }
  161. if (areaEval.getWidth() != width) {
  162. return false;
  163. }
  164. }
  165. return true;
  166. }
  167. /**
  168. * Determines a {@code double} value for the specified {@code ValueEval}.
  169. * @param isScalarProduct {@code false} for SUMPRODUCTs over area refs.
  170. * @throws EvaluationException if {@code ve} represents an error value.
  171. * <p>
  172. * Note - string values and empty cells are interpreted differently depending on
  173. * {@code isScalarProduct}. For scalar products, if any term is blank or a string, the
  174. * error (#VALUE!) is raised. For area (sum)products, if any term is blank or a string, the
  175. * result is zero.
  176. */
  177. private static double getProductTerm(ValueEval ve, boolean isScalarProduct) throws EvaluationException {
  178. if(ve instanceof BlankEval || ve == null) {
  179. // TODO - shouldn't BlankEval.INSTANCE be used always instead of null?
  180. // null seems to occur when the blank cell is part of an area ref (but not reliably)
  181. if(isScalarProduct) {
  182. throw new EvaluationException(ErrorEval.VALUE_INVALID);
  183. }
  184. return 0;
  185. }
  186. if(ve instanceof ErrorEval) {
  187. throw new EvaluationException((ErrorEval)ve);
  188. }
  189. if(ve instanceof StringEval) {
  190. if(isScalarProduct) {
  191. throw new EvaluationException(ErrorEval.VALUE_INVALID);
  192. }
  193. // Note for area SUMPRODUCTs, string values are interpreted as zero
  194. // even if they would parse as valid numeric values
  195. return 0;
  196. }
  197. if(ve instanceof NumericValueEval) {
  198. NumericValueEval nve = (NumericValueEval) ve;
  199. return nve.getNumberValue();
  200. }
  201. throw new RuntimeException("Unexpected value eval class ("
  202. + ve.getClass().getName() + ")");
  203. }
  204. }