int decimal_shift()

in mysql_strings/decimal.cc [753:906]


int decimal_shift(decimal_t *dec, int shift) {
  /* index of first non zero digit (all indexes from 0) */
  int beg;
  /* index of position after last decimal digit */
  int end;
  /* index of digit position just after point */
  int point = ROUND_UP(dec->intg) * DIG_PER_DEC1;
  /* new point position */
  int new_point = point + shift;
  /* length of result and new fraction in big digits*/
  int new_len, new_frac_len;
  /* return code */
  int err = E_DEC_OK;
  int new_front;

  if (shift == 0) return E_DEC_OK;

  digits_bounds(dec, &beg, &end);

  if (beg == end) {
    decimal_make_zero(dec);
    return E_DEC_OK;
  }

  /* number of digits in result */
  int digits_int = std::max(new_point - beg, 0);
  int digits_frac = std::max(end - new_point, 0);

  if ((new_len = ROUND_UP(digits_int) +
                 (new_frac_len = ROUND_UP(digits_frac))) > dec->len) {
    int lack = new_len - dec->len;
    int diff;

    if (new_frac_len < lack)
      return E_DEC_OVERFLOW; /* lack more then we have in fraction */

    /* cat off fraction part to allow new number to fit in our buffer */
    err = E_DEC_TRUNCATED;
    new_frac_len -= lack;
    diff = digits_frac - (new_frac_len * DIG_PER_DEC1);
    /* Make rounding method as parameter? */
    decimal_round(dec, dec, end - point - diff, HALF_UP);
    end -= diff;
    digits_frac = new_frac_len * DIG_PER_DEC1;

    if (end <= beg) {
      /*
        we lost all digits (they will be shifted out of buffer), so we can
        just return 0
      */
      decimal_make_zero(dec);
      return E_DEC_TRUNCATED;
    }
  }

  if (shift % DIG_PER_DEC1) {
    int l_mini_shift, r_mini_shift, mini_shift;
    int do_left;
    /*
      Calculate left/right shift to align decimal digits inside our bug
      digits correctly
    */
    if (shift > 0) {
      l_mini_shift = shift % DIG_PER_DEC1;
      r_mini_shift = DIG_PER_DEC1 - l_mini_shift;
      /*
        It is left shift so prefer left shift, but if we have not place from
        left, we have to have it from right, because we checked length of
        result
      */
      do_left = l_mini_shift <= beg;
      assert(do_left || (dec->len * DIG_PER_DEC1 - end) >= r_mini_shift);
    } else {
      r_mini_shift = (-shift) % DIG_PER_DEC1;
      l_mini_shift = DIG_PER_DEC1 - r_mini_shift;
      /* see comment above */
      do_left = !((dec->len * DIG_PER_DEC1 - end) >= r_mini_shift);
      assert(!do_left || l_mini_shift <= beg);
    }
    if (do_left) {
      do_mini_left_shift(dec, l_mini_shift, beg, end);
      mini_shift = -l_mini_shift;
    } else {
      do_mini_right_shift(dec, r_mini_shift, beg, end);
      mini_shift = r_mini_shift;
    }
    new_point += mini_shift;
    /*
      If number is shifted and correctly aligned in buffer we can
      finish
    */
    if (!(shift += mini_shift) && (new_point - digits_int) < DIG_PER_DEC1) {
      dec->intg = digits_int;
      dec->frac = digits_frac;
      return err; /* already shifted as it should be */
    }
    beg += mini_shift;
    end += mini_shift;
  }

  /* if new 'decimal front' is in first digit, we do not need move digits */
  if ((new_front = (new_point - digits_int)) >= DIG_PER_DEC1 || new_front < 0) {
    /* need to move digits */
    int d_shift;
    dec1 *to, *barier;
    if (new_front > 0) {
      /* move left */
      d_shift = new_front / DIG_PER_DEC1;
      to = dec->buf + (ROUND_UP(beg + 1) - 1 - d_shift);
      barier = dec->buf + (ROUND_UP(end) - 1 - d_shift);
      assert(to >= dec->buf);
      assert(barier + d_shift < dec->buf + dec->len);
      for (; to <= barier; to++) *to = *(to + d_shift);
      for (barier += d_shift; to <= barier; to++) *to = 0;
      d_shift = -d_shift;
    } else {
      /* move right */
      d_shift = (1 - new_front) / DIG_PER_DEC1;
      to = dec->buf + ROUND_UP(end) - 1 + d_shift;
      barier = dec->buf + ROUND_UP(beg + 1) - 1 + d_shift;
      assert(to < dec->buf + dec->len);
      assert(barier - d_shift >= dec->buf);
      for (; to >= barier; to--) *to = *(to - d_shift);
      for (barier -= d_shift; to >= barier; to--) *to = 0;
    }
    d_shift *= DIG_PER_DEC1;
    beg += d_shift;
    end += d_shift;
    new_point += d_shift;
  }

  /*
    If there are gaps then fill ren with 0.

    Only one of following 'for' loops will work becouse beg <= end
  */
  beg = ROUND_UP(beg + 1) - 1;
  end = ROUND_UP(end) - 1;
  assert(new_point >= 0);

  /* We don't want negative new_point below */
  if (new_point != 0) new_point = ROUND_UP(new_point) - 1;

  if (new_point > end) {
    do {
      dec->buf[new_point] = 0;
    } while (--new_point > end);
  } else {
    for (; new_point < beg; new_point++) dec->buf[new_point] = 0;
  }
  dec->intg = digits_int;
  dec->frac = digits_frac;
  return err;
}