Implement complete VarBstrFromDec() for any valid DECIMAL, with

internationalization support borrowed from VARIANT_BstrFromReal(). The
decimal fix for VARIANT_BstrFromReal was encapsulated in a separate
function and reused.
This commit is contained in:
Alex Villacís Lasso 2005-10-26 10:12:20 +00:00 committed by Alexandre Julliard
parent 453e16d329
commit 00187fab29
1 changed files with 214 additions and 59 deletions

View File

@ -4594,6 +4594,146 @@ VarDecAdd_AsPositive:
return hRet;
}
/* internal representation of the value stored in a DECIMAL. The bytes are
stored from LSB at index 0 to MSB at index 11
*/
typedef struct DECIMAL_internal
{
DWORD bitsnum[3]; /* 96 significant bits, unsigned */
unsigned char scale; /* number scaled * 10 ^ -(scale) */
unsigned int sign : 1; /* 0 - positive, 1 - negative */
} VARIANT_DI;
/* translate from external DECIMAL format into an internal representation */
static void VARIANT_DIFromDec(const DECIMAL * from, VARIANT_DI * to)
{
to->scale = DEC_SCALE(from);
to->sign = DEC_SIGN(from) ? 1 : 0;
to->bitsnum[0] = DEC_LO32(from);
to->bitsnum[1] = DEC_MID32(from);
to->bitsnum[2] = DEC_HI32(from);
}
/* divide the (unsigned) number stored in p (LSB) by a byte value (<= 0xff). Any nonzero
size is supported. The value in p is replaced by the quotient of the division, and
the remainder is returned as a result. This routine is most often used with a divisor
of 10 in order to scale up numbers, and in the DECIMAL->string conversion.
*/
static unsigned char VARIANT_int_divbychar(DWORD * p, unsigned int n, unsigned char divisor)
{
if (divisor == 0) {
/* division by 0 */
return 0xFF;
} else if (divisor == 1) {
/* dividend remains unchanged */
return 0;
} else {
unsigned char remainder = 0;
ULONGLONG iTempDividend;
signed int i;
for (i = n - 1; i >= 0 && !p[i]; i--); /* skip leading zeros */
for (; i >= 0; i--) {
iTempDividend = ((ULONGLONG)remainder << 32) + p[i];
remainder = iTempDividend % divisor;
p[i] = iTempDividend / divisor;
}
return remainder;
}
}
/* check to test if encoded number is a zero. Returns 1 if zero, 0 for nonzero */
static int VARIANT_int_iszero(DWORD * p, unsigned int n)
{
for (; n > 0; n--) if (*p++ != 0) return 0;
return 1;
}
/* cast DECIMAL into string. Any scale should be handled properly. en_US locale is
hardcoded (period for decimal separator, dash as negative sign). Returns 0 for
success, nonzero if insufficient space in output buffer.
*/
static int VARIANT_DI_tostringW(VARIANT_DI * a, WCHAR * s, unsigned int n)
{
int overflow = 0;
DWORD quotient[3];
unsigned char remainder;
unsigned int i;
/* place negative sign */
if (!VARIANT_int_iszero(a->bitsnum, sizeof(a->bitsnum) / sizeof(DWORD)) && a->sign) {
if (n > 0) {
*s++ = '-';
n--;
}
else overflow = 1;
}
/* prepare initial 0 */
if (!overflow) {
if (n >= 2) {
s[0] = '0';
s[1] = '\0';
} else overflow = 1;
}
i = 0;
memcpy(quotient, a->bitsnum, sizeof(a->bitsnum));
while (!overflow && !VARIANT_int_iszero(quotient, sizeof(quotient) / sizeof(DWORD))) {
remainder = VARIANT_int_divbychar(quotient, sizeof(quotient) / sizeof(DWORD), 10);
if (i + 2 > n) {
overflow = 1;
} else {
s[i++] = '0' + remainder;
s[i] = '\0';
}
}
if (!overflow && !VARIANT_int_iszero(a->bitsnum, sizeof(a->bitsnum) / sizeof(DWORD))) {
/* reverse order of digits */
WCHAR * x = s; WCHAR * y = s + i - 1;
while (x < y) {
*x ^= *y;
*y ^= *x;
*x++ ^= *y--;
}
/* check for decimal point. "i" now has string length */
if (i <= a->scale) {
unsigned int numzeroes = a->scale + 1 - i;
if (i + 1 + numzeroes >= n) {
overflow = 1;
} else {
memmove(s + numzeroes, s, (i + 1) * sizeof(WCHAR));
i += numzeroes;
while (numzeroes > 0) {
s[--numzeroes] = '0';
}
}
}
/* place decimal point */
if (a->scale > 0) {
unsigned int periodpos = i - a->scale;
if (i + 2 >= n) {
overflow = 1;
} else {
memmove(s + periodpos + 1, s + periodpos, (i + 1 - periodpos) * sizeof(WCHAR));
s[periodpos] = '.'; i++;
/* remove extra zeros at the end, if any */
while (s[i - 1] == '0') s[--i] = '\0';
if (s[i - 1] == '.') s[--i] = '\0';
}
}
}
return overflow;
}
/************************************************************************
* VarDecDiv (OLEAUT32.178)
*
@ -5409,6 +5549,58 @@ HRESULT WINAPI VarBstrFromI4(LONG lIn, LCID lcid, ULONG dwFlags, BSTR* pbstrOut)
return VARIANT_BstrFromUInt(ul64, lcid, dwFlags, pbstrOut);
}
static BSTR VARIANT_BstrReplaceDecimal(WCHAR * buff, LCID lcid, ULONG dwFlags)
{
BSTR bstrOut;
WCHAR lpDecimalSep[16];
/* Native oleaut32 uses the locale-specific decimal separator even in the
absence of the LOCALE_USE_NLS flag. For example, the Spanish/Latin
American locales will see "one thousand and one tenth" as "1000,1"
instead of "1000.1" (notice the comma). The following code checks for
the need to replace the decimal separator, and if so, will prepare an
appropriate NUMBERFMTW structure to do the job via GetNumberFormatW().
*/
GetLocaleInfoW(lcid, LOCALE_SDECIMAL, lpDecimalSep, sizeof(lpDecimalSep) / sizeof(WCHAR));
if (lpDecimalSep[0] == '.' && lpDecimalSep[1] == '\0')
{
/* locale is compatible with English - return original string */
bstrOut = SysAllocString(buff);
}
else
{
WCHAR *p;
WCHAR numbuff[256];
WCHAR empty[1] = {'\0'};
NUMBERFMTW minFormat;
minFormat.NumDigits = 0;
minFormat.LeadingZero = 0;
minFormat.Grouping = 0;
minFormat.lpDecimalSep = lpDecimalSep;
minFormat.lpThousandSep = empty;
minFormat.NegativeOrder = 1; /* NLS_NEG_LEFT */
/* count number of decimal digits in string */
p = strchrW( buff, '.' );
if (p) minFormat.NumDigits = strlenW(p + 1);
numbuff[0] = '\0';
if (!GetNumberFormatW(lcid, dwFlags & LOCALE_NOUSEROVERRIDE,
buff, &minFormat, numbuff, sizeof(numbuff) / sizeof(WCHAR)))
{
WARN("GetNumberFormatW() failed, returning raw number string instead\n");
bstrOut = SysAllocString(buff);
}
else
{
TRACE("created minimal NLS string %s\n", debugstr_w(numbuff));
bstrOut = SysAllocString(numbuff);
}
}
return bstrOut;
}
static HRESULT VARIANT_BstrFromReal(DOUBLE dblIn, LCID lcid, ULONG dwFlags,
BSTR* pbstrOut, LPCWSTR lpszFormat)
{
@ -5445,52 +5637,7 @@ static HRESULT VARIANT_BstrFromReal(DOUBLE dblIn, LCID lcid, ULONG dwFlags,
}
else
{
WCHAR lpDecimalSep[16];
/* Native oleaut32 uses the locale-specific decimal separator even in the
absence of the LOCALE_USE_NLS flag. For example, the Spanish/Latin
American locales will see "one thousand and one tenth" as "1000,1"
instead of "1000.1" (notice the comma). The following code checks for
the need to replace the decimal separator, and if so, will prepare an
appropriate NUMBERFMTW structure to do the job via GetNumberFormatW().
*/
GetLocaleInfoW(lcid, LOCALE_SDECIMAL, lpDecimalSep, sizeof(lpDecimalSep) / sizeof(WCHAR));
if (lpDecimalSep[0] == '.' && lpDecimalSep[1] == '\0')
{
/* locale is compatible with English - return original string */
*pbstrOut = SysAllocString(buff);
}
else
{
WCHAR *p;
WCHAR numbuff[256];
WCHAR empty[1] = {'\0'};
NUMBERFMTW minFormat;
minFormat.NumDigits = 0;
minFormat.LeadingZero = 0;
minFormat.Grouping = 0;
minFormat.lpDecimalSep = lpDecimalSep;
minFormat.lpThousandSep = empty;
minFormat.NegativeOrder = 1; /* NLS_NEG_LEFT */
/* count number of decimal digits in string */
p = strchrW( buff, '.' );
if (p) minFormat.NumDigits = strlenW(p + 1);
numbuff[0] = '\0';
if (!GetNumberFormatW(lcid, dwFlags & LOCALE_NOUSEROVERRIDE,
buff, &minFormat, numbuff, sizeof(numbuff) / sizeof(WCHAR)))
{
WARN("GetNumberFormatW() failed, returning raw number string instead\n");
*pbstrOut = SysAllocString(buff);
}
else
{
TRACE("created minimal NLS string %s\n", debugstr_w(numbuff));
*pbstrOut = SysAllocString(numbuff);
}
}
*pbstrOut = VARIANT_BstrReplaceDecimal(buff, lcid, dwFlags);
}
return *pbstrOut ? S_OK : E_OUTOFMEMORY;
}
@ -5812,25 +5959,33 @@ HRESULT WINAPI VarBstrFromUI4(ULONG ulIn, LCID lcid, ULONG dwFlags, BSTR* pbstrO
*/
HRESULT WINAPI VarBstrFromDec(DECIMAL* pDecIn, LCID lcid, ULONG dwFlags, BSTR* pbstrOut)
{
WCHAR buff[256];
VARIANT_DI temp;
if (!pbstrOut)
return E_INVALIDARG;
if (!DEC_SCALE(pDecIn) && !DEC_HI32(pDecIn))
VARIANT_DIFromDec(pDecIn, &temp);
VARIANT_DI_tostringW(&temp, buff, 256);
if (dwFlags & LOCALE_USE_NLS)
{
WCHAR szBuff[256], *szOut = szBuff + sizeof(szBuff)/sizeof(WCHAR) - 1;
WCHAR numbuff[256];
/* Create the basic number string */
*szOut-- = '\0';
szOut = VARIANT_WriteNumber(DEC_LO64(pDecIn), szOut);
if (DEC_SIGN(pDecIn))
dwFlags |= VAR_NEGATIVE;
*pbstrOut = VARIANT_MakeBstr(lcid, dwFlags, szOut);
TRACE("returning %s\n", debugstr_w(*pbstrOut));
return *pbstrOut ? S_OK : E_OUTOFMEMORY;
/* Format the number for the locale */
numbuff[0] = '\0';
GetNumberFormatW(lcid, dwFlags & LOCALE_NOUSEROVERRIDE,
buff, NULL, numbuff, sizeof(numbuff) / sizeof(WCHAR));
TRACE("created NLS string %s\n", debugstr_w(numbuff));
*pbstrOut = SysAllocString(numbuff);
}
FIXME("semi-stub\n");
return E_INVALIDARG;
else
{
*pbstrOut = VARIANT_BstrReplaceDecimal(buff, lcid, dwFlags);
}
TRACE("returning %s\n", debugstr_w(*pbstrOut));
return *pbstrOut ? S_OK : E_OUTOFMEMORY;
}
/************************************************************************