int decimal_round()

in mysql_strings/decimal.cc [1650:1832]


int decimal_round(const decimal_t *from, decimal_t *to, int scale,
                  decimal_round_mode mode) {
  int frac0 = scale > 0 ? ROUND_UP(scale) : (scale + 1) / DIG_PER_DEC1,
      frac1 = ROUND_UP(from->frac), round_digit = 0,
      intg0 = ROUND_UP(from->intg), error = E_DEC_OK, len = to->len;

  dec1 *buf0 = from->buf, *buf1 = to->buf, x, y, carry = 0;
  int first_dig;

  sanity(to);

  switch (mode) {
    case HALF_UP:
    case HALF_EVEN:
      round_digit = 5;
      break;
    case CEILING:
      round_digit = from->sign ? 10 : 0;
      break;
    case FLOOR:
      round_digit = from->sign ? 0 : 10;
      break;
    case TRUNCATE:
      round_digit = 10;
      break;
    default:
      assert(0);
  }

  /*
    For my_decimal we always use len == DECIMAL_BUFF_LENGTH == 9
    For internal testing here (ifdef MAIN) we always use len == 100/4
   */
  assert(from->len == to->len);

  if (unlikely(frac0 + intg0 > len)) {
    frac0 = len - intg0;
    scale = frac0 * DIG_PER_DEC1;
    error = E_DEC_TRUNCATED;
  }

  if (scale + from->intg < 0) {
    decimal_make_zero(to);
    return E_DEC_OK;
  }

  if (to != from) {
    dec1 *p0 = buf0 + intg0 + std::max(frac1, frac0);
    dec1 *p1 = buf1 + intg0 + std::max(frac1, frac0);

    assert(p0 - buf0 <= len);
    assert(p1 - buf1 <= len);

    while (buf0 < p0) *(--p1) = *(--p0);

    buf0 = to->buf;
    buf1 = to->buf;
    to->sign = from->sign;
    to->intg = std::min(intg0, len) * DIG_PER_DEC1;
  }

  if (frac0 > frac1) {
    buf1 += intg0 + frac1;
    while (frac0-- > frac1) *buf1++ = 0;
    goto done;
  }

  if (scale >= from->frac) goto done; /* nothing to do */

  buf0 += intg0 + frac0 - 1;
  buf1 += intg0 + frac0 - 1;
  if (scale == frac0 * DIG_PER_DEC1) {
    int do_inc = false;
    assert(frac0 + intg0 >= 0);
    switch (round_digit) {
      case 0: {
        dec1 *p0 = buf0 + (frac1 - frac0);
        for (; p0 > buf0; p0--) {
          if (*p0) {
            do_inc = true;
            break;
          }
        }
        break;
      }
      case 5: {
        x = buf0[1] / DIG_MASK;
        do_inc =
            (x > 5) ||
            ((x == 5) && (mode == HALF_UP || (frac0 + intg0 > 0 && *buf0 & 1)));
        break;
      }
      default:
        break;
    }
    if (do_inc) {
      if (frac0 + intg0 > 0)
        (*buf1)++;
      else
        *(++buf1) = DIG_BASE;
    } else if (frac0 + intg0 == 0) {
      decimal_make_zero(to);
      return E_DEC_OK;
    }
  } else {
    /* TODO - fix this code as it won't work for CEILING mode */
    int pos = frac0 * DIG_PER_DEC1 - scale - 1;
    assert(frac0 + intg0 > 0);
    x = *buf1 / powers10[pos];
    y = x % 10;
    if (y > round_digit ||
        (round_digit == 5 && y == 5 && (mode == HALF_UP || (x / 10) & 1)))
      x += 10;
    *buf1 = powers10[pos] * (x - y);
  }
  /*
    In case we're rounding e.g. 1.5e9 to 2.0e9, the decimal_digit_t's inside
    the buffer are as follows.

    Before <1, 5e8>
    After  <2, 5e8>

    Hence we need to set the 2nd field to 0.
    The same holds if we round 1.5e-9 to 2e-9.
   */
  if (frac0 < frac1) {
    dec1 *buf = to->buf + ((scale == 0 && intg0 == 0) ? 1 : intg0 + frac0);
    dec1 *end = to->buf + len;

    while (buf < end) *buf++ = 0;
  }
  if (*buf1 >= DIG_BASE) {
    carry = 1;
    *buf1 -= DIG_BASE;
    while (carry && --buf1 >= to->buf) ADD(*buf1, *buf1, 0, carry);
    if (unlikely(carry)) {
      /* shifting the number to create space for new digit */
      if (frac0 + intg0 >= len) {
        frac0--;
        scale = frac0 * DIG_PER_DEC1;
        error = E_DEC_TRUNCATED; /* XXX */
      }
      for (buf1 = to->buf + intg0 + std::max(frac0, 0); buf1 > to->buf;
           buf1--) {
        /* Avoid out-of-bounds write. */
        if (buf1 < to->buf + len)
          buf1[0] = buf1[-1];
        else
          error = E_DEC_OVERFLOW;
      }
      *buf1 = 1;
      /* We cannot have more than 9 * 9 = 81 digits. */
      if (to->intg < len * DIG_PER_DEC1)
        to->intg++;
      else
        error = E_DEC_OVERFLOW;
    }
  } else {
    for (;;) {
      if (likely(*buf1)) break;
      if (buf1-- == to->buf) {
        /* making 'zero' with the proper scale */
        dec1 *p0 = to->buf + frac0 + 1;
        to->intg = 1;
        to->frac = std::max(scale, 0);
        to->sign = false;
        for (buf1 = to->buf; buf1 < p0; buf1++) *buf1 = 0;
        return E_DEC_OK;
      }
    }
  }

  /* Here we  check 999.9 -> 1000 case when we need to increase intg */
  first_dig = to->intg % DIG_PER_DEC1;
  if (first_dig && (*buf1 >= powers10[first_dig])) to->intg++;

  if (scale < 0) scale = 0;

done:
  assert(to->intg <= (len * DIG_PER_DEC1));
  to->frac = scale;
  return error;
}