/* The algorithm for this particular function can be found in:
 *
 *      Printing Floating-Point Numbers Quickly and Accurately, Robert
 *      G. Burger, and R. Kent Dybvig, Programming Language Design and
 *      Implementation (PLDI) 1996, pp.108-116.
 *
 * The previous implementation of this function combined m+ and m- into
 * one single M which caused some inaccuracy of the last digit. The
 * particular case below shows this inaccuracy:
 *
 *       System.out.println(new Double((1.234123412431233E107)).toString());
 *       System.out.println(new Double((1.2341234124312331E107)).toString());
 *       System.out.println(new Double((1.2341234124312332E107)).toString());
 *
 *       outputs the following:
 *
 *           1.234123412431233E107
 *           1.234123412431233E107
 *           1.234123412431233E107
 *
 *       instead of:
 *
 *           1.234123412431233E107
 *           1.2341234124312331E107
 *           1.2341234124312331E107
 * 
 */
JNIEXPORT void JNICALL
Java_org_apache_harmony_luni_util_NumberConverter_bigIntDigitGeneratorInstImpl (JNIEnv *
                                                                    env,
                                                                    jobject
                                                                    inst,
                                                                    jlong f,
                                                                    jint e,
                                                                    jboolean
                                                                    isDenormalized,
                                                                    jboolean
                                                                    mantissaIsZero,
                                                                    jint p)
{
  int RLength, SLength, TempLength, mplus_Length, mminus_Length;
  int high, low, i;
  jint k, firstK, U;
  jint getCount, setCount;
  jint *uArray;

  jclass clazz;
  jfieldID fid;
  jintArray uArrayObject;

  U_64 R[RM_SIZE], S[STemp_SIZE], mplus[RM_SIZE], mminus[RM_SIZE],
    Temp[STemp_SIZE];

  memset (R     , 0, RM_SIZE    * sizeof (U_64));
  memset (S     , 0, STemp_SIZE * sizeof (U_64));
  memset (mplus , 0, RM_SIZE    * sizeof (U_64));
  memset (mminus, 0, RM_SIZE    * sizeof (U_64));
  memset (Temp  , 0, STemp_SIZE * sizeof (U_64));

  if (e >= 0)
    {
      *R = f;
      *mplus = *mminus = 1;
      simpleShiftLeftHighPrecision (mminus, RM_SIZE, e);
      if (f != (2 << (p - 1)))
        {
          simpleShiftLeftHighPrecision (R, RM_SIZE, e + 1);
          *S = 2;
          /*
           * m+ = m+ << e results in 1.0e23 to be printed as
           * 0.9999999999999999E23
           * m+ = m+ << e+1 results in 1.0e23 to be printed as
           * 1.0e23 (caused too much rounding)
           *      470fffffffffffff = 2.0769187434139308E34
           *      4710000000000000 = 2.076918743413931E34
           */
          simpleShiftLeftHighPrecision (mplus, RM_SIZE, e);
        }
      else
        {
          simpleShiftLeftHighPrecision (R, RM_SIZE, e + 2);
          *S = 4;
          simpleShiftLeftHighPrecision (mplus, RM_SIZE, e + 1);
        }
    }
  else
    {
      if (isDenormalized || (f != (2 << (p - 1))))
        {
          *R = f << 1;
          *S = 1;
          simpleShiftLeftHighPrecision (S, STemp_SIZE, 1 - e);
          *mplus = *mminus = 1;
        }
      else
        {
          *R = f << 2;
          *S = 1;
          simpleShiftLeftHighPrecision (S, STemp_SIZE, 2 - e);
          *mplus = 2;
          *mminus = 1;
        }
    }

  k = (int) ceil ((e + p - 1) * INV_LOG_OF_TEN_BASE_2 - 1e-10);

  if (k > 0)
    {
      timesTenToTheEHighPrecision (S, STemp_SIZE, k);
    }
  else
    {
      timesTenToTheEHighPrecision (R     , RM_SIZE, -k);
      timesTenToTheEHighPrecision (mplus , RM_SIZE, -k);
      timesTenToTheEHighPrecision (mminus, RM_SIZE, -k);
    }

  RLength = mplus_Length = mminus_Length = RM_SIZE;
  SLength = TempLength = STemp_SIZE;

  memset (Temp + RM_SIZE, 0, (STemp_SIZE - RM_SIZE) * sizeof (U_64));
  memcpy (Temp, R, RM_SIZE * sizeof (U_64));

  while (RLength > 1 && R[RLength - 1] == 0)
    --RLength;
  while (mplus_Length > 1 && mplus[mplus_Length - 1] == 0)
    --mplus_Length;
  while (mminus_Length > 1 && mminus[mminus_Length - 1] == 0)
    --mminus_Length;
  while (SLength > 1 && S[SLength - 1] == 0)
    --SLength;
  TempLength = (RLength > mplus_Length ? RLength : mplus_Length) + 1;
  addHighPrecision (Temp, TempLength, mplus, mplus_Length);

  if (compareHighPrecision (Temp, TempLength, S, SLength) >= 0)
    {
      firstK = k;
    }
  else
    {
      firstK = k - 1;
      simpleAppendDecimalDigitHighPrecision (R     , ++RLength      , 0);
      simpleAppendDecimalDigitHighPrecision (mplus , ++mplus_Length , 0);
      simpleAppendDecimalDigitHighPrecision (mminus, ++mminus_Length, 0);
      while (RLength > 1 && R[RLength - 1] == 0)
        --RLength;
      while (mplus_Length > 1 && mplus[mplus_Length - 1] == 0)
        --mplus_Length;
      while (mminus_Length > 1 && mminus[mminus_Length - 1] == 0)
        --mminus_Length;
    }

  clazz = (*env)->GetObjectClass (env, inst);
  fid = (*env)->GetFieldID (env, clazz, "uArray", "[I");
  uArrayObject = (jintArray) (*env)->GetObjectField (env, inst, fid);
  uArray = (*env)->GetIntArrayElements (env, uArrayObject, 0);

  getCount = setCount = 0;
  do
    {
      U = 0;
      for (i = 3; i >= 0; --i)
        {
          TempLength = SLength + 1;
          Temp[SLength] = 0;
          memcpy (Temp, S, SLength * sizeof (U_64));
          simpleShiftLeftHighPrecision (Temp, TempLength, i);
          if (compareHighPrecision (R, RLength, Temp, TempLength) >= 0)
            {
              subtractHighPrecision (R, RLength, Temp, TempLength);
              U += 1 << i;
            }
        }

      low = compareHighPrecision (R, RLength, mminus, mminus_Length) <= 0;

      memset (Temp + RLength, 0, (STemp_SIZE - RLength) * sizeof (U_64));
      memcpy (Temp, R, RLength * sizeof (U_64));
      TempLength = (RLength > mplus_Length ? RLength : mplus_Length) + 1;
      addHighPrecision (Temp, TempLength, mplus, mplus_Length);

      high = compareHighPrecision (Temp, TempLength, S, SLength) >= 0;

      if (low || high)
        break;

      simpleAppendDecimalDigitHighPrecision (R     , ++RLength      , 0);
      simpleAppendDecimalDigitHighPrecision (mplus , ++mplus_Length , 0);
      simpleAppendDecimalDigitHighPrecision (mminus, ++mminus_Length, 0);
      while (RLength > 1 && R[RLength - 1] == 0)
        --RLength;
      while (mplus_Length > 1 && mplus[mplus_Length - 1] == 0)
        --mplus_Length;
      while (mminus_Length > 1 && mminus[mminus_Length - 1] == 0)
        --mminus_Length;
      uArray[setCount++] = U;
    }
  while (1);

  simpleShiftLeftHighPrecision (R, ++RLength, 1);
  if (low && !high)
    uArray[setCount++] = U;
  else if (high && !low)
    uArray[setCount++] = U + 1;
  else if (compareHighPrecision (R, RLength, S, SLength) < 0)
    uArray[setCount++] = U;
  else
    uArray[setCount++] = U + 1;

  (*env)->ReleaseIntArrayElements (env, uArrayObject, uArray, 0);

  fid = (*env)->GetFieldID (env, clazz, "setCount", "I");
  (*env)->SetIntField (env, inst, fid, setCount);

  fid = (*env)->GetFieldID (env, clazz, "getCount", "I");
  (*env)->SetIntField (env, inst, fid, getCount);

  fid = (*env)->GetFieldID (env, clazz, "firstK", "I");
  (*env)->SetIntField (env, inst, fid, firstK);

}
/* The algorithm for this particular function can be found in:
 *
 *      Printing Floating-Point Numbers Quickly and Accurately, Robert
 *      G. Burger, and R. Kent Dybvig, Programming Language Design and
 *      Implementation (PLDI) 1996, pp.108-116.
 *
 * The previous implementation of this function combined m+ and m- into
 * one single M which caused some inaccuracy of the last digit. The
 * particular case below shows this inaccuracy:
 *
 *       System.out.println(new Double((1.234123412431233E107)).toString());
 *       System.out.println(new Double((1.2341234124312331E107)).toString());
 *       System.out.println(new Double((1.2341234124312332E107)).toString());
 *
 *       outputs the following:
 *
 *           1.234123412431233E107
 *           1.234123412431233E107
 *           1.234123412431233E107
 *
 *       instead of:
 *
 *           1.234123412431233E107
 *           1.2341234124312331E107
 *           1.2341234124312331E107
 *
 */
void RealToString_bigIntDigitGenerator(JNIEnv* env, jobject obj, jlong f, jint e,
        jboolean isDenormalized, jint p) {
  int RLength, SLength, TempLength, mplus_Length, mminus_Length;
  int high, low, i;
  jint k, firstK, U;

  uint64_t R[RM_SIZE], S[STemp_SIZE], mplus[RM_SIZE], mminus[RM_SIZE], Temp[STemp_SIZE];

  memset (R     , 0, RM_SIZE    * sizeof (uint64_t));
  memset (S     , 0, STemp_SIZE * sizeof (uint64_t));
  memset (mplus , 0, RM_SIZE    * sizeof (uint64_t));
  memset (mminus, 0, RM_SIZE    * sizeof (uint64_t));
  memset (Temp  , 0, STemp_SIZE * sizeof (uint64_t));

  if (e >= 0)
    {
      *R = f;
      *mplus = *mminus = 1;
      simpleShiftLeftHighPrecision (mminus, RM_SIZE, e);
      if (f != (2 << (p - 1)))
        {
          simpleShiftLeftHighPrecision (R, RM_SIZE, e + 1);
          *S = 2;
          /*
           * m+ = m+ << e results in 1.0e23 to be printed as
           * 0.9999999999999999E23
           * m+ = m+ << e+1 results in 1.0e23 to be printed as
           * 1.0e23 (caused too much rounding)
           *      470fffffffffffff = 2.0769187434139308E34
           *      4710000000000000 = 2.076918743413931E34
           */
          simpleShiftLeftHighPrecision (mplus, RM_SIZE, e);
        }
      else
        {
          simpleShiftLeftHighPrecision (R, RM_SIZE, e + 2);
          *S = 4;
          simpleShiftLeftHighPrecision (mplus, RM_SIZE, e + 1);
        }
    }
  else
    {
      if (isDenormalized || (f != (2 << (p - 1))))
        {
          *R = f << 1;
          *S = 1;
          simpleShiftLeftHighPrecision (S, STemp_SIZE, 1 - e);
          *mplus = *mminus = 1;
        }
      else
        {
          *R = f << 2;
          *S = 1;
          simpleShiftLeftHighPrecision (S, STemp_SIZE, 2 - e);
          *mplus = 2;
          *mminus = 1;
        }
    }

  k = static_cast<int>(ceil ((e + p - 1) * INV_LOG_OF_TEN_BASE_2 - 1e-10));

  if (k > 0)
    {
      timesTenToTheEHighPrecision (S, STemp_SIZE, k);
    }
  else
    {
      timesTenToTheEHighPrecision (R     , RM_SIZE, -k);
      timesTenToTheEHighPrecision (mplus , RM_SIZE, -k);
      timesTenToTheEHighPrecision (mminus, RM_SIZE, -k);
    }

  RLength = mplus_Length = mminus_Length = RM_SIZE;
  SLength = TempLength = STemp_SIZE;

  memset (Temp + RM_SIZE, 0, (STemp_SIZE - RM_SIZE) * sizeof (uint64_t));
  memcpy (Temp, R, RM_SIZE * sizeof (uint64_t));

  while (RLength > 1 && R[RLength - 1] == 0)
    --RLength;
  while (mplus_Length > 1 && mplus[mplus_Length - 1] == 0)
    --mplus_Length;
  while (mminus_Length > 1 && mminus[mminus_Length - 1] == 0)
    --mminus_Length;
  while (SLength > 1 && S[SLength - 1] == 0)
    --SLength;
  TempLength = (RLength > mplus_Length ? RLength : mplus_Length) + 1;
  addHighPrecision (Temp, TempLength, mplus, mplus_Length);

  if (compareHighPrecision (Temp, TempLength, S, SLength) >= 0)
    {
      firstK = k;
    }
  else
    {
      firstK = k - 1;
      simpleAppendDecimalDigitHighPrecision (R     , ++RLength      , 0);
      simpleAppendDecimalDigitHighPrecision (mplus , ++mplus_Length , 0);
      simpleAppendDecimalDigitHighPrecision (mminus, ++mminus_Length, 0);
      while (RLength > 1 && R[RLength - 1] == 0)
        --RLength;
      while (mplus_Length > 1 && mplus[mplus_Length - 1] == 0)
        --mplus_Length;
      while (mminus_Length > 1 && mminus[mminus_Length - 1] == 0)
        --mminus_Length;
    }

#ifndef _WIN32
  static 
#endif	  
  jfieldID digitsFid = env->GetFieldID(JniConstants::realToStringClass, "digits", "[I");
  ScopedLocalRef<jintArray> javaDigits(env, reinterpret_cast<jintArray>(env->GetObjectField(obj, digitsFid)));
  ScopedIntArrayRW digits(env, javaDigits.get());
  if (digits.get() == NULL) {
    return;
  }

  jint digitCount = 0;
  do
    {
      U = 0;
      for (i = 3; i >= 0; --i)
        {
          TempLength = SLength + 1;
          Temp[SLength] = 0;
          memcpy (Temp, S, SLength * sizeof (uint64_t));
          simpleShiftLeftHighPrecision (Temp, TempLength, i);
          if (compareHighPrecision (R, RLength, Temp, TempLength) >= 0)
            {
              subtractHighPrecision (R, RLength, Temp, TempLength);
              U += 1 << i;
            }
        }

      low = compareHighPrecision (R, RLength, mminus, mminus_Length) <= 0;

      memset (Temp + RLength, 0, (STemp_SIZE - RLength) * sizeof (uint64_t));
      memcpy (Temp, R, RLength * sizeof (uint64_t));
      TempLength = (RLength > mplus_Length ? RLength : mplus_Length) + 1;
      addHighPrecision (Temp, TempLength, mplus, mplus_Length);

      high = compareHighPrecision (Temp, TempLength, S, SLength) >= 0;

      if (low || high)
        break;

      simpleAppendDecimalDigitHighPrecision (R     , ++RLength      , 0);
      simpleAppendDecimalDigitHighPrecision (mplus , ++mplus_Length , 0);
      simpleAppendDecimalDigitHighPrecision (mminus, ++mminus_Length, 0);
      while (RLength > 1 && R[RLength - 1] == 0)
        --RLength;
      while (mplus_Length > 1 && mplus[mplus_Length - 1] == 0)
        --mplus_Length;
      while (mminus_Length > 1 && mminus[mminus_Length - 1] == 0)
        --mminus_Length;
      digits[digitCount++] = U;
    }
  while (1);

  simpleShiftLeftHighPrecision (R, ++RLength, 1);
  if (low && !high)
    digits[digitCount++] = U;
  else if (high && !low)
    digits[digitCount++] = U + 1;
  else if (compareHighPrecision (R, RLength, S, SLength) < 0)
    digits[digitCount++] = U;
  else
    digits[digitCount++] = U + 1;

  static jfieldID digitCountFid = env->GetFieldID(JniConstants::realToStringClass, "digitCount", "I");
  env->SetIntField(obj, digitCountFid, digitCount);

  static jfieldID firstKFid = env->GetFieldID(JniConstants::realToStringClass, "firstK", "I");
  env->SetIntField(obj, firstKFid, firstK);
}
void org_apache_harmony_luni_util_NumberConverter_bigIntDigitGeneratorInstImpl___long_int_boolean_boolean_int(JAVA_OBJECT me, JAVA_LONG n1, JAVA_INT n2, JAVA_BOOLEAN n3, JAVA_BOOLEAN n4, JAVA_INT n5)
{
    //XMLVM_BEGIN_NATIVE[org_apache_harmony_luni_util_NumberConverter_bigIntDigitGeneratorInstImpl___long_int_boolean_boolean_int]
		JAVA_LONG f = n1;
		JAVA_INT e = n2;
		JAVA_BOOLEAN isDenormalized = n3;
		JAVA_BOOLEAN mantissaIsZero = n4;
		JAVA_INT p = n5;

		JAVA_INT RLength, SLength, TempLength, mplus_Length, mminus_Length;
		JAVA_INT high, low, i;
		JAVA_LONG k, firstK, U;
		JAVA_LONG getCount, setCount;
		JAVA_ARRAY_INT* uArray;

		U_64 R[RM_SIZE], S[STemp_SIZE], mplus[RM_SIZE], mminus[RM_SIZE],Temp[STemp_SIZE];

		memset (R, 0, RM_SIZE * sizeof (U_64));
		memset (S, 0, STemp_SIZE * sizeof (U_64));
		memset (mplus, 0, RM_SIZE * sizeof (U_64));
		memset (mminus, 0, RM_SIZE * sizeof (U_64));
		memset (Temp, 0, STemp_SIZE * sizeof (U_64));

		if (e >= 0)
	    {
			*R = f;
			*mplus = *mminus = 1;
			simpleShiftLeftHighPrecision (mminus, RM_SIZE, e);
			if (f != (2 << (p - 1)))
	        {
				simpleShiftLeftHighPrecision (R, RM_SIZE, e + 1);
				*S = 2;
				simpleShiftLeftHighPrecision (mplus, RM_SIZE, e);
			}
			else {
				simpleShiftLeftHighPrecision (R, RM_SIZE, e+2);
				*S = 4;
				simpleShiftLeftHighPrecision (mplus, RM_SIZE, e + 1);

			}
		}
		else {
			if (isDenormalized || (f != (2 << (p - 1))))
	        {
				*R = f << 1;
				*S = 1;
				simpleShiftLeftHighPrecision (S, STemp_SIZE, 1 - e);
				*mplus = *mminus = 1;
	        }
			else
	        {
				*R = f << 2;
				*S = 1;
				simpleShiftLeftHighPrecision (S, STemp_SIZE, 2 - e);
				*mplus = 2;
				*mminus = 1;
	        }
		}
		k = (int) ceil ((e + p - 1) * INV_LOG_OF_TEN_BASE_2 - 1e-10);

		if (k > 0)
	    {
			timesTenToTheEHighPrecision (S, STemp_SIZE, k);
	    }
		else
	    {
			timesTenToTheEHighPrecision (R, RM_SIZE, -k);
			timesTenToTheEHighPrecision (mplus, RM_SIZE, -k);
			timesTenToTheEHighPrecision (mminus, RM_SIZE, -k);
	    }

		RLength = mplus_Length = mminus_Length = RM_SIZE;
		SLength = TempLength = STemp_SIZE;

		memset (Temp + RM_SIZE, 0, (STemp_SIZE - RM_SIZE) * sizeof (U_64));
		memcpy (Temp, R, RM_SIZE * sizeof (U_64));

		while (RLength > 1 && R[RLength - 1] == 0)
			--RLength;
		while (mplus_Length > 1 && mplus[mplus_Length - 1] == 0)
			--mplus_Length;
		while (mminus_Length > 1 && mminus[mminus_Length - 1] == 0)
			--mminus_Length;
		while (SLength > 1 && S[SLength - 1] == 0)
			--SLength;
		TempLength = (RLength > mplus_Length ? RLength : mplus_Length) + 1;
		addHighPrecision (Temp, TempLength, mplus, mplus_Length);

		if (compareHighPrecision (Temp, TempLength, S, SLength) >= 0)
	    {
			firstK = k;
	    }
		else
	    {
			firstK = k - 1;
			simpleAppendDecimalDigitHighPrecision (R, ++RLength, 0);
			simpleAppendDecimalDigitHighPrecision (mplus, ++mplus_Length, 0);
			simpleAppendDecimalDigitHighPrecision (mminus, ++mminus_Length, 0);
			while (RLength > 1 && R[RLength - 1] == 0)
				--RLength;
			while (mplus_Length > 1 && mplus[mplus_Length - 1] == 0)
				--mplus_Length;
			while (mminus_Length > 1 && mminus[mminus_Length - 1] == 0)
				--mminus_Length;
	    }

		org_apache_harmony_luni_util_NumberConverter* inst = (org_apache_harmony_luni_util_NumberConverter*)me;
		org_xmlvm_runtime_XMLVMArray* uArrayObject = inst->fields.org_apache_harmony_luni_util_NumberConverter.uArray_;
		uArray = uArrayObject->fields.org_xmlvm_runtime_XMLVMArray.array_;

		getCount = setCount = 0;
		do
	    {
			U = 0;
			for (i = 3; i >= 0; --i)
	        {
				TempLength = SLength + 1;
				Temp[SLength] = 0;
				memcpy (Temp, S, SLength * sizeof (U_64));
				simpleShiftLeftHighPrecision (Temp, TempLength, i);
				if (compareHighPrecision (R, RLength, Temp, TempLength) >= 0)
	            {
					subtractHighPrecision (R, RLength, Temp, TempLength);
					U += 1 << i;
	            }
	        }

			low = compareHighPrecision (R, RLength, mminus, mminus_Length) <= 0;

			memset (Temp + RLength, 0, (STemp_SIZE - RLength) * sizeof (U_64));
			memcpy (Temp, R, RLength * sizeof (U_64));
			TempLength = (RLength > mplus_Length ? RLength : mplus_Length) + 1;
			addHighPrecision (Temp, TempLength, mplus, mplus_Length);

			high = compareHighPrecision (Temp, TempLength, S, SLength) >= 0;

			if (low || high)
				break;

			simpleAppendDecimalDigitHighPrecision (R, ++RLength, 0);
			simpleAppendDecimalDigitHighPrecision (mplus, ++mplus_Length, 0);
			simpleAppendDecimalDigitHighPrecision (mminus, ++mminus_Length, 0);
			while (RLength > 1 && R[RLength - 1] == 0)
				--RLength;
			while (mplus_Length > 1 && mplus[mplus_Length - 1] == 0)
				--mplus_Length;
			while (mminus_Length > 1 && mminus[mminus_Length - 1] == 0)
				--mminus_Length;
			uArray[setCount++] = U;

		}
		while (1);

		simpleShiftLeftHighPrecision (R, ++RLength, 1);
		if (low && !high)
			uArray[setCount++] = U;
		else if (high && !low)
			uArray[setCount++] = U + 1;
		else if (compareHighPrecision (R, RLength, S, SLength) < 0)
			uArray[setCount++] = U;
		else
			uArray[setCount++] = U + 1;

		inst->fields.org_apache_harmony_luni_util_NumberConverter.setCount_ = setCount;
		inst->fields.org_apache_harmony_luni_util_NumberConverter.getCount_ = getCount;
		inst->fields.org_apache_harmony_luni_util_NumberConverter.firstK_ = firstK;


    //XMLVM_END_NATIVE
}