mirror of
git://source.winehq.org/git/wine.git
synced 2024-11-05 18:01:34 +00:00
Implement complete VarDecDiv() for any valid DECIMAL.
This commit is contained in:
parent
cb443bdb04
commit
69b5f808a7
1 changed files with 391 additions and 2 deletions
|
@ -4850,6 +4850,344 @@ static int VARIANT_DI_tostringW(VARIANT_DI * a, WCHAR * s, unsigned int n)
|
||||||
return overflow;
|
return overflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* shift the bits of a DWORD array to the left. p[0] is assumed LSB */
|
||||||
|
static void VARIANT_int_shiftleft(DWORD * p, unsigned int n, unsigned int shift)
|
||||||
|
{
|
||||||
|
DWORD shifted;
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
/* shift whole DWORDs to the left */
|
||||||
|
while (shift >= 32)
|
||||||
|
{
|
||||||
|
memmove(p + 1, p, (n - 1) * sizeof(DWORD));
|
||||||
|
*p = 0; shift -= 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* shift remainder (1..31 bits) */
|
||||||
|
shifted = 0;
|
||||||
|
if (shift > 0) for (i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
DWORD b;
|
||||||
|
b = p[i] >> (32 - shift);
|
||||||
|
p[i] = (p[i] << shift) | shifted;
|
||||||
|
shifted = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add the (unsigned) numbers stored in two DWORD arrays with LSB at index 0.
|
||||||
|
Value at v is incremented by the value at p. Any size is supported, provided
|
||||||
|
that v is not shorter than p. Any unapplied carry is returned as a result.
|
||||||
|
*/
|
||||||
|
static unsigned char VARIANT_int_add(DWORD * v, unsigned int nv, DWORD * p,
|
||||||
|
unsigned int np)
|
||||||
|
{
|
||||||
|
unsigned char carry = 0;
|
||||||
|
|
||||||
|
if (nv >= np) {
|
||||||
|
ULONGLONG sum;
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
for (i = 0; i < np; i++) {
|
||||||
|
sum = (ULONGLONG)v[i]
|
||||||
|
+ (ULONGLONG)p[i]
|
||||||
|
+ (ULONGLONG)carry;
|
||||||
|
v[i] = sum & 0xffffffff;
|
||||||
|
carry = sum >> 32;
|
||||||
|
}
|
||||||
|
for (; i < nv && carry; i++) {
|
||||||
|
sum = (ULONGLONG)v[i]
|
||||||
|
+ (ULONGLONG)carry;
|
||||||
|
v[i] = sum & 0xffffffff;
|
||||||
|
carry = sum >> 32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return carry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* perform integral division with operand p as dividend. Parameter n indicates
|
||||||
|
number of available DWORDs in divisor p, but available space in p must be
|
||||||
|
actually at least 2 * n DWORDs, because the remainder of the integral
|
||||||
|
division is built in the next n DWORDs past the start of the quotient. This
|
||||||
|
routine replaces the dividend in p with the quotient, and appends n
|
||||||
|
additional DWORDs for the remainder.
|
||||||
|
|
||||||
|
Thanks to Lee & Mark Atkinson for their book _Using_C_ (my very first book on
|
||||||
|
C/C++ :-) where the "longhand binary division" algorithm was exposed for the
|
||||||
|
source code to the VLI (Very Large Integer) division operator. This algorithm
|
||||||
|
was then heavily modified by me (Alex Villacis Lasso) in order to handle
|
||||||
|
variably-scaled integers such as the MS DECIMAL representation.
|
||||||
|
*/
|
||||||
|
static void VARIANT_int_div(DWORD * p, unsigned int n, DWORD * divisor,
|
||||||
|
unsigned int dn)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
DWORD tempsub[8];
|
||||||
|
DWORD * negdivisor = tempsub + n;
|
||||||
|
|
||||||
|
/* build 2s-complement of divisor */
|
||||||
|
for (i = 0; i < n; i++) negdivisor[i] = (i < dn) ? ~divisor[i] : 0xFFFFFFFF;
|
||||||
|
p[n] = 1;
|
||||||
|
VARIANT_int_add(negdivisor, n, p + n, 1);
|
||||||
|
memset(p + n, 0, n * sizeof(DWORD));
|
||||||
|
|
||||||
|
/* skip all leading zero DWORDs in quotient */
|
||||||
|
for (i = 0; i < n && !p[n - 1]; i++) VARIANT_int_shiftleft(p, n, 32);
|
||||||
|
/* i is now number of DWORDs left to process */
|
||||||
|
for (i <<= 5; i < (n << 5); i++) {
|
||||||
|
VARIANT_int_shiftleft(p, n << 1, 1); /* shl quotient+remainder */
|
||||||
|
|
||||||
|
/* trial subtraction */
|
||||||
|
memcpy(tempsub, p + n, n * sizeof(DWORD));
|
||||||
|
VARIANT_int_add(tempsub, n, negdivisor, n);
|
||||||
|
|
||||||
|
/* check whether result of subtraction was negative */
|
||||||
|
if ((tempsub[n - 1] & 0x80000000) == 0) {
|
||||||
|
memcpy(p + n, tempsub, n * sizeof(DWORD));
|
||||||
|
p[0] |= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* perform integral multiplication by a byte operand. Used for scaling by 10 */
|
||||||
|
static unsigned char VARIANT_int_mulbychar(DWORD * p, unsigned int n, unsigned char m)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
ULONG iOverflowMul;
|
||||||
|
|
||||||
|
for (iOverflowMul = 0, i = 0; i < n; i++)
|
||||||
|
p[i] = VARIANT_Mul(p[i], m, &iOverflowMul);
|
||||||
|
return (unsigned char)iOverflowMul;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* increment value in A by the value indicated in B, with scale adjusting.
|
||||||
|
Modifies parameters by adjusting scales. Returns 0 if addition was
|
||||||
|
successful, nonzero if a parameter underflowed before it could be
|
||||||
|
successfully used in the addition.
|
||||||
|
*/
|
||||||
|
static int VARIANT_int_addlossy(
|
||||||
|
DWORD * a, int * ascale, unsigned int an,
|
||||||
|
DWORD * b, int * bscale, unsigned int bn)
|
||||||
|
{
|
||||||
|
int underflow = 0;
|
||||||
|
|
||||||
|
if (VARIANT_int_iszero(a, an)) {
|
||||||
|
/* if A is zero, copy B into A, after removing digits */
|
||||||
|
while (bn > an && !VARIANT_int_iszero(b + an, bn - an)) {
|
||||||
|
VARIANT_int_divbychar(b, bn, 10);
|
||||||
|
(*bscale)--;
|
||||||
|
}
|
||||||
|
memcpy(a, b, an * sizeof(DWORD));
|
||||||
|
*ascale = *bscale;
|
||||||
|
} else if (!VARIANT_int_iszero(b, bn)) {
|
||||||
|
unsigned int tn = an + 1;
|
||||||
|
DWORD t[5];
|
||||||
|
|
||||||
|
if (bn + 1 > tn) tn = bn + 1;
|
||||||
|
if (*ascale != *bscale) {
|
||||||
|
/* first (optimistic) try - try to scale down the one with the bigger
|
||||||
|
scale, while this number is divisible by 10 */
|
||||||
|
DWORD * digitchosen;
|
||||||
|
unsigned int nchosen;
|
||||||
|
int * scalechosen;
|
||||||
|
int targetscale;
|
||||||
|
|
||||||
|
if (*ascale < *bscale) {
|
||||||
|
targetscale = *ascale;
|
||||||
|
scalechosen = bscale;
|
||||||
|
digitchosen = b;
|
||||||
|
nchosen = bn;
|
||||||
|
} else {
|
||||||
|
targetscale = *bscale;
|
||||||
|
scalechosen = ascale;
|
||||||
|
digitchosen = a;
|
||||||
|
nchosen = an;
|
||||||
|
}
|
||||||
|
memset(t, 0, tn * sizeof(DWORD));
|
||||||
|
memcpy(t, digitchosen, nchosen * sizeof(DWORD));
|
||||||
|
|
||||||
|
/* divide by 10 until target scale is reached */
|
||||||
|
while (*scalechosen > targetscale) {
|
||||||
|
unsigned char remainder = VARIANT_int_divbychar(t, tn, 10);
|
||||||
|
if (!remainder) {
|
||||||
|
(*scalechosen)--;
|
||||||
|
memcpy(digitchosen, t, nchosen * sizeof(DWORD));
|
||||||
|
} else break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*ascale != *bscale) {
|
||||||
|
DWORD * digitchosen;
|
||||||
|
unsigned int nchosen;
|
||||||
|
int * scalechosen;
|
||||||
|
int targetscale;
|
||||||
|
|
||||||
|
/* try to scale up the one with the smaller scale */
|
||||||
|
if (*ascale > *bscale) {
|
||||||
|
targetscale = *ascale;
|
||||||
|
scalechosen = bscale;
|
||||||
|
digitchosen = b;
|
||||||
|
nchosen = bn;
|
||||||
|
} else {
|
||||||
|
targetscale = *bscale;
|
||||||
|
scalechosen = ascale;
|
||||||
|
digitchosen = a;
|
||||||
|
nchosen = an;
|
||||||
|
}
|
||||||
|
memset(t, 0, tn * sizeof(DWORD));
|
||||||
|
memcpy(t, digitchosen, nchosen * sizeof(DWORD));
|
||||||
|
|
||||||
|
/* multiply by 10 until target scale is reached, or
|
||||||
|
significant bytes overflow the number
|
||||||
|
*/
|
||||||
|
while (*scalechosen < targetscale && t[nchosen] == 0) {
|
||||||
|
VARIANT_int_mulbychar(t, tn, 10);
|
||||||
|
if (t[nchosen] == 0) {
|
||||||
|
/* still does not overflow */
|
||||||
|
(*scalechosen)++;
|
||||||
|
memcpy(digitchosen, t, nchosen * sizeof(DWORD));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*ascale != *bscale) {
|
||||||
|
/* still different? try to scale down the one with the bigger scale
|
||||||
|
(this *will* lose significant digits) */
|
||||||
|
DWORD * digitchosen;
|
||||||
|
unsigned int nchosen;
|
||||||
|
int * scalechosen;
|
||||||
|
int targetscale;
|
||||||
|
|
||||||
|
if (*ascale < *bscale) {
|
||||||
|
targetscale = *ascale;
|
||||||
|
scalechosen = bscale;
|
||||||
|
digitchosen = b;
|
||||||
|
nchosen = bn;
|
||||||
|
} else {
|
||||||
|
targetscale = *bscale;
|
||||||
|
scalechosen = ascale;
|
||||||
|
digitchosen = a;
|
||||||
|
nchosen = an;
|
||||||
|
}
|
||||||
|
memset(t, 0, tn * sizeof(DWORD));
|
||||||
|
memcpy(t, digitchosen, nchosen * sizeof(DWORD));
|
||||||
|
|
||||||
|
/* divide by 10 until target scale is reached */
|
||||||
|
while (*scalechosen > targetscale) {
|
||||||
|
VARIANT_int_divbychar(t, tn, 10);
|
||||||
|
(*scalechosen)--;
|
||||||
|
memcpy(digitchosen, t, nchosen * sizeof(DWORD));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check whether any of the operands still has significant digits
|
||||||
|
(underflow case 1)
|
||||||
|
*/
|
||||||
|
if (VARIANT_int_iszero(a, an) || VARIANT_int_iszero(b, bn)) {
|
||||||
|
underflow = 1;
|
||||||
|
} else {
|
||||||
|
/* at this step, both numbers have the same scale and can be added
|
||||||
|
as integers. However, the result might not fit in A, so further
|
||||||
|
scaling down might be necessary.
|
||||||
|
*/
|
||||||
|
while (!underflow) {
|
||||||
|
memset(t, 0, tn * sizeof(DWORD));
|
||||||
|
memcpy(t, a, an * sizeof(DWORD));
|
||||||
|
|
||||||
|
VARIANT_int_add(t, tn, b, bn);
|
||||||
|
if (VARIANT_int_iszero(t + an, tn - an)) {
|
||||||
|
/* addition was successful */
|
||||||
|
memcpy(a, t, an * sizeof(DWORD));
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
/* addition overflowed - remove significant digits
|
||||||
|
from both operands and try again */
|
||||||
|
VARIANT_int_divbychar(a, an, 10); (*ascale)--;
|
||||||
|
VARIANT_int_divbychar(b, bn, 10); (*bscale)--;
|
||||||
|
/* check whether any operand keeps significant digits after
|
||||||
|
scaledown (underflow case 2)
|
||||||
|
*/
|
||||||
|
underflow = (VARIANT_int_iszero(a, an) || VARIANT_int_iszero(b, bn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return underflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* perform complete DECIMAL division in the internal representation. Returns
|
||||||
|
0 if the division was completed (even if quotient is set to 0), or nonzero
|
||||||
|
in case of quotient overflow.
|
||||||
|
*/
|
||||||
|
static HRESULT VARIANT_DI_div(VARIANT_DI * dividend, VARIANT_DI * divisor, VARIANT_DI * quotient)
|
||||||
|
{
|
||||||
|
HRESULT r_overflow = S_OK;
|
||||||
|
|
||||||
|
if (VARIANT_int_iszero(divisor->bitsnum, sizeof(divisor->bitsnum)/sizeof(DWORD))) {
|
||||||
|
/* division by 0 */
|
||||||
|
r_overflow = DISP_E_DIVBYZERO;
|
||||||
|
} else if (VARIANT_int_iszero(dividend->bitsnum, sizeof(dividend->bitsnum)/sizeof(DWORD))) {
|
||||||
|
VARIANT_DI_clear(quotient);
|
||||||
|
} else {
|
||||||
|
int quotientscale, remainderscale, tempquotientscale;
|
||||||
|
DWORD remainderplusquotient[8];
|
||||||
|
int underflow;
|
||||||
|
|
||||||
|
quotientscale = remainderscale = (int)dividend->scale - (int)divisor->scale;
|
||||||
|
tempquotientscale = quotientscale;
|
||||||
|
VARIANT_DI_clear(quotient);
|
||||||
|
quotient->sign = (dividend->sign ^ divisor->sign) ? 1 : 0;
|
||||||
|
|
||||||
|
/* The following strategy is used for division
|
||||||
|
1) if there was a nonzero remainder from previous iteration, use it as
|
||||||
|
dividend for this iteration, else (for first iteration) use intended
|
||||||
|
dividend
|
||||||
|
2) perform integer division in temporary buffer, develop quotient in
|
||||||
|
low-order part, remainder in high-order part
|
||||||
|
3) add quotient from step 2 to final result, with possible loss of
|
||||||
|
significant digits
|
||||||
|
4) multiply integer part of remainder by 10, while incrementing the
|
||||||
|
scale of the remainder. This operation preserves the intended value
|
||||||
|
of the remainder.
|
||||||
|
5) loop to step 1 until one of the following is true:
|
||||||
|
a) remainder is zero (exact division achieved)
|
||||||
|
b) addition in step 3 fails to modify bits in quotient (remainder underflow)
|
||||||
|
*/
|
||||||
|
memset(remainderplusquotient, 0, sizeof(remainderplusquotient));
|
||||||
|
memcpy(remainderplusquotient, dividend->bitsnum, sizeof(dividend->bitsnum));
|
||||||
|
do {
|
||||||
|
VARIANT_int_div(
|
||||||
|
remainderplusquotient, 4,
|
||||||
|
divisor->bitsnum, sizeof(divisor->bitsnum)/sizeof(DWORD));
|
||||||
|
underflow = VARIANT_int_addlossy(
|
||||||
|
quotient->bitsnum, "ientscale, sizeof(quotient->bitsnum) / sizeof(DWORD),
|
||||||
|
remainderplusquotient, &tempquotientscale, 4);
|
||||||
|
VARIANT_int_mulbychar(remainderplusquotient + 4, 4, 10);
|
||||||
|
memcpy(remainderplusquotient, remainderplusquotient + 4, 4 * sizeof(DWORD));
|
||||||
|
tempquotientscale = ++remainderscale;
|
||||||
|
} while (!underflow && !VARIANT_int_iszero(remainderplusquotient + 4, 4));
|
||||||
|
|
||||||
|
/* quotient scale might now be negative (extremely big number). If, so, try
|
||||||
|
to multiply quotient by 10 (without overflowing), while adjusting the scale,
|
||||||
|
until scale is 0. If this cannot be done, it is a real overflow.
|
||||||
|
*/
|
||||||
|
while (!r_overflow && quotientscale < 0) {
|
||||||
|
memset(remainderplusquotient, 0, sizeof(remainderplusquotient));
|
||||||
|
memcpy(remainderplusquotient, quotient->bitsnum, sizeof(quotient->bitsnum));
|
||||||
|
VARIANT_int_mulbychar(remainderplusquotient, sizeof(remainderplusquotient)/sizeof(DWORD), 10);
|
||||||
|
if (VARIANT_int_iszero(remainderplusquotient + sizeof(quotient->bitsnum)/sizeof(DWORD),
|
||||||
|
(sizeof(remainderplusquotient) - sizeof(quotient->bitsnum))/sizeof(DWORD))) {
|
||||||
|
quotientscale++;
|
||||||
|
memcpy(quotient->bitsnum, remainderplusquotient, sizeof(quotient->bitsnum));
|
||||||
|
} else r_overflow = DISP_E_OVERFLOW;
|
||||||
|
}
|
||||||
|
if (!r_overflow) {
|
||||||
|
if (quotientscale <= 255) quotient->scale = quotientscale;
|
||||||
|
else VARIANT_DI_clear(quotient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r_overflow;
|
||||||
|
}
|
||||||
|
|
||||||
/************************************************************************
|
/************************************************************************
|
||||||
* VarDecDiv (OLEAUT32.178)
|
* VarDecDiv (OLEAUT32.178)
|
||||||
*
|
*
|
||||||
|
@ -4866,8 +5204,59 @@ static int VARIANT_DI_tostringW(VARIANT_DI * a, WCHAR * s, unsigned int n)
|
||||||
*/
|
*/
|
||||||
HRESULT WINAPI VarDecDiv(const DECIMAL* pDecLeft, const DECIMAL* pDecRight, DECIMAL* pDecOut)
|
HRESULT WINAPI VarDecDiv(const DECIMAL* pDecLeft, const DECIMAL* pDecRight, DECIMAL* pDecOut)
|
||||||
{
|
{
|
||||||
FIXME("(%p,%p,%p)-stub!\n",pDecLeft,pDecRight,pDecOut);
|
HRESULT hRet = S_OK;
|
||||||
return DISP_E_OVERFLOW;
|
VARIANT_DI di_left, di_right, di_result;
|
||||||
|
HRESULT divresult;
|
||||||
|
|
||||||
|
if (!pDecLeft || !pDecRight || !pDecOut) return E_INVALIDARG;
|
||||||
|
|
||||||
|
VARIANT_DIFromDec(pDecLeft, &di_left);
|
||||||
|
VARIANT_DIFromDec(pDecRight, &di_right);
|
||||||
|
divresult = VARIANT_DI_div(&di_left, &di_right, &di_result);
|
||||||
|
if (divresult)
|
||||||
|
{
|
||||||
|
/* division actually overflowed */
|
||||||
|
hRet = divresult;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hRet = S_OK;
|
||||||
|
|
||||||
|
if (di_result.scale > DEC_MAX_SCALE)
|
||||||
|
{
|
||||||
|
unsigned char remainder = 0;
|
||||||
|
|
||||||
|
/* division underflowed. In order to comply with the MSDN
|
||||||
|
specifications for DECIMAL ranges, some significant digits
|
||||||
|
must be removed
|
||||||
|
*/
|
||||||
|
WARN("result scale is %u, scaling (with loss of significant digits)...\n",
|
||||||
|
di_result.scale);
|
||||||
|
while (di_result.scale > DEC_MAX_SCALE &&
|
||||||
|
!VARIANT_int_iszero(di_result.bitsnum, sizeof(di_result.bitsnum) / sizeof(DWORD)))
|
||||||
|
{
|
||||||
|
remainder = VARIANT_int_divbychar(di_result.bitsnum, sizeof(di_result.bitsnum) / sizeof(DWORD), 10);
|
||||||
|
di_result.scale--;
|
||||||
|
}
|
||||||
|
if (di_result.scale > DEC_MAX_SCALE)
|
||||||
|
{
|
||||||
|
WARN("result underflowed, setting to 0\n");
|
||||||
|
di_result.scale = 0;
|
||||||
|
di_result.sign = 0;
|
||||||
|
}
|
||||||
|
else if (remainder >= 5) /* round up result - native oleaut32 does this */
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
for (remainder = 1, i = 0; i < sizeof(di_result.bitsnum) / sizeof(DWORD) && remainder; i++) {
|
||||||
|
ULONGLONG digit = di_result.bitsnum[i] + 1;
|
||||||
|
remainder = (digit > 0xFFFFFFFF) ? 1 : 0;
|
||||||
|
di_result.bitsnum[i] = digit & 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VARIANT_DecFromDI(&di_result, pDecOut);
|
||||||
|
}
|
||||||
|
return hRet;
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************************************************************
|
/************************************************************************
|
||||||
|
|
Loading…
Reference in a new issue