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;
}