// ==++==
//
//
// Copyright (c) 2006 Microsoft Corporation. All rights reserved.
//
// The use and distribution terms for this software are contained in the file
// named license.txt, which can be found in the root of this distribution.
// By using this software in any fashion, you are agreeing to be bound by the
// terms of this license.
//
// You must not remove this notice, or any other, from this software.
//
//
// ==--==
#include "common.h"
#include "excep.h"
#include "comnumber.h"
#include "comstring.h"
#include "comdecimal.h"
#include <stdlib.h>
typedef wchar_t wchar;
#define INT32_PRECISION 10
#define UINT32_PRECISION INT32_PRECISION
#define INT64_PRECISION 19
#define UINT64_PRECISION 20
#define FLOAT_PRECISION 7
#define DOUBLE_PRECISION 15
#define LARGE_BUFFER_SIZE 600
#define MIN_BUFFER_SIZE 105
#define SCALE_NAN 0x80000000
#define SCALE_INF 0x7FFFFFFF
struct FPSINGLE {
#if BIGENDIAN
unsigned int sign: 1;
unsigned int exp: 8;
unsigned int mant: 23;
#else
unsigned int mant: 23;
unsigned int exp: 8;
unsigned int sign: 1;
#endif
};
struct FPDOUBLE {
#if BIGENDIAN
unsigned int sign: 1;
unsigned int exp: 11;
unsigned int mantHi: 20;
unsigned int mantLo;
#else
unsigned int mantLo;
unsigned int mantHi: 20;
unsigned int exp: 11;
unsigned int sign: 1;
#endif
};
static const char* const posCurrencyFormats[] = {
"$#", "#$", "$ #", "# $"};
static const char* const negCurrencyFormats[] = {
"($#)", "-$#", "$-#", "$#-",
"(#$)", "-#$", "#-$", "#$-",
"-# $", "-$ #", "# $-", "$ #-",
"$ -#", "#- $", "($ #)", "(# $)"};
static const char* const posPercentFormats[] = {
"# %", "#%", "%#", "% #"
};
static const char* const negPercentFormats[] = {
"-# %", "-#%", "-%#",
"%-#", "%#-",
"#-%", "#%-",
"-% #", "# %-", "% #-",
"% -#", "#- %"
};
static const char* const negNumberFormats[] = {
"(#)", "-#", "- #", "#-", "# -",
};
static const char posNumberFormat[] = "#";
void DoubleToNumber(double value, int precision, NUMBER* number)
{
WRAPPER_CONTRACT
_ASSERTE(number != NULL);
number->precision = precision;
if (((FPDOUBLE*)&value)->exp == 0x7FF) {
number->scale = (((FPDOUBLE*)&value)->mantLo || ((FPDOUBLE*)&value)->mantHi) ? SCALE_NAN: SCALE_INF;
number->sign = ((FPDOUBLE*)&value)->sign;
number->digits[0] = 0;
}
else {
char* src = _ecvt(value, precision, &number->scale, &number->sign);
wchar* dst = number->digits;
if (*src != '0') {
while (*src) *dst++ = *src++;
}
*dst = 0;
}
}
/*===========================================================
Portable NumberToDouble implementation
--------------------------------------
- does the conversion with the best possible precision.
- does not use any float arithmetic so it is not sensitive
to differences in precision of floating point calculations
across platforms.
The internal integer representation of the float number is
UINT64 mantisa + INT exponent. The mantisa is kept normalized
ie with the most significant one being 63-th bit of UINT64.
===========================================================*/
//
// get 32-bit integer from at most 9 digits
//
static unsigned DigitsToInt(__in_ecount(count) wchar* p, int count)
{
LEAF_CONTRACT
_ASSERTE(1 <= count && count <= 9);
wchar* end = p + count;
unsigned res = *p - '0';
for ( p = p + 1; p < end; p++) {
res = 10 * res + *p - '0';
}
return res;
}
//
// helper macro to multiply two 32-bit uints
//
#define Mul32x32To64(a, b) ((UINT64)((UINT32)(a)) * (UINT64)((UINT32)(b)))
//
// multiply two numbers in the internal integer representation
//
static UINT64 Mul64Lossy(UINT64 a, UINT64 b, INT* pexp)
{
LEAF_CONTRACT
// it's ok to losse some precision here - Mul64 will be called
// at most twice during the conversion, so the error won't propagate
// to any of the 53 significant bits of the result
UINT64 val = Mul32x32To64(a >> 32, b >> 32) +
(Mul32x32To64(a >> 32, b) >> 32) +
(Mul32x32To64(a, b >> 32) >> 32);
// normalize
if ((val & I64(0x8000000000000000)) == 0) { val <<= 1; *pexp -= 1; }
return val;
}
//
// precomputed tables with powers of 10. These allows us to do at most
// two Mul64 during the conversion. This is important not only
// for speed, but also for precision because of Mul64 computes with 1 bit error.
//
static const UINT64 rgval64Power10[] = {
// powers of 10
/*1*/ I64(0xa000000000000000),
/*2*/ I64(0xc800000000000000),
/*3*/ I64(0xfa00000000000000),
/*4*/ I64(0x9c40000000000000),
/*5*/ I64(0xc350000000000000),
/*6*/ I64(0xf424000000000000),
/*7*/ I64(0x9896800000000000),
/*8*/ I64(0xbebc200000000000),
/*9*/ I64(0xee6b280000000000),
/*10*/ I64(0x9502f90000000000),
/*11*/ I64(0xba43b74000000000),
/*12*/ I64(0xe8d4a51000000000),
/*13*/ I64(0x9184e72a00000000),
/*14*/ I64(0xb5e620f480000000),
/*15*/ I64(0xe35fa931a0000000),
// powers of 0.1
/*1*/ I64(0xcccccccccccccccd),
/*2*/ I64(0xa3d70a3d70a3d70b),
/*3*/ I64(0x83126e978d4fdf3c),
/*4*/ I64(0xd1b71758e219652e),
/*5*/ I64(0xa7c5ac471b478425),
/*6*/ I64(0x8637bd05af6c69b7),
/*7*/ I64(0xd6bf94d5e57a42be),
/*8*/ I64(0xabcc77118461ceff),
/*9*/ I64(0x89705f4136b4a599),
/*10*/ I64(0xdbe6fecebdedd5c2),
/*11*/ I64(0xafebff0bcb24ab02),
/*12*/ I64(0x8cbccc096f5088cf),
/*13*/ I64(0xe12e13424bb40e18),
/*14*/ I64(0xb424dc35095cd813),
/*15*/ I64(0x901d7cf73ab0acdc),
};
static const INT8 rgexp64Power10[] = {
// exponents for both powers of 10 and 0.1
/*1*/ 4,
/*2*/ 7,
/*3*/ 10,
/*4*/ 14,
/*5*/ 17,
/*6*/ 20,
/*7*/ 24,
/*8*/ 27,
/*9*/ 30,
/*10*/ 34,
/*11*/ 37,
/*12*/ 40,
/*13*/ 44,
/*14*/ 47,
/*15*/ 50,
};
static const UINT64 rgval64Power10By16[] = {
// powers of 10^16
/*1*/ I64(0x8e1bc9bf04000000),
/*2*/ I64(0x9dc5ada82b70b59e),
/*3*/ I64(0xaf298d050e4395d6),
/*4*/ I64(0xc2781f49ffcfa6d4),
/*5*/ I64(0xd7e77a8f87daf7fa),
/*6*/ I64(0xefb3ab16c59b14a0),
/*7*/ I64(0x850fadc09923329c),
/*8*/ I64(0x93ba47c980e98cde),
/*9*/ I64(0xa402b9c5a8d3a6e6),
/*10*/ I64(0xb616a12b7fe617a8),
/*11*/ I64(0xca28a291859bbf90),
/*12*/ I64(0xe070f78d39275566),
/*13*/ I64(0xf92e0c3537826140),
/*14*/ I64(0x8a5296ffe33cc92c),
/*15*/ I64(0x9991a6f3d6bf1762),
/*16*/ I64(0xaa7eebfb9df9de8a),
/*17*/ I64(0xbd49d14aa79dbc7e),
/*18*/ I64(0xd226fc195c6a2f88),
/*19*/ I64(0xe950df20247c83f8),
/*20*/ I64(0x81842f29f2cce373),
/*21*/ I64(0x8fcac257558ee4e2),
// powers of 0.1^16
/*1*/ I64(0xe69594bec44de160),
/*2*/ I64(0xcfb11ead453994c3),
/*3*/ I64(0xbb127c53b17ec165),
/*4*/ I64(0xa87fea27a539e9b3),
/*5*/ I64(0x97c560ba6b0919b5),
/*6*/ I64(0x88b402f7fd7553ab),
/*7*/ I64(0xf64335bcf065d3a0),
/*8*/ I64(0xddd0467c64bce4c4),
/*9*/ I64(0xc7caba6e7c5382ed),
/*10*/ I64(0xb3f4e093db73a0b7),
/*11*/ I64(0xa21727db38cb0053),
/*12*/ I64(0x91ff83775423cc29),
/*13*/ I64(0x8380dea93da4bc82),
/*14*/ I64(0xece53cec4a314f00),
/*15*/ I64(0xd5605fcdcf32e217),
/*16*/ I64(0xc0314325637a1978),
/*17*/ I64(0xad1c8eab5ee43ba2),
/*18*/ I64(0x9becce62836ac5b0),
/*19*/ I64(0x8c71dcd9ba0b495c),
/*20*/ I64(0xfd00b89747823938),
/*21*/ I64(0xe3e27a444d8d991a),
};
static const INT16 rgexp64Power10By16[] = {
// exponents for both powers of 10^16 and 0.1^16
/*1*/ 54,
/*2*/ 107,
/*3*/ 160,
/*4*/ 213,
/*5*/ 266,
/*6*/ 319,
/*7*/ 373,
/*8*/ 426,
/*9*/ 479,
/*10*/ 532,
/*11*/ 585,
/*12*/ 638,
/*13*/ 691,
/*14*/ 745,
/*15*/ 798,
/*16*/ 851,
/*17*/ 904,
/*18*/ 957,
/*19*/ 1010,
/*20*/ 1064,
/*21*/ 1117,
};
#ifdef _DEBUG
//
// slower high precision version of Mul64 for computation of the tables
//
static UINT64 Mul64Precise(UINT64 a, UINT64 b, INT* pexp)
{
LEAF_CONTRACT
UINT64 hilo =
((Mul32x32To64(a >> 32, b) >> 1) +
(Mul32x32To64(a, b >> 32) >> 1) +
(Mul32x32To64(a, b) >> 33)) >> 30;
UINT64 val = Mul32x32To64(a >> 32, b >> 32) + (hilo >> 1) + (hilo & 1);
// normalize
if ((val & I64(0x8000000000000000)) == 0) { val <<= 1; *pexp -= 1; }
return val;
}
//
// debug-only verification of the precomputed tables
//
static void CheckTable(UINT64 val, INT exp, LPCVOID table, int size, LPCSTR name, int tabletype)
{
WRAPPER_CONTRACT
UINT64 multval = val;
INT mulexp = exp;
bool fBad = false;
for (int i = 0; i < size; i++) {
switch (tabletype) {
case 1:
if (((UINT64*)table)[i] != val) {
if (!fBad) {
fprintf(stderr, "%s:\n", name);
fBad = true;
}
fprintf(stderr, "/*%d*/ I64(0x%I64x),\n", i+1, val);
}
break;
case 2:
if (((INT8*)table)[i] != exp) {
if (!fBad) {
fprintf(stderr, "%s:\n", name);
fBad = true;
}
fprintf(stderr, "/*%d*/ %d,\n", i+1, exp);
}
break;
case 3:
if (((INT16*)table)[i] != exp) {
if (!fBad) {
fprintf(stderr, "%s:\n", name);
fBad = true;
}
fprintf(stderr, "/*%d*/ %d,\n", i+1, exp);
}
break;
default:
_ASSERTE(false);
break;
}
exp += mulexp;
val = Mul64Precise(val, multval, &exp);
}
_ASSERTE(!fBad || !"NumberToDouble table not correct. Correct version dumped to stderr.");
}
void CheckTables()
{
WRAPPER_CONTRACT
UINT64 val; INT exp;
val = I64(0xa000000000000000); exp = 4; // 10
CheckTable(val, exp, rgval64Power10, 15, "rgval64Power10", 1);
CheckTable(val, exp, rgexp64Power10, 15, "rgexp64Power10", 2);
val = I64(0x8e1bc9bf04000000); exp = 54; //10^16
CheckTable(val, exp, rgval64Power10By16, 21, "rgval64Power10By16", 1);
CheckTable(val, exp, rgexp64Power10By16, 21, "rgexp64Power10By16", 3);
val = I64(0xCCCCCCCCCCCCCCCD); exp = -3; // 0.1
CheckTable(val, exp, rgval64Power10+15, 15, "rgval64Power10 - inv", 1);
val = I64(0xe69594bec44de160); exp = -53; // 0.1^16
CheckTable(val, exp, rgval64Power10By16+21, 21, "rgval64Power10By16 - inv", 1);
}
#endif // _DEBUG
void NumberToDouble(NUMBER* number, double* value)
{
WRAPPER_CONTRACT
UINT64 val;
INT exp;
wchar* src = number->digits;
int remaining;
int total;
int count;
int scale;
int absscale;
int index;
#ifdef _DEBUG
static bool fCheckedTables = false;
if (!fCheckedTables) {
CheckTables();
fCheckedTables = true;
}
#endif // _DEBUG
total = (int)wcslen(src);
remaining = total;
// skip the leading zeros
while (*src == '0') {
remaining--;
src++;
}
if (remaining == 0) {
*value = 0;
goto done;
}
count = min(remaining, 9);
remaining -= count;
val = DigitsToInt(src, count);
if (remaining > 0) {
count = min(remaining, 9);
remaining -= count;
// get the denormalized power of 10
UINT32 mult = (UINT32)(rgval64Power10[count-1] >> (64 - rgexp64Power10[count-1]));
val = Mul32x32To64(val, mult) + DigitsToInt(src+9, count);
}
scale = number->scale - (total - remaining);
absscale = abs(scale);
if (absscale >= 22 * 16) {
// overflow / underflow
*(UINT64*)value = (scale > 0) ? I64(0x7FF0000000000000) : 0;
goto done;
}
exp = 64;
// normalize the mantisa
if ((val & I64(0xFFFFFFFF00000000)) == 0) { val <<= 32; exp -= 32; }
if ((val & I64(0xFFFF000000000000)) == 0) { val <<= 16; exp -= 16; }
if ((val & I64(0xFF00000000000000)) == 0) { val <<= 8; exp -= 8; }
if ((val & I64(0xF000000000000000)) == 0) { val <<= 4; exp -= 4; }
if ((val & I64(0xC000000000000000)) == 0) { val <<= 2; exp -= 2; }
if ((val & I64(0x8000000000000000)) == 0) { val <<= 1; exp -= 1; }
index = absscale & 15;
if (index) {
INT multexp = rgexp64Power10[index-1];
// the exponents are shared between the inverted and regular table
exp += (scale < 0) ? (-multexp + 1) : multexp;
UINT64 multval = rgval64Power10[index + ((scale < 0) ? 15 : 0) - 1];
val = Mul64Lossy(val, multval, &exp);
}
index = absscale >> 4;
if (index) {
INT multexp = rgexp64Power10By16[index-1];
// the exponents are shared between the inverted and regular table
exp += (scale < 0) ? (-multexp + 1) : multexp;
UINT64 multval = rgval64Power10By16[index + ((scale < 0) ? 21 : 0) - 1];
val = Mul64Lossy(val, multval, &exp);
}
// round & scale down
if ((UINT32)val & (1 << 10))
{
// IEEE round to even
UINT64 tmp = val + ((1 << 10) - 1) + (((UINT32)val >> 11) & 1);
if (tmp < val) {
// overflow
tmp = (tmp >> 1) | I64(0x8000000000000000);
exp += 1;
}
val = tmp;
}
val >>= 11;
exp += 0x3FE;
if (exp <= 0) {
if (exp <= -52) {
// underflow
val = 0;
}
else {
// denormalized
val >>= (-exp+1);
}
}
else
if (exp >= 0x7FF) {
// overflow
val = I64(0x7FF0000000000000);
}
else {
val = ((UINT64)exp << 52) + (val & I64(0x000FFFFFFFFFFFFF));
}
*(UINT64*)value = val;
done:
if (number->sign) *(UINT64*)value |= I64(0x8000000000000000);
}
wchar_t* COMNumber::Int32ToDecChars(wchar_t* p, unsigned int value, int digits)
{
LEAF_CONTRACT
_ASSERTE(p != NULL);
while (--digits >= 0 || value != 0) {
*--p = value % 10 + '0';
value /= 10;
}
return p;
}
unsigned int Int64DivMod1E9(unsigned __int64* value)
{
LEAF_CONTRACT
_ASSERTE(value != NULL);
unsigned int rem = (unsigned int)(*value % 1000000000);
*value /= 1000000000;
return rem;
}
inline void AddStringRef(wchar** ppBuffer, STRINGREF strRef)
{
WRAPPER_CONTRACT
_ASSERTE(ppBuffer != NULL && strRef != NULL);
wchar* buffer = strRef->GetBuffer();
_ASSERTE(buffer != NULL);
DWORD length = strRef->GetStringLength();
for (wchar* str = buffer; str < buffer + length; (*ppBuffer)++, str++)
{
**ppBuffer = *str;
}
}
LPCWSTR MatchChars(LPCWSTR p, LPCWSTR str)
{
LEAF_CONTRACT
_ASSERTE(p != NULL && str != NULL);
if (!*str) return 0;
for (; *str; p++, str++)
{
if (*p != *str) //We only hurt the failure case
{
if ((*str == 0xA0) && (*p == 0x20)) // This fix is for French or Kazakh cultures. Since a user cannot type 0xA0 as a
// space character we use 0x20 space character instead to mean the same.
continue;
return 0;
}
}
return p;
}
wchar* Int32ToHexChars(wchar* p, unsigned int value, int hexBase, int digits)
{
LEAF_CONTRACT
_ASSERTE(p != NULL);
while (--digits >= 0 || value != 0) {
int digit = value & 0xF;
*--p = digit + (digit < 10? '0': hexBase);
value >>= 4;
}
return p;
}
STRINGREF Int32ToDecStr(int value, int digits, STRINGREF sNegative)
{
CONTRACTL {
THROWS;
INJECT_FAULT(COMPlusThrowOM());
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
CQuickBytes buf;
if (digits < 1) digits = 1;
UINT maxDigitsLength = (digits > 15) ? digits : 15; // Since an int32 can have maximum of 10 chars as a String
UINT bufferLength = (maxDigitsLength > 100) ? maxDigitsLength : 100;
int negLength = 0;
wchar* src = NULL;
if (value < 0) {
_ASSERTE(sNegative != NULL);
src = sNegative->GetBuffer();
_ASSERTE(src != NULL);
negLength = sNegative->GetStringLength();
if ((UINT) negLength > bufferLength - maxDigitsLength) {
bufferLength = (UINT) negLength + maxDigitsLength;
}
}
wchar *buffer = (wchar*)buf.AllocThrows(bufferLength * sizeof(WCHAR));
wchar* p = COMNumber::Int32ToDecChars(buffer + bufferLength, value >= 0? value: -value, digits);
_ASSERTE(p != NULL);
if (value < 0) {
for (int i =negLength - 1; i >= 0; i--)
{
*(--p) = *(src+i);
}
}
_ASSERTE(buffer + bufferLength - p >=0 && buffer <= p);
return COMString::NewString(p, (int)(buffer + bufferLength - p));
}
STRINGREF UInt32ToDecStr(unsigned int value, int digits)
{
WRAPPER_CONTRACT
wchar buffer[100];
if (digits < 1) digits = 1;
wchar* p = COMNumber::Int32ToDecChars(buffer + 100, value, digits);
_ASSERTE(p != NULL && p >= buffer && p < (buffer + 100));
return COMString::NewString(p, (int) (buffer + 100 - p));
}
STRINGREF Int32ToHexStr(unsigned int value, int hexBase, int digits)
{
WRAPPER_CONTRACT
wchar buffer[100];
if (digits < 1) digits = 1;
wchar* p = Int32ToHexChars(buffer + 100, value, hexBase, digits);
return COMString::NewString(p, (int) (buffer + 100 - p));
}
void Int32ToNumber(int value, NUMBER* number)
{
WRAPPER_CONTRACT
_ASSERTE(number != NULL);
wchar buffer[INT32_PRECISION+1];
number->precision = INT32_PRECISION;
if (value >= 0) {
number->sign = 0;
}
else {
number->sign = 1;
value = -value;
}
wchar* p = COMNumber::Int32ToDecChars(buffer + INT32_PRECISION, value, 0);
_ASSERTE(p != NULL);
int i = (int) (buffer + INT32_PRECISION - p);
number->scale = i;
wchar* dst = number->digits;
_ASSERTE(dst != NULL);
while (--i >= 0) *dst++ = *p++;
*dst = 0;
}
void UInt32ToNumber(unsigned int value, NUMBER* number)
{
WRAPPER_CONTRACT
_ASSERTE(number != NULL);
wchar buffer[UINT32_PRECISION+1];
number->precision = UINT32_PRECISION;
number->sign = 0;
wchar* p = COMNumber::Int32ToDecChars(buffer + UINT32_PRECISION, value, 0);
_ASSERT(p != NULL);
int i = (int) (buffer + UINT32_PRECISION - p);
number->scale = i;
wchar* dst = number->digits;
_ASSERT(dst != NULL);
while (--i >= 0) *dst++ = *p++;
*dst = 0;
}
#define LO32(x) ((unsigned int)(x))
#define HI32(x) ((unsigned int)(((x) & UI64(0xFFFFFFFF00000000)) >> 32))
STRINGREF Int64ToDecStr(__int64 value, int digits, STRINGREF sNegative)
{
CONTRACTL {
THROWS;
INJECT_FAULT(COMPlusThrowOM());
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
CQuickBytes buf;
if (digits < 1) digits = 1;
int sign = HI32(value);
// digits as specified in the format string can be at most 99.
UINT maxDigitsLength = (digits > 20) ? digits : 20;
UINT bufferLength = (maxDigitsLength > 100) ? maxDigitsLength : 100;
if (sign < 0) {
value = -value;
_ASSERTE(sNegative);
int negLength = sNegative->GetStringLength();
if ((UINT) negLength > bufferLength - maxDigitsLength) {
bufferLength = negLength + maxDigitsLength;
}
}
wchar *buffer = (wchar*)buf.AllocThrows(bufferLength * sizeof(WCHAR));
wchar* p = buffer + bufferLength;
while (HI32(value)) {
p = COMNumber::Int32ToDecChars(p, Int64DivMod1E9((unsigned __int64*)&value), 9);
_ASSERTE(p != NULL);
digits -= 9;
}
p = COMNumber::Int32ToDecChars(p, LO32(value), digits);
_ASSERTE(p != NULL);
if (sign < 0) {
wchar* src = sNegative->GetBuffer();
_ASSERTE(src != NULL);
for (int i =sNegative->GetStringLength() - 1; i >= 0; i--)
{
*(--p) = *(src+i);
}
}
return COMString::NewString(p, (int) (buffer + bufferLength - p));
}
STRINGREF UInt64ToDecStr(unsigned __int64 value, int digits)
{
WRAPPER_CONTRACT
wchar buffer[100];
if (digits < 1) digits = 1;
wchar* p = buffer + 100;
while (HI32(value)) {
p = COMNumber::Int32ToDecChars(p, Int64DivMod1E9(&value), 9);
_ASSERTE(p != NULL);
digits -= 9;
}
p = COMNumber::Int32ToDecChars(p, LO32(value), digits);
_ASSERTE(p != NULL && p >= buffer && p < (buffer + 100));
return COMString::NewString(p, (int) (buffer + 100 - p));
}
STRINGREF Int64ToHexStr(unsigned __int64 value, int hexBase, int digits)
{
WRAPPER_CONTRACT
wchar buffer[100];
wchar* p;
if (HI32(value)) {
Int32ToHexChars(buffer + 100, LO32(value), hexBase, 8);
p = Int32ToHexChars(buffer + 100 - 8, HI32(value), hexBase, digits - 8);
}
else {
if (digits < 1) digits = 1;
p = Int32ToHexChars(buffer + 100, LO32(value), hexBase, digits);
}
_ASSERTE(p != NULL && p >= buffer && p < (buffer + 100));
return COMString::NewString(p, (int) (buffer + 100 - p));
}
void Int64ToNumber(__int64 value, NUMBER* number)
{
WRAPPER_CONTRACT
wchar buffer[INT64_PRECISION+1];
number->precision = INT64_PRECISION;
if (value >= 0) {
number->sign = 0;
}
else {
number->sign = 1;
value = -value;
}
wchar* p = buffer + INT64_PRECISION;
while (HI32(value)) {
p = COMNumber::Int32ToDecChars(p, Int64DivMod1E9((unsigned __int64*)&value), 9);
_ASSERTE(p != NULL);
}
p = COMNumber::Int32ToDecChars(p, LO32(value), 0);
_ASSERTE(p != NULL);
int i = (int) (buffer + INT64_PRECISION - p);
number->scale = i;
wchar* dst = number->digits;
_ASSERTE(dst != NULL);
while (--i >= 0) *dst++ = *p++;
*dst = 0;
}
void UInt64ToNumber(unsigned __int64 value, NUMBER* number)
{
WRAPPER_CONTRACT
wchar buffer[UINT64_PRECISION+1];
number->precision = UINT64_PRECISION;
number->sign = 0;
wchar* p = buffer + UINT64_PRECISION;
while (HI32(value)) {
p = COMNumber::Int32ToDecChars(p, Int64DivMod1E9(&value), 9);
_ASSERTE(p != NULL);
}
p = COMNumber::Int32ToDecChars(p, LO32(value), 0);
_ASSERTE(p != NULL);
int i = (int) (buffer + UINT64_PRECISION - p);
number->scale = i;
wchar* dst = number->digits;
_ASSERTE(dst != NULL);
while (--i >= 0) *dst++ = *p++;
*dst = 0;
}
void RoundNumber(NUMBER* number, int pos)
{
LEAF_CONTRACT
_ASSERTE(number != NULL);
int i = 0;
while (i < pos && number->digits[i] != 0) i++;
if (i == pos && number->digits[i] >= '5') {
while (i > 0 && number->digits[i - 1] == '9') i--;
if (i > 0) {
number->digits[i - 1]++;
}
else {
number->scale++;
number->digits[0] = '1';
i = 1;
}
}
else {
while (i > 0 && number->digits[i - 1] == '0') i--;
}
if (i == 0) {
number->scale = 0;
number->sign = 0;
}
number->digits[i] = 0;
}
wchar ParseFormatSpecifier(STRINGREF str, int* digits)
{
WRAPPER_CONTRACT
_ASSERTE(digits != NULL);
if (str != 0) {
wchar* p = str->GetBuffer();
_ASSERTE(p != NULL);
wchar ch = *p;
if (ch != 0) {
if (ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z') {
p++;
int n = -1;
if (*p >= '0' && *p <= '9') {
n = *p++ - '0';
while (*p >= '0' && *p <= '9') {
n = n * 10 + *p++ - '0';
if (n >= 10) break;
}
}
if (*p == 0) {
*digits = n;
return ch;
}
}
return 0;
}
}
*digits = -1;
return 'G';
}
wchar* FormatExponent(wchar* buffer, int value, wchar expChar,
STRINGREF posSignStr, STRINGREF negSignStr, int minDigits)
{
WRAPPER_CONTRACT
_ASSERTE(buffer != NULL);
wchar digits[11];
*buffer++ = expChar;
if (value < 0) {
_ASSERTE(negSignStr != NULL);
AddStringRef(&buffer, negSignStr);
value = -value;
}
else {
if (posSignStr!= NULL) {
AddStringRef(&buffer, posSignStr);
}
}
wchar* p = COMNumber::Int32ToDecChars(digits + 10, value, minDigits);
_ASSERTE(p != NULL);
int i = (int) (digits + 10 - p);
while (--i >= 0) *buffer++ = *p++;
return buffer;
}
wchar* FormatGeneral(wchar* buffer, NUMBER* number, int digits, wchar expChar,
NUMFMTREF numfmt, BOOL bSuppressScientific = FALSE)
{
WRAPPER_CONTRACT
_ASSERTE(number != NULL);
_ASSERTE(buffer != NULL);
_ASSERTE(numfmt != NULL);
int digPos = number->scale;
int scientific = 0;
if (!bSuppressScientific) { // Don't switch to scientific notation
if (digPos > digits || digPos < -3) {
digPos = 1;
scientific = 1;
}
}
wchar* dig = number->digits;
_ASSERT(dig != NULL);
if (digPos > 0) {
do {
*buffer++ = *dig != 0? *dig++: '0';
} while (--digPos > 0);
}
else {
*buffer++ = '0';
}
if (*dig != 0 || digPos < 0) {
AddStringRef(&buffer, numfmt->sNumberDecimal);
while (digPos < 0) {
*buffer++ = '0';
digPos++;
}
while (*dig != 0) {
*buffer++ = *dig++;
}
}
if (scientific) buffer = FormatExponent(buffer, number->scale - 1, expChar, numfmt->sPositive, numfmt->sNegative, 2);
return buffer;
}
wchar* FormatScientific(wchar* buffer, NUMBER* number, int digits, wchar expChar,
NUMFMTREF numfmt)
{
WRAPPER_CONTRACT
_ASSERTE(number != NULL);
_ASSERTE(buffer != NULL);
_ASSERTE(numfmt != NULL);
wchar* dig = number->digits;
_ASSERTE(dig != NULL);
*buffer++ = *dig != 0? *dig++: '0';
if (digits != 1) // For E0 we would like to suppress the decimal point
AddStringRef(&buffer, numfmt->sNumberDecimal);
while (--digits > 0) *buffer++ = *dig != 0? *dig++: '0';
int e = number->digits[0] == 0? 0: number->scale - 1;
buffer = FormatExponent(buffer, e, expChar, numfmt->sPositive, numfmt->sNegative, 3);
_ASSERTE(buffer != NULL);
return buffer;
}
wchar* FormatFixed(wchar* buffer, NUMBER* number, int digits,
I4ARRAYREF groupDigitsRef, STRINGREF sDecimal, STRINGREF sGroup)
{
CONTRACTL {
THROWS;
INJECT_FAULT(COMPlusThrowOM());
GC_TRIGGERS;
MODE_COOPERATIVE;
PRECONDITION(CheckPointer(buffer));
PRECONDITION(CheckPointer(number));
} CONTRACTL_END;
int digPos = number->scale;
wchar* dig = number->digits;
const I4* groupDigits = NULL;
if (groupDigitsRef != NULL) {
groupDigits = groupDigitsRef->GetDirectConstPointerToNonObjectElements();
}
if (digPos > 0) {
if (groupDigits != NULL) {
int groupSizeIndex = 0; // index into the groupDigits array.
int groupSizeCount = groupDigits[groupSizeIndex]; // the current total of group size.
int groupSizeLen = groupDigitsRef->GetNumComponents(); // the length of groupDigits array.
int bufferSize = digPos; // the length of the result buffer string.
int groupSeparatorLen = sGroup->GetStringLength(); // the length of the group separator string.
int groupSize = 0; // the current group size.
//
// Find out the size of the string buffer for the result.
//
if (groupSizeLen != 0) // You can pass in 0 length arrays
{
while (digPos > groupSizeCount) {
groupSize = groupDigits[groupSizeIndex];
if (groupSize == 0) {
break;
}
bufferSize += groupSeparatorLen;
if (groupSizeIndex < groupSizeLen - 1) {
groupSizeIndex++;
}
groupSizeCount += groupDigits[groupSizeIndex];
if (groupSizeCount < 0 || bufferSize < 0) {
COMPlusThrow(kArgumentOutOfRangeException); // if we overflow
}
}
if (groupSizeCount == 0) // If you passed in an array with one entry as 0, groupSizeCount == 0
groupSize = 0;
else
groupSize = groupDigits[0];
}
groupSizeIndex = 0;
int digitCount = 0;
int digStart;
int digLength = (int)wcslen(dig);
digStart = (digPos<digLength)?digPos:digLength;
wchar* p = buffer + bufferSize - 1;
for (int i = digPos - 1; i >=0; i--) {
*(p--) = (i<digStart)?dig[i]:'0';
if (groupSize > 0) {
digitCount++;
if (digitCount == groupSize && i != 0) {
for (int j = groupSeparatorLen - 1; j >=0; j--) {
*(p--) = sGroup->GetBuffer()[j];
}
if (groupSizeIndex < groupSizeLen - 1) {
groupSizeIndex++;
groupSize = groupDigits[groupSizeIndex];
}
digitCount = 0;
}
}
}
if (p < buffer - 1) {
// This indicates a buffer underflow since we write in backwards.
DoJITFailFast();
}
buffer += bufferSize;
dig += digStart;
} else {
do {
*buffer++ = *dig != 0? *dig++: '0';
} while (--digPos > 0);
}
}
else {
*buffer++ = '0';
}
if (digits > 0) {
AddStringRef(&buffer, sDecimal);
while (digPos < 0 && digits > 0) {
*buffer++ = '0';
digPos++;
digits--;
}
while (digits > 0) {
*buffer++ = *dig != 0? *dig++: '0';
digits--;
}
}
return buffer;
}
wchar* FormatNumber(wchar* buffer, NUMBER* number, int digits, NUMFMTREF numfmt)
{
CONTRACTL {
MODE_COOPERATIVE;
THROWS;
GC_TRIGGERS;
PRECONDITION(CheckPointer(buffer));
PRECONDITION(CheckPointer(number));
} CONTRACTL_END;
char ch;
const char* fmt;
fmt = number->sign?
negNumberFormats[numfmt->cNegativeNumberFormat]:
posNumberFormat;
while ((ch = *fmt++) != 0) {
switch (ch) {
case '#':
buffer = FormatFixed(buffer, number, digits,
numfmt->cNumberGroup,
numfmt->sNumberDecimal, numfmt->sNumberGroup);
break;
case '-':
AddStringRef(&buffer, numfmt->sNegative);
break;
default:
*buffer++ = ch;
}
}
return buffer;
}
wchar* FormatCurrency(wchar* buffer, NUMBER* number, int digits, NUMFMTREF numfmt)
{
CONTRACTL {
MODE_COOPERATIVE;
THROWS;
GC_TRIGGERS;
PRECONDITION(CheckPointer(buffer));
PRECONDITION(CheckPointer(number));
} CONTRACTL_END;
char ch;
const char* fmt;
fmt = number->sign?
negCurrencyFormats[numfmt->cNegCurrencyFormat]:
posCurrencyFormats[numfmt->cPosCurrencyFormat];
while ((ch = *fmt++) != 0) {
switch (ch) {
case '#':
buffer = FormatFixed(buffer, number, digits,
numfmt->cCurrencyGroup,
numfmt->sCurrencyDecimal, numfmt->sCurrencyGroup);
break;
case '-':
AddStringRef(&buffer, numfmt->sNegative);
break;
case '$':
AddStringRef(&buffer, numfmt->sCurrency);
break;
default:
*buffer++ = ch;
}
}
return buffer;
}
wchar* FormatPercent(wchar* buffer, NUMBER* number, int digits, NUMFMTREF numfmt)
{
CONTRACTL {
MODE_COOPERATIVE;
THROWS;
GC_TRIGGERS;
PRECONDITION(CheckPointer(buffer));
PRECONDITION(CheckPointer(number));
} CONTRACTL_END;
char ch;
const char* fmt;
fmt = number->sign?
negPercentFormats[numfmt->cNegativePercentFormat]:
posPercentFormats[numfmt->cPositivePercentFormat];
while ((ch = *fmt++) != 0) {
switch (ch) {
case '#':
buffer = FormatFixed(buffer, number, digits,
numfmt->cPercentGroup,
numfmt->sPercentDecimal, numfmt->sPercentGroup);
break;
case '-':
AddStringRef(&buffer, numfmt->sNegative);
break;
case '%':
AddStringRef(&buffer, numfmt->sPercent);
break;
default:
*buffer++ = ch;
}
}
return buffer;
}
STRINGREF NumberToString(NUMBER* number, wchar format, int digits, NUMFMTREF numfmt, BOOL bDecimal = FALSE )
{
CONTRACTL {
THROWS;
INJECT_FAULT(COMPlusThrowOM());
GC_TRIGGERS;
MODE_COOPERATIVE;
PRECONDITION(CheckPointer(number));
} CONTRACTL_END;
// Do the worst case calculation
/* US English - for Double.MinValue.ToString("C99"); we require 514 characters
----------
2 paranthesis
1 currency character
308 characters
103 group seperators
1 decimal separator
99 0's
digPos + 99 + 6(slack) => digPos + 105
C
sNegative
sCurrencyGroup
sCurrencyDecimal
sCurrency
F
sNegative
sNumberDecimal
N
sNegative
sNumberDecimal
sNumberGroup
E
sNegative
sPositive
sNegative (for exponent)
sPositive
sNumberDecimal
G
sNegative
sPositive
sNegative (for exponent)
sPositive
sNumberDecimal
P (+2 for some spaces)
sNegative
sPercentGroup
sPercentDecimal
sPercent
*/
_ASSERTE(numfmt != NULL);
INT64 newBufferLen = MIN_BUFFER_SIZE;
CQuickBytesSpecifySize<LARGE_BUFFER_SIZE * sizeof(WCHAR)> buf;
wchar *buffer = NULL;
wchar* dst = NULL;
wchar ftype = format & 0xFFDF;
int digCount = 0;
switch (ftype) {
case 'C':
if (digits < 0) digits = numfmt->cCurrencyDecimals;
if (number->scale < 0)
digCount = 0;
else
digCount = number->scale + digits;
newBufferLen += digCount;
newBufferLen += numfmt->sNegative->GetStringLength(); // For number and exponent
newBufferLen += ((INT64)numfmt->sCurrencyGroup->GetStringLength() * digCount); // For all the grouping sizes
newBufferLen += numfmt->sCurrencyDecimal->GetStringLength();
newBufferLen += numfmt->sCurrency->GetStringLength();
_ASSERTE(newBufferLen >= MIN_BUFFER_SIZE);
if (newBufferLen > INT32_MAX) {
COMPlusThrowOM();
}
newBufferLen = newBufferLen * sizeof(WCHAR);
dst = buffer = (WCHAR*)buf.AllocThrows(newBufferLen);
RoundNumber(number, number->scale + digits); // Don't change this line to use digPos since digCount could have its sign changed.
dst = FormatCurrency(dst, number, digits, numfmt);
break;
case 'F':
if (digits < 0) digits = numfmt->cNumberDecimals;
if (number->scale < 0)
digCount = 0;
else
digCount = number->scale + digits;
newBufferLen += digCount;
newBufferLen += numfmt->sNegative->GetStringLength(); // For number and exponent
newBufferLen += numfmt->sNumberDecimal->GetStringLength();
_ASSERTE(newBufferLen >= MIN_BUFFER_SIZE);
if (newBufferLen > INT32_MAX) {
COMPlusThrowOM();
}
newBufferLen = newBufferLen * sizeof(WCHAR);
dst = buffer = (WCHAR*)buf.AllocThrows(newBufferLen);
RoundNumber(number, number->scale + digits);
if (number->sign) {
AddStringRef(&dst, numfmt->sNegative);
}
dst = FormatFixed(dst, number, digits,
NULL,
numfmt->sNumberDecimal, NULL);
break;
case 'N':
if (digits < 0) digits = numfmt->cNumberDecimals; // Since we are using digits in our calculation
if (number->scale < 0)
digCount = 0;
else
digCount = number->scale + digits;
newBufferLen += digCount;
newBufferLen += numfmt->sNegative->GetStringLength(); // For number and exponent
newBufferLen += ((INT64)numfmt->sNumberGroup->GetStringLength()) * digCount; // For all the grouping sizes
newBufferLen += numfmt->sNumberDecimal->GetStringLength();
_ASSERTE(newBufferLen >= MIN_BUFFER_SIZE);
if (newBufferLen > INT32_MAX) {
COMPlusThrowOM();
}
newBufferLen = newBufferLen * sizeof(WCHAR);
dst = buffer = (WCHAR*)buf.AllocThrows(newBufferLen);
RoundNumber(number, number->scale + digits);
dst = FormatNumber(dst, number, digits, numfmt);
break;
case 'E':
if (digits < 0) digits = 6;
digits++;
newBufferLen += digits;
newBufferLen += (((INT64)numfmt->sNegative->GetStringLength() + numfmt->sPositive->GetStringLength()) *2); // For number and exponent
newBufferLen += numfmt->sNumberDecimal->GetStringLength();
_ASSERTE(newBufferLen >= MIN_BUFFER_SIZE);
if (newBufferLen > INT32_MAX) {
COMPlusThrowOM();
}
newBufferLen = newBufferLen * sizeof(WCHAR);
dst = buffer = (WCHAR*)buf.AllocThrows(newBufferLen);
RoundNumber(number, digits);
if (number->sign) {
AddStringRef(&dst, numfmt->sNegative);
}
dst = FormatScientific(dst, number, digits, format, numfmt);
break;
case 'G':
{
bool enableRounding = true;
if (digits < 1) {
if (bDecimal && (digits == -1)) { // Default to 29 digits precision only for G formatting without a precision specifier
digits = DECIMAL_PRECISION;
enableRounding = false; // Turn off rounding for ECMA compliance to output trailing 0's after decimal as significant
}
else {
digits = number->precision;
}
}
newBufferLen += digits;
newBufferLen += ((numfmt->sNegative->GetStringLength() + numfmt->sPositive->GetStringLength()) *2); // For number and exponent
newBufferLen += numfmt->sNumberDecimal->GetStringLength();
_ASSERTE(newBufferLen >= MIN_BUFFER_SIZE);
if (newBufferLen > INT32_MAX) {
COMPlusThrowOM();
}
newBufferLen = newBufferLen * sizeof(WCHAR);
dst = buffer = (WCHAR*)buf.AllocThrows(newBufferLen);
if (enableRounding) // Don't round for G formatting without precision
RoundNumber(number, digits); // This also fixes up the minus zero case
else {
if (bDecimal && (number->digits[0] == 0)) { // Minus zero should be formatted as 0
number->sign = 0;
}
}
if (number->sign) {
AddStringRef(&dst, numfmt->sNegative);
}
dst = FormatGeneral(dst, number, digits, format - ('G' - 'E'), numfmt, !enableRounding);
}
break;
case 'P':
if (digits < 0) digits = numfmt->cPercentDecimals;
number->scale += 2;
if (number->scale < 0)
digCount = 0;
else
digCount = number->scale + digits;
newBufferLen += digCount;
newBufferLen += numfmt->sNegative->GetStringLength(); // For number and exponent
newBufferLen += ((INT64)numfmt->sPercentGroup->GetStringLength()) * digCount; // For all the grouping sizes
newBufferLen += numfmt->sPercentDecimal->GetStringLength();
newBufferLen += numfmt->sPercent->GetStringLength();
_ASSERTE(newBufferLen >= MIN_BUFFER_SIZE);
if (newBufferLen > INT32_MAX) {
COMPlusThrowOM();
}
newBufferLen = newBufferLen * sizeof(WCHAR);
dst = buffer = (WCHAR*)buf.AllocThrows(newBufferLen);
RoundNumber(number, number->scale + digits);
dst = FormatPercent(dst, number, digits, numfmt);
break;
default:
COMPlusThrow(kFormatException, L"Format_BadFormatSpecifier");
}
if (!((dst - buffer >= 0) && (dst - buffer) <= newBufferLen)) {
DoJITFailFast();
}
return COMString::NewString(buffer, (int) (dst - buffer));
}
LPCWSTR FindSection(LPCWSTR format, int section)
{
LEAF_CONTRACT
_ASSERTE(format != NULL);
LPCWSTR src;
wchar ch;
if (section == 0) return format;
src = format;
for (;;) {
switch (ch = *src++) {
case '\'':
case '"':
while (*src != 0 && *src++ != ch);
break;
case '\\':
if (*src != 0) src++;
break;
case ';':
if (--section != 0) break;
if (*src != 0 && *src != ';') return src;
case 0:
return format;
}
}
}
STRINGREF NumberToStringFormat(NUMBER* number, STRINGREF str, NUMFMTREF numfmt)
{
CONTRACTL {
THROWS;
INJECT_FAULT(COMPlusThrowOM());
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
int digitCount;
int decimalPos;
int firstDigit;
int lastDigit;
int digPos;
int scientific;
int percent;
int permille;
int thousandPos;
int thousandCount = 0;
int thousandSeps;
int scaleAdjust;
int adjust;
wchar* format=NULL;
LPCWSTR section=NULL;
LPCWSTR src=NULL;
wchar* dst=NULL;
wchar* dig=NULL;
wchar ch;
wchar* buffer=NULL;
CQuickBytes buf;
_ASSERTE(str != NULL);
_ASSERTE(numfmt != NULL);
format = str->GetBuffer();
section = FindSection(format, number->digits[0] == 0? 2: number->sign? 1: 0);
ParseSection:
digitCount = 0;
decimalPos = -1;
firstDigit = 0x7FFFFFFF;
lastDigit = 0;
scientific = 0;
percent = 0;
permille = 0;
thousandPos = -1;
thousandSeps = 0;
scaleAdjust = 0;
src = section;
_ASSERTE(src != NULL);
while ((ch = *src++) != 0 && ch != ';') {
switch (ch) {
case '#':
digitCount++;
break;
case '0':
if (firstDigit == 0x7FFFFFFF) firstDigit = digitCount;
digitCount++;
lastDigit = digitCount;
break;
case '.':
if (decimalPos < 0) {
decimalPos = digitCount;
}
break;
case ',':
if (digitCount > 0 && decimalPos < 0) {
if (thousandPos >= 0) {
if (thousandPos == digitCount) {
thousandCount++;
break;
}
thousandSeps = 1;
}
thousandPos = digitCount;
thousandCount = 1;
}
break;
case '%':
percent++;
scaleAdjust += 2;
break;
case 0x2030:
permille++;
scaleAdjust += 3;
break;
case '\'':
case '"':
while (*src != 0 && *src++ != ch);
break;
case '\\':
if (*src != 0) src++;
break;
case 'E':
case 'e':
if (*src=='0' || ((*src == '+' || *src == '-') && src[1] == '0')) {
while (*++src == '0');
scientific = 1;
}
break;
}
}
if (decimalPos < 0) decimalPos = digitCount;
if (thousandPos >= 0) {
if (thousandPos == decimalPos) {
scaleAdjust -= thousandCount * 3;
}
else {
thousandSeps = 1;
}
}
if (number->digits[0] != 0) {
number->scale += scaleAdjust;
int pos = scientific? digitCount: number->scale + digitCount - decimalPos;
RoundNumber(number, pos);
if (number->digits[0] == 0) {
src = FindSection(format, 2);
if (src != section) {
section = src;
goto ParseSection;
}
}
} else {
number->sign = 0; // We need to format -0 without the sign set.
number->scale = 0; // Decimals with scale ('0.00') should be rounded.
}
firstDigit = firstDigit < decimalPos? decimalPos - firstDigit: 0;
lastDigit = lastDigit > decimalPos? decimalPos - lastDigit: 0;
if (scientific) {
digPos = decimalPos;
adjust = 0;
}
else {
digPos = number->scale > decimalPos? number->scale: decimalPos;
adjust = number->scale - decimalPos;
}
src = section;
dig = number->digits;
// Find maximum number of characters that the destination string can grow by
// in the following while loop. Use this to avoid buffer overflows.
// Longest strings are potentially +/- signs with 10 digit exponents,
// or decimal numbers, or the while loops copying from a quote or a \ onwards.
// Check for positive and negative
UINT64 maxStrIncLen = 0; // We need this to be UINT64 since the percent computation could go beyond a UINT.
if (number->sign) {
maxStrIncLen = numfmt->sNegative->GetStringLength();
}
else {
maxStrIncLen = numfmt->sPositive->GetStringLength();
}
// Add for any big decimal seperator
maxStrIncLen += numfmt->sNumberDecimal->GetStringLength();
// Add for scientific
if (scientific) {
int inc1 = numfmt->sPositive->GetStringLength();
int inc2 = numfmt->sNegative->GetStringLength();
maxStrIncLen +=(inc1>inc2)?inc1:inc2;
}
// Add for percent separator
if (percent) {
maxStrIncLen += ((INT64)numfmt->sPercent->GetStringLength()) * percent;
}
// Add for permilli separator
if (permille) {
maxStrIncLen += ((INT64)numfmt->sPerMille->GetStringLength()) * permille;
}
//adjust can be negative, so we make this an int instead of an unsigned int.
// adjust represents the number of characters over the formatting eg. format string is "0000" and you are trying to
// format 100000 (6 digits). Means adjust will be 2. On the other hand if you are trying to format 10 adjust will be
// -2 and we'll need to fixup these digits with 0 padding if we have 0 formatting as in this example.
INT64 adjustLen=(adjust>0)?adjust:0; // We need to add space for these extra characters anyway.
CQuickBytes thousands;
INT32 bufferLen2 = 125;
INT32 *thousandsSepPos = NULL;
INT32 thousandsSepCtr = -1;
if (thousandSeps) { // Fixup possible buffer overrun problems
// We need to precompute this outside the number formatting loop
if(numfmt->cNumberGroup->GetNumComponents() == 0) {
thousandSeps = 0; // Nothing to add
}
else {
thousandsSepPos = (INT32 *)thousands.AllocThrows(bufferLen2 * sizeof(INT32));
// - We need this array to figure out where to insert the thousands seperator. We would have to traverse the string
// backwords. PIC formatting always traverses forwards. These indices are precomputed to tell us where to insert
// the thousands seperator so we can get away with traversing forwards. Note we only have to compute upto digPos.
// The max is not bound since you can have formatting strings of the form "000,000..", and this
// should handle that case too.
const I4* groupDigits = numfmt->cNumberGroup->GetDirectConstPointerToNonObjectElements();
_ASSERTE(groupDigits != NULL);
int groupSizeIndex = 0; // index into the groupDigits array.
INT64 groupTotalSizeCount = 0;
int groupSizeLen = numfmt->cNumberGroup->GetNumComponents(); // the length of groupDigits array.
if (groupSizeLen != 0)
groupTotalSizeCount = groupDigits[groupSizeIndex]; // the current running total of group size.
int groupSize = groupTotalSizeCount;
int totalDigits = digPos + ((adjust < 0)?adjust:0); // actual number of digits in o/p
int numDigits = (firstDigit > totalDigits) ? firstDigit : totalDigits;
while (numDigits > groupTotalSizeCount) {
if (groupSize == 0)
break;
thousandsSepPos[++thousandsSepCtr] = groupTotalSizeCount;
if (groupSizeIndex < groupSizeLen - 1) {
groupSizeIndex++;
groupSize = groupDigits[groupSizeIndex];
}
groupTotalSizeCount += groupSize;
if (bufferLen2 - thousandsSepCtr < 10) { // Slack of 10
bufferLen2 *= 2;
thousands.ReSizeThrows(bufferLen2*sizeof(INT32)); // memcopied by CQuickBytes automatically
thousandsSepPos = (INT32 *)thousands.Ptr();
}
}
// We already have computed the number of separators above. Simply add space for them.
adjustLen += ( (thousandsSepCtr + 1) * ((INT64)numfmt->sNumberGroup->GetStringLength()));
}
}
maxStrIncLen += adjustLen;
// Allocate temp buffer - gotta deal with Schertz' 500 MB strings.
// Some computations like when you specify Int32.MaxValue-2 %'s and each percent is setup to be Int32.MaxValue in length
// will generate a result that will be larget than an unsigned int can hold. This is to protect against overflow.
UINT64 tempLen = str->GetStringLength() + maxStrIncLen + 10; // Include a healthy amount of temp space.
if (tempLen > 0x7FFFFFFF)
COMPlusThrowOM(); // if we overflow
unsigned int bufferLen = (UINT)tempLen;
if (bufferLen < 250) // Stay under 512 bytes
bufferLen = 250; // This is to prevent unneccessary calls to resize
buffer = (wchar *) buf.AllocThrows(bufferLen* sizeof(WCHAR));
dst = buffer;
if (number->sign && section == format) {
AddStringRef(&dst, numfmt->sNegative);
}
BOOL decimalWritten = FALSE;
while ((ch = *src++) != 0 && ch != ';') {
// Make sure temp buffer is big enough, else resize it.
if (bufferLen - (unsigned int)(dst-buffer) < 10) {
int offset = dst - buffer;
bufferLen *= 2;
buf.ReSizeThrows(bufferLen*sizeof(WCHAR));
buffer = (wchar*)buf.Ptr(); // memcopied by QuickBytes automatically
dst = buffer + offset;
}
if (adjust > 0) {
switch (ch) {
case '#':
case '0':
case '.':
while (adjust > 0) { // digPos will be one greater than thousandsSepPos[thousandsSepCtr] since we are at
// the character after which the groupSeparator needs to be appended.
*dst++ = *dig != 0? *dig++: '0';
if (thousandSeps && digPos > 1 && thousandsSepCtr>=0) {
if (digPos == thousandsSepPos[thousandsSepCtr] + 1) {
AddStringRef(&dst, numfmt->sNumberGroup);
thousandsSepCtr--;
}
}
digPos--;
adjust--;
}
}
}
switch (ch) {
case '#':
case '0':
{
if (adjust < 0) {
adjust++;
ch = digPos <= firstDigit? '0': 0;
}
else {
ch = *dig != 0? *dig++: digPos > lastDigit? '0': 0;
}
if (ch != 0) {
*dst++ = ch;
if (thousandSeps && digPos > 1 && thousandsSepCtr>=0) {
if (digPos == thousandsSepPos[thousandsSepCtr] + 1) {
AddStringRef(&dst, numfmt->sNumberGroup);
thousandsSepCtr--;
}
}
}
digPos--;
break;
}
case '.':
{
if (digPos != 0 || decimalWritten) {
// For compatability, don't echo repeated decimals
break;
}
// If the format has trailing zeros or the format has a decimal and digits remain
if (lastDigit < 0
|| (decimalPos < digitCount && *dig != 0)) {
AddStringRef(&dst, numfmt->sNumberDecimal);
decimalWritten = TRUE;
}
break;
}
case 0x2030:
AddStringRef(&dst, numfmt->sPerMille);
break;
case '%':
AddStringRef(&dst, numfmt->sPercent);
break;
case ',':
break;
case '\'':
case '"':
// Buffer overflow possibility
while (*src != 0 && *src != ch) {
*dst++ = *src++;
if ((unsigned int)(dst-buffer) == bufferLen-1) {
if (bufferLen - (unsigned int)(dst-buffer) < maxStrIncLen) {
int offset = dst - buffer;
bufferLen *= 2;
buf.ReSizeThrows(bufferLen*sizeof(WCHAR)); // memcopied by CQuickBytes automatically
buffer = (wchar *)buf.Ptr();
dst = buffer + offset;
}
}
}
if (*src != 0) src++;
break;
case '\\':
if (*src != 0) *dst++ = *src++;
break;
case 'E':
case 'e':
{
STRINGREF sign = NULL;
int i = 0;
if (scientific) {
if (*src=='0') {
//Handles E0, which should format the same as E-0
i++;
} else if (*src == '+' && src[1] == '0') {
//Handles E+0
sign = numfmt->sPositive;
} else if (*src == '-' && src[1] == '0') {
//Handles E-0
//Do nothing, this is just a place holder s.t. we don't break out of the loop.
} else {
*dst++ = ch;
break;
}
while (*++src == '0') i++;
if (i > 10) i = 10;
int exp = number->digits[0] == 0? 0: number->scale - decimalPos;
dst = FormatExponent(dst, exp, ch, sign, numfmt->sNegative, i);
scientific = 0;
}
else
{
*dst++ = ch; // Copy E or e to output
if (*src== '+' || *src == '-') {
*dst++ = *src++;
}
while (*src == '0') {
*dst++ = *src++;
}
}
break;
}
default:
*dst++ = ch;
}
}
if (!((dst - buffer >= 0) && (dst - buffer <= (int)bufferLen))) {
DoJITFailFast();
}
STRINGREF newStr = COMString::NewString(buffer, (int)(dst - buffer));
return newStr;
}
FCIMPL3_VII(Object*, COMNumber::FormatDecimal, DECIMAL value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE)
{
CONTRACTL
{
MODE_COOPERATIVE;
DISABLED(GC_TRIGGERS); // can't use this in an FCALL because we're in forbid gc mode until we setup a H_M_F.
THROWS;
SO_TOLERANT;
}
CONTRACTL_END;
NUMBER number;
wchar fmt;
int digits;
STRINGREF refRetVal = NULL;
HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_1(Frame::FRAME_ATTR_RETURNOBJ, refRetVal);
struct _gc
{
STRINGREF format;
NUMFMTREF numfmt;
} gc;
gc.format = (STRINGREF) formatUNSAFE;
gc.numfmt = (NUMFMTREF) numfmtUNSAFE;
if (gc.numfmt == 0)
COMPlusThrowArgumentNull(L"NumberFormatInfo");
COMDecimal::DecimalToNumber(&value, &number);
fmt = ParseFormatSpecifier(gc.format, &digits);
if (fmt != 0) {
refRetVal = NumberToString(&number, fmt, digits, gc.numfmt, TRUE);
} else {
refRetVal = NumberToStringFormat(&number, gc.format, gc.numfmt);
}
HELPER_METHOD_FRAME_END();
return OBJECTREFToObject(refRetVal);
}
FCIMPLEND
FCIMPL3_VII(Object*, COMNumber::FormatDouble, double value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE)
{
CONTRACTL
{
MODE_COOPERATIVE;
DISABLED(GC_TRIGGERS); // can't use this in an FCALL because we're in forbid gc mode until we setup a H_M_F.
THROWS;
SO_TOLERANT;
}
CONTRACTL_END;
NUMBER number;
int digits;
double dTest;
struct _gc
{
STRINGREF format;
NUMFMTREF numfmt;
STRINGREF refRetVal;
} gc;
gc.format = (STRINGREF) formatUNSAFE;
gc.numfmt = (NUMFMTREF) numfmtUNSAFE;
gc.refRetVal = NULL;
HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_PROTECT(Frame::FRAME_ATTR_RETURNOBJ, gc);
if (gc.numfmt == 0) COMPlusThrowArgumentNull(L"NumberFormatInfo");
wchar fmt = ParseFormatSpecifier(gc.format, &digits);
wchar val = (fmt & 0xFFDF);
int precision = DOUBLE_PRECISION;
switch (val) {
case 'R':
//In order to give numbers that are both friendly to display and round-trippable,
//we parse the number using 15 digits and then determine if it round trips to the same
//value. If it does, we convert that NUMBER to a string, otherwise we reparse using 17 digits
//and display that.
DoubleToNumber(value, DOUBLE_PRECISION, &number);
if (number.scale == (int) SCALE_NAN) {
gc.refRetVal = gc.numfmt->sNaN;
goto lExit;
}
if (number.scale == SCALE_INF) {
gc.refRetVal = (number.sign? gc.numfmt->sNegativeInfinity: gc.numfmt->sPositiveInfinity);
goto lExit;
}
NumberToDouble(&number, &dTest);
if (dTest == value) {
gc.refRetVal = NumberToString(&number, 'G', DOUBLE_PRECISION, gc.numfmt);
goto lExit;
}
DoubleToNumber(value, 17, &number);
gc.refRetVal = NumberToString(&number, 'G', 17, gc.numfmt);
goto lExit;
break;
case 'E':
// Here we round values less than E14 to 15 digits
if (digits > 14) {
precision = 17;
}
break;
case 'G':
// Here we round values less than G15 to 15 digits, G16 and G17 will not be touched
if (digits > 15) {
precision = 17;
}
break;
}
DoubleToNumber(value, precision, &number);
if (number.scale == (int) SCALE_NAN) {
gc.refRetVal = gc.numfmt->sNaN;
goto lExit;
}
if (number.scale == SCALE_INF) {
gc.refRetVal = (number.sign? gc.numfmt->sNegativeInfinity: gc.numfmt->sPositiveInfinity);
goto lExit;
}
if (fmt != 0) {
gc.refRetVal = NumberToString( &number, fmt, digits, gc.numfmt);
}
else {
gc.refRetVal = NumberToStringFormat( &number, gc.format, gc.numfmt);
}
lExit: ;
HELPER_METHOD_FRAME_END();
return OBJECTREFToObject(gc.refRetVal);
}
FCIMPLEND
//This function and the function pointer which we use to access are a really
//nasty hack to prevent VC7 from optimizing away our cast from double to float.
//We need this narrowing operation to verify whether or not we successfully round-tripped
//the single value.
static void CvtToFloat(double val, volatile float* fltPtr)
{
LEAF_CONTRACT;
STATIC_CONTRACT_SO_TOLERANT;
*fltPtr = (float)val;
}
void (*CvtToFloatPtr)(double val, volatile float* fltPtr) = CvtToFloat;
FCIMPL3_VII(Object*, COMNumber::FormatSingle, float value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE)
{
CONTRACTL
{
MODE_COOPERATIVE;
DISABLED(GC_TRIGGERS); // can't use this in an FCALL because we're in forbid gc mode until we setup a H_M_F.
THROWS;
SO_TOLERANT;
}
CONTRACTL_END;
NUMBER number;
int digits;
double dTest;
double argsValue = value;
struct _gc
{
STRINGREF format;
NUMFMTREF numfmt;
STRINGREF refRetVal;
} gc;
gc.format = (STRINGREF) formatUNSAFE;
gc.numfmt = (NUMFMTREF) numfmtUNSAFE;
gc.refRetVal = NULL;
HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_PROTECT(Frame::FRAME_ATTR_RETURNOBJ, gc);
if (gc.numfmt == 0) COMPlusThrowArgumentNull(L"NumberFormatInfo");
wchar fmt = ParseFormatSpecifier(gc.format, &digits);
wchar val = fmt & 0xFFDF;
int precision = FLOAT_PRECISION;
switch (val) {
case 'R':
//In order to give numbers that are both friendly to display and round-trippable,
//we parse the number using 7 digits and then determine if it round trips to the same
//value. If it does, we convert that NUMBER to a string, otherwise we reparse using 9 digits
//and display that.
DoubleToNumber(argsValue, FLOAT_PRECISION, &number);
if (number.scale == (int) SCALE_NAN) {
gc.refRetVal = gc.numfmt->sNaN;
goto lExit;
}
if (number.scale == SCALE_INF) {
gc.refRetVal = (number.sign? gc.numfmt->sNegativeInfinity: gc.numfmt->sPositiveInfinity);
goto lExit;
}
NumberToDouble(&number, &dTest);
volatile float fTest;
(*CvtToFloatPtr)(dTest, &fTest);
if (fTest == value) {
gc.refRetVal = NumberToString(&number, 'G', FLOAT_PRECISION, gc.numfmt);
goto lExit;
}
DoubleToNumber(argsValue, 9, &number);
gc.refRetVal = NumberToString(&number, 'G', 9, gc.numfmt);
goto lExit;
break;
case 'E':
// Here we round values less than E14 to 15 digits
if (digits > 6) {
precision = 9;
}
break;
case 'G':
// Here we round values less than G15 to 15 digits, G16 and G17 will not be touched
if (digits > 7) {
precision = 9;
}
break;
}
DoubleToNumber(value, precision, &number);
if (number.scale == (int) SCALE_NAN) {
gc.refRetVal = gc.numfmt->sNaN;
goto lExit;
}
if (number.scale == SCALE_INF) {
gc.refRetVal = (number.sign? gc.numfmt->sNegativeInfinity: gc.numfmt->sPositiveInfinity);
goto lExit;
}
if (fmt != 0) {
gc.refRetVal = NumberToString( &number, fmt, digits, gc.numfmt);
}
else {
gc.refRetVal = NumberToStringFormat( &number, gc.format, gc.numfmt);
}
lExit: ;
HELPER_METHOD_FRAME_END();
return OBJECTREFToObject(gc.refRetVal);
}
FCIMPLEND
FCIMPL3(Object*, COMNumber::FormatInt32, INT32 value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE)
{
CONTRACTL
{
MODE_COOPERATIVE;
DISABLED(GC_TRIGGERS); // can't use this in an FCALL because we're in forbid gc mode until we setup a H_M_F.
THROWS;
SO_TOLERANT;
}
CONTRACTL_END;
wchar fmt;
int digits;
struct _gc
{
STRINGREF refFormat;
NUMFMTREF refNumFmt;
STRINGREF refRetString;
} gc;
gc.refFormat = (STRINGREF)formatUNSAFE;
gc.refNumFmt = (NUMFMTREF)numfmtUNSAFE;
gc.refRetString = NULL;
HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_PROTECT(Frame::FRAME_ATTR_RETURNOBJ, gc);
if (gc.refNumFmt == 0) COMPlusThrowArgumentNull(L"NumberFormatInfo");
fmt = ParseFormatSpecifier(gc.refFormat, &digits);
//ANDing fmt with FFDF has the effect of uppercasing the character because
//we've removed the bit that marks lower-case.
switch (fmt & 0xFFDF) {
case 'G':
if (digits > 0)
{
NUMBER number;
Int32ToNumber(value, &number);
if (fmt != 0) {
gc.refRetString = NumberToString(&number, fmt, digits, gc.refNumFmt);
break;
}
gc.refRetString = NumberToStringFormat(&number, gc.refFormat, gc.refNumFmt);
break;
}
// fall through
case 'D':
gc.refRetString = Int32ToDecStr(value, digits, gc.refNumFmt->sNegative);
break;
case 'X':
gc.refRetString = Int32ToHexStr(value, fmt - ('X' - 'A' + 10), digits);
break;
default:
NUMBER number;
Int32ToNumber(value, &number);
if (fmt != 0) {
gc.refRetString = NumberToString(&number, fmt, digits, gc.refNumFmt);
break;
}
gc.refRetString = NumberToStringFormat(&number, gc.refFormat, gc.refNumFmt);
break;
}
HELPER_METHOD_FRAME_END();
return OBJECTREFToObject(gc.refRetString);
}
FCIMPLEND
FCIMPL3(Object*, COMNumber::FormatUInt32, UINT32 value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE)
{
CONTRACTL
{
MODE_COOPERATIVE;
DISABLED(GC_TRIGGERS); // can't use this in an FCALL because we're in forbid gc mode until we setup a H_M_F.
THROWS;
SO_TOLERANT;
}
CONTRACTL_END;
wchar fmt;
int digits;
struct _gc
{
STRINGREF refFormat;
NUMFMTREF refNumFmt;
STRINGREF refRetString;
} gc;
gc.refFormat = (STRINGREF)formatUNSAFE;
gc.refNumFmt = (NUMFMTREF)numfmtUNSAFE;
gc.refRetString = NULL;
HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_PROTECT(Frame::FRAME_ATTR_RETURNOBJ, gc);
if (gc.refNumFmt == 0) COMPlusThrowArgumentNull(L"NumberFormatInfo");
fmt = ParseFormatSpecifier(gc.refFormat, &digits);
switch (fmt & 0xFFDF)
{
case 'G':
if (digits > 0)
{
NUMBER number;
UInt32ToNumber(value, &number);
if (fmt != 0) {
gc.refRetString = NumberToString(&number, fmt, digits, gc.refNumFmt);
break;
}
gc.refRetString = NumberToStringFormat(&number, gc.refFormat, gc.refNumFmt);
break;
}
// fall through
case 'D':
gc.refRetString = UInt32ToDecStr(value, digits);
break;
case 'X':
gc.refRetString = Int32ToHexStr(value, fmt - ('X' - 'A' + 10), digits);
break;
default:
NUMBER number;
UInt32ToNumber(value, &number);
if (fmt != 0) {
gc.refRetString = NumberToString(&number, fmt, digits, gc.refNumFmt);
break;
}
gc.refRetString = NumberToStringFormat(&number, gc.refFormat, gc.refNumFmt);
break;
}
HELPER_METHOD_FRAME_END();
return OBJECTREFToObject(gc.refRetString);
}
FCIMPLEND
FCIMPL3_VII(Object*, COMNumber::FormatInt64, INT64 value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE)
{
CONTRACTL
{
MODE_COOPERATIVE;
DISABLED(GC_TRIGGERS); // can't use this in an FCALL because we're in forbid gc mode until we setup a H_M_F.
THROWS;
SO_TOLERANT;
}
CONTRACTL_END;
wchar fmt;
int digits;
struct _gc
{
STRINGREF refFormat;
NUMFMTREF refNumFmt;
STRINGREF refRetString;
} gc;
gc.refFormat = ObjectToSTRINGREF(formatUNSAFE);
gc.refNumFmt = (NUMFMTREF)numfmtUNSAFE;
gc.refRetString = NULL;
HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_PROTECT(Frame::FRAME_ATTR_RETURNOBJ, gc);
if (gc.refNumFmt == 0) COMPlusThrowArgumentNull(L"NumberFormatInfo");
fmt = ParseFormatSpecifier(gc.refFormat, &digits);
switch (fmt & 0xFFDF)
{
case 'G':
if (digits > 0)
{
NUMBER number;
Int64ToNumber(value, &number);
if (fmt != 0) {
gc.refRetString = NumberToString(&number, fmt, digits, gc.refNumFmt);
break;
}
gc.refRetString = NumberToStringFormat(&number, gc.refFormat, gc.refNumFmt);
break;
}
// fall through
case 'D':
gc.refRetString = Int64ToDecStr(value, digits, gc.refNumFmt->sNegative);
break;
case 'X':
gc.refRetString = Int64ToHexStr(value, fmt - ('X' - 'A' + 10), digits);
break;
default:
NUMBER number;
Int64ToNumber(value, &number);
if (fmt != 0) {
gc.refRetString = NumberToString(&number, fmt, digits, gc.refNumFmt);
break;
}
gc.refRetString = NumberToStringFormat(&number, gc.refFormat, gc.refNumFmt);
break;
}
HELPER_METHOD_FRAME_END();
return OBJECTREFToObject(gc.refRetString);
}
FCIMPLEND
FCIMPL3_VII(Object*, COMNumber::FormatUInt64, UINT64 value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE)
{
CONTRACTL
{
MODE_COOPERATIVE;
DISABLED(GC_TRIGGERS); // can't use this in an FCALL because we're in forbid gc mode until we setup a H_M_F.
THROWS;
SO_TOLERANT;
}
CONTRACTL_END;
wchar fmt;
int digits;
struct _gc
{
STRINGREF refFormat;
NUMFMTREF refNumFmt;
STRINGREF refRetString;
} gc;
gc.refFormat = ObjectToSTRINGREF(formatUNSAFE);
gc.refNumFmt = (NUMFMTREF)numfmtUNSAFE;
gc.refRetString = NULL;
HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_PROTECT(Frame::FRAME_ATTR_RETURNOBJ, gc);
if (gc.refNumFmt == 0) COMPlusThrowArgumentNull(L"NumberFormatInfo");
fmt = ParseFormatSpecifier(gc.refFormat, &digits);
switch (fmt & 0xFFDF) {
case 'G':
if (digits > 0)
{
NUMBER number;
UInt64ToNumber(value, &number);
if (fmt != 0) {
gc.refRetString = NumberToString(&number, fmt, digits, gc.refNumFmt);
break;
}
gc.refRetString = NumberToStringFormat(&number, gc.refFormat, gc.refNumFmt);
break;
}
// fall through
case 'D':
gc.refRetString = UInt64ToDecStr(value, digits);
break;
case 'X':
gc.refRetString = Int64ToHexStr(value, fmt - ('X' - 'A' + 10), digits);
break;
default:
NUMBER number;
UInt64ToNumber(value, &number);
if (fmt != 0) {
gc.refRetString = NumberToString(&number, fmt, digits, gc.refNumFmt);
break;
}
gc.refRetString = NumberToStringFormat(&number, gc.refFormat, gc.refNumFmt);
break;
}
HELPER_METHOD_FRAME_END();
return OBJECTREFToObject(gc.refRetString);
}
FCIMPLEND
FCIMPL2(FC_BOOL_RET, COMNumber::NumberBufferToDecimal, BYTE* number, DECIMAL* value)
{
WRAPPER_CONTRACT;
STATIC_CONTRACT_SO_TOLERANT;
FC_RETURN_BOOL(COMDecimal::NumberToDecimal((NUMBER *) number, value) != 0);
}
FCIMPLEND
FCIMPL2(FC_BOOL_RET, COMNumber::NumberBufferToDouble, BYTE* number, double* value)
{
WRAPPER_CONTRACT;
STATIC_CONTRACT_SO_TOLERANT;
double d = 0;
NumberToDouble((NUMBER*) number, &d);
unsigned int e = ((FPDOUBLE*)&d)->exp;
unsigned int fmntLow = ((FPDOUBLE*)&d)->mantLo;
unsigned int fmntHigh = ((FPDOUBLE*)&d)->mantHi;
if (e == 0x7FF) {
FC_RETURN_BOOL(false);
}
if (e == 0 && fmntLow ==0 && fmntHigh == 0) {
d = 0;
}
*value = d;
FC_RETURN_BOOL(true);
}
FCIMPLEND