content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *    http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.arrow.gandiva.evaluator;
  
  import org.apache.arrow.vector.types.pojo.ArrowType.Decimal;
  
  /** Utility methods for working with {@link Decimal} values. */
  public class DecimalTypeUtil {
    private DecimalTypeUtil() {}
  
    /** Enum for supported mathematical operations. */
    public enum OperationType {
      ADD,
      SUBTRACT,
      MULTIPLY,
      DIVIDE,
      MOD
    }
  
    private static final int MIN_ADJUSTED_SCALE = 6;
    /// The maximum precision representable by a 16-byte decimal
    private static final int MAX_PRECISION = 38;
  
    /** Determines the scale and precision of applying the given operation to the operands. */
    public static Decimal getResultTypeForOperation(
        OperationType operation, Decimal operand1, Decimal operand2) {
      int s1 = operand1.getScale();
      int s2 = operand2.getScale();
      int p1 = operand1.getPrecision();
      int p2 = operand2.getPrecision();
      int resultScale = 0;
      int resultPrecision = 0;
      switch (operation) {
        case ADD:
        case SUBTRACT:
          resultScale = Math.max(operand1.getScale(), operand2.getScale());
          resultPrecision =
              resultScale
                  + Math.max(
                      operand1.getPrecision() - operand1.getScale(),
                      operand2.getPrecision() - operand2.getScale())
                  + 1;
          break;
        case MULTIPLY:
          resultScale = s1 + s2;
          resultPrecision = p1 + p2 + 1;
          break;
        case DIVIDE:
          resultScale =
              Math.max(MIN_ADJUSTED_SCALE, operand1.getScale() + operand2.getPrecision() + 1);
          resultPrecision =
              operand1.getPrecision() - operand1.getScale() + operand2.getScale() + resultScale;
          break;
        case MOD:
          resultScale = Math.max(operand1.getScale(), operand2.getScale());
          resultPrecision =
              Math.min(
                      operand1.getPrecision() - operand1.getScale(),
                      operand2.getPrecision() - operand2.getScale())
                  + resultScale;
          break;
        default:
          throw new RuntimeException("Needs support");
      }
      return adjustScaleIfNeeded(resultPrecision, resultScale);
    }
  
    private static Decimal adjustScaleIfNeeded(int precision, int scale) {
      if (precision > MAX_PRECISION) {
        int minScale = Math.min(scale, MIN_ADJUSTED_SCALE);
        int delta = precision - MAX_PRECISION;
        precision = MAX_PRECISION;
        scale = Math.max(scale - delta, minScale);
      }
      return new Decimal(precision, scale, 128);
    }
  }
  