text: // © 2017 and later: Unicode, Inc. and others.
  // License & terms of use: http://www.unicode.org/copyright.html
  
  #include "unicode/utypes.h"
  
  #if !UCONFIG_NO_FORMATTING
  #ifndef __NUMBER_ROUNDINGUTILS_H__
  #define __NUMBER_ROUNDINGUTILS_H__
  
  #include "number_types.h"
  
  U_NAMESPACE_BEGIN
  namespace number {
  namespace impl {
  namespace roundingutils {
  
  enum Section {
      SECTION_LOWER_EDGE = -1,
      SECTION_UPPER_EDGE = -2,
      SECTION_LOWER = 1,
      SECTION_MIDPOINT = 2,
      SECTION_UPPER = 3
  };
  
  /**
   * Converts a rounding mode and metadata about the quantity being rounded to a boolean determining
   * whether the value should be rounded toward infinity or toward zero.
   *
   * <p>The parameters are of type int because benchmarks on an x86-64 processor against OpenJDK
   * showed that ints were demonstrably faster than enums in switch statements.
   *
   * @param isEven Whether the digit immediately before the rounding magnitude is even.
   * @param isNegative Whether the quantity is negative.
   * @param section Whether the part of the quantity to the right of the rounding magnitude is
   *     exactly halfway between two digits, whether it is in the lower part (closer to zero), or
   *     whether it is in the upper part (closer to infinity). See {@link #SECTION_LOWER}, {@link
   *     #SECTION_MIDPOINT}, and {@link #SECTION_UPPER}.
   * @param roundingMode The integer version of the {@link RoundingMode}, which you can get via
   *     {@link RoundingMode#ordinal}.
   * @param status Error code, set to U_FORMAT_INEXACT_ERROR if the rounding mode is kRoundUnnecessary.
   * @return true if the number should be rounded toward zero; false if it should be rounded toward
   *     infinity.
   */
  inline bool
  getRoundingDirection(bool isEven, bool isNegative, Section section, RoundingMode roundingMode,
                       UErrorCode &status) {
      switch (roundingMode) {
          case RoundingMode::UNUM_ROUND_UP:
              // round away from zero
              return false;
  
          case RoundingMode::UNUM_ROUND_DOWN:
              // round toward zero
              return true;
  
          case RoundingMode::UNUM_ROUND_CEILING:
              // round toward positive infinity
              return isNegative;
  
          case RoundingMode::UNUM_ROUND_FLOOR:
              // round toward negative infinity
              return !isNegative;
  
          case RoundingMode::UNUM_ROUND_HALFUP:
              switch (section) {
                  case SECTION_MIDPOINT:
                      return false;
                  case SECTION_LOWER:
                      return true;
                  case SECTION_UPPER:
                      return false;
                  default:
                      break;
              }
              break;
  
          case RoundingMode::UNUM_ROUND_HALFDOWN:
              switch (section) {
                  case SECTION_MIDPOINT:
                      return true;
                  case SECTION_LOWER:
                      return true;
                  case SECTION_UPPER:
                      return false;
                  default:
                      break;
              }
              break;
  
          case RoundingMode::UNUM_ROUND_HALFEVEN:
              switch (section) {
                  case SECTION_MIDPOINT:
                      return isEven;
                  case SECTION_LOWER:
                      return true;
                  case SECTION_UPPER:
                      return false;
                  default:
                      break;
              }
              break;
  
          default:
              break;
      }
  
      status = U_FORMAT_INEXACT_ERROR;
      return false;
  }
  
  /**
   * Gets whether the given rounding mode's rounding boundary is at the midpoint. The rounding
   * boundary is the point at which a number switches from being rounded down to being rounded up.
   * For example, with rounding mode HALF_EVEN, HALF_UP, or HALF_DOWN, the rounding boundary is at
   * the midpoint, and this function would return true. However, for UP, DOWN, CEILING, and FLOOR,
   * the rounding boundary is at the "edge", and this function would return false.
   *
   * @param roundingMode The integer version of the {@link RoundingMode}.
   * @return true if rounding mode is HALF_EVEN, HALF_UP, or HALF_DOWN; false otherwise.
   */
  inline bool roundsAtMidpoint(int roundingMode) {
      switch (roundingMode) {
          case RoundingMode::UNUM_ROUND_UP:
          case RoundingMode::UNUM_ROUND_DOWN:
          case RoundingMode::UNUM_ROUND_CEILING:
          case RoundingMode::UNUM_ROUND_FLOOR:
              return false;
  
          default:
              return true;
      }
  }
  
  /**
   * Computes the number of fraction digits in a double. Used for computing maxFrac for an increment.
   * Calls into the DoubleToStringConverter library to do so.
   */
  digits_t doubleFractionLength(double input);
  
  } // namespace roundingutils
  
  
  /**
   * Encapsulates a Precision and a RoundingMode and performs rounding on a DecimalQuantity.
   *
   * This class does not exist in Java: instead, the base Precision class is used.
   */
  class RoundingImpl {
    public:
      RoundingImpl() = default;  // default constructor: leaves object in undefined state
  
      RoundingImpl(const Precision& precision, UNumberFormatRoundingMode roundingMode,
                   const CurrencyUnit& currency, UErrorCode& status);
  
      static RoundingImpl passThrough();
  
      /** Required for ScientificFormatter */
      bool isSignificantDigits() const;
  
      /**
       * Rounding endpoint used by Engineering and Compact notation. Chooses the most appropriate multiplier (magnitude
       * adjustment), applies the adjustment, rounds, and returns the chosen multiplier.
       *
       * <p>
       * In most cases, this is simple. However, when rounding the number causes it to cross a multiplier boundary, we
       * need to re-do the rounding. For example, to display 999,999 in Engineering notation with 2 sigfigs, first you
       * guess the multiplier to be -3. However, then you end up getting 1000E3, which is not the correct output. You then
       * change your multiplier to be -6, and you get 1.0E6, which is correct.
       *
       * @param input The quantity to process.
       * @param producer Function to call to return a multiplier based on a magnitude.
       * @return The number of orders of magnitude the input was adjusted by this method.
       */
      int32_t
      chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer,
                               UErrorCode &status);
  
      void apply(impl::DecimalQuantity &value, UErrorCode &status) const;
  
      /** Version of {@link #apply} that obeys minInt constraints. Used for scientific notation compatibility mode. */
      void apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode status);
  
    private:
      Precision fPrecision;
      UNumberFormatRoundingMode fRoundingMode;
      bool fPassThrough;
  };
  
  
  } // namespace impl
  } // namespace number
  U_NAMESPACE_END
  
  #endif //__NUMBER_ROUNDINGUTILS_H__
  
  #endif /* #if !UCONFIG_NO_FORMATTING */
  