Skip to content

File numeric_opfunc.c

File List > cubrid > src > query > numeric_opfunc.c

Go to the documentation of this file

/*
 * Copyright 2008 Search Solution Corporation
 * Copyright 2016 CUBRID Corporation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

/*
 * numeric_opfunc.c - Basig manipulations of DB_NUMERIC type data
 */

#ident "$Id$"

/* The bits in the character string of a DB_NUMERIC are the binary digits of
 * the number. The LSB's of the DB_NUMERIC are in buf[DB_NUMERIC_BUF_SIZE-1].
 */

#include <float.h>
#include <math.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <cstdint>

#include "mprec.h"
#include "numeric_opfunc.h"
#include "tz_support.h"
#include "db_date.h"
#include "memory_alloc.h"
#include "system_parameter.h"
#include "byte_order.h"
#include "object_primitive.h"
#include "object_representation.h"

#if defined (__cplusplus)
#include <cmath>
#endif

#include "dbtype.h"
// XXX: SHOULD BE THE LAST INCLUDE HEADER
#include "memory_wrapper.hpp"

#if defined (SUPPRESS_STRLEN_WARNING)
#define strlen(s1)  ((int) strlen(s1))
#endif /* defined (SUPPRESS_STRLEN_WARNING) */

/* the multipler of long NUMERIC, internal used */
#define DB_LONG_NUMERIC_MULTIPLIER 2

#define CARRYOVER(arg)      ((arg) >> 8)
#define BORROW_NEXT(minuend, subtrahend, borrow) ((unsigned)(minuend) < (unsigned)((subtrahend) + (borrow)))
#define GET_LOWER_BYTE(arg) ((arg) & 0xff)
#define NUMERIC_ABS(a)      ((a) >= 0 ? a : -a)

#define NUMERIC_AS_WORDS                (3) /* (DB_NUMERIC_BUF_SIZE(17) + 7) / 8 */
#define NUMERIC_AS_WORD_BYTES           (24)    /* NUMERIC_AS_WORDS * 8 */
#define NUMERIC_GET_FULL_WORDS(bytes)   ((bytes) >> 3)  /* Convert bytes to full words (floor) */
#define NUMERIC_GET_REM_BYTES(bytes)    ((bytes) & 7)   /* Remaining bytes after word alignment */
#define NUMERIC_GET_WORD_COUNT(bytes)   (((bytes) + 7) >> 3)    /* Total word count to cover bytes (ceiling) */
#define NUMERIC_GET_BYTE_COUNT(words)   ((words) << 3)  /* Convert words to byte count */
/*
 * TWICE_NUM_MAX_PREC:
 * - Defines the maximum number of significant digits that can be stored
 *   in a NUMERIC value (excluding sign and decimal point).
 * - Used as the internal buffer size for representing the maximum
 *   range of NUMERIC values.
 * - Also used when converting base-256 encoded NUMERIC values
 *   into their decimal string representation.
 *
 * The maximum allowable range of NUMERIC values is:
 *   1) Exponential form
 *        1.0 × 10^-252 <= |Value| < 1.0 × 10^254
 *   2) Precision/Scale form
 *        (prec, scale) in the range: (1,252) <= (prec,scale) <= (40,-214)
 *
 * TWICE_NUM_MAX_PREC: 256
 * = max digits (40 + 214) = 254 + 2 extra digit
 * Must always be smaller than NUMERIC_MAX_STRING_SIZE.
 */
#define TWICE_NUM_MAX_PREC      ((DB_MAX_NUMERIC_PRECISION - DB_MIN_NUMERIC_SCALE) + 2)
#define SECONDS_IN_A_DAY    (int)(24L * 60L * 60L)

#define ROUND(x)                ((x) > 0 ? ((x) + .5) : ((x) - .5))
#define ROUND_HALF_UP_DIGIT     (5)

/* 
 * (((DB_MAX_NUMERIC_PRECISION - DB_MIN_NUMERIC_SCALE) + DB_MAX_NUMERIC_SCALE) + 6) 
 * = ((40 - (-214)) + 252) + 6 
 * = 506 + 6 = 512
 */
#define POW10_MAX_INDEX         (((DB_MAX_NUMERIC_PRECISION - DB_MIN_NUMERIC_SCALE) + DB_MAX_NUMERIC_SCALE) + 6)
/* 
 * floor(POW10_MAX_INDEX * log256​(10)) + 1 
 * = (512 * 0.41524) + 1 
 * = 212 + 1 = 213
 * = 213 / 8 = 26.6 -> 27
 */
#define POW10_BUF_WORDS         (27)
#define POW10_BUF_SIZE          (POW10_BUF_WORDS * sizeof(uint64_t))

/* [10^n][multi-precision buffer (uint64_t words, MSB-first, each word in host endianness)] */
static uint64_t powers_of_10[POW10_MAX_INDEX + 1][POW10_BUF_WORDS];

static const double numeric_Pow_of_10[10] = {
  1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9
};

/* look-up table : containing pre-calculated 4-byte ASCII representations for numbers 0000-9999. */
static uint32_t _gv_digits4_ascii_lut[10000];

/*
 * _gv_numeric_precision_to_bytes_lookup
 *
 * this is a lookup table (LUT) that precomputes the number of bytes
 * required for a numeric value based on its decimal digits (precision),
 * instead of calculating it with floating-point operations each time.
 *
 *   bytes = ceil(precision / log10(256))
 *
 * - Index/size:
 *   513 = (((DB_MAX_NUMERIC_PRECISION - DB_MIN_NUMERIC_SCALE) + DB_MAX_NUMERIC_SCALE) + 6) + 1
 *       = (((40 - (-214)) + 252) + 6) + 1
 *
 *   +6 : Extra space added to align the maximum index used internally
 *        to a power-of-two boundary (e.g., aligning 506 -> 512).
 *   +1 : Index 0 is reserved for special use / adjustment.
 *
 * the size is chosen generously to safely cover the full range of
 * precision/scale values that are actually used, including the
 * extra range required when adjusting scale and allocating working buffers.
 */
static const int _gv_numeric_precision_to_bytes_lookup[513] = {
  0, 1, 1, 2, 2, 3, 3, 3, 4, 4, 5, 5, 5, 6, 6, 7, 7, 8, 8, 8,
  9, 9, 10, 10, 10, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17,
  17, 18, 18, 18, 19, 19, 20, 20, 20, 21, 21, 22, 22, 23, 23, 23, 24, 24, 25, 25,
  25, 26, 26, 27, 27, 27, 28, 28, 29, 29, 30, 30, 30, 31, 31, 32, 32, 32, 33, 33,
  34, 34, 35, 35, 35, 36, 36, 37, 37, 37, 38, 38, 39, 39, 40, 40, 40, 41, 41, 42,
  42, 42, 43, 43, 44, 44, 45, 45, 45, 46, 46, 47, 47, 47, 48, 48, 49, 49, 49, 50,
  50, 51, 51, 52, 52, 52, 53, 53, 54, 54, 54, 55, 55, 56, 56, 57, 57, 57, 58, 58,
  59, 59, 59, 60, 60, 61, 61, 62, 62, 62, 63, 63, 64, 64, 64, 65, 65, 66, 66, 67,
  67, 67, 68, 68, 69, 69, 69, 70, 70, 71, 71, 72, 72, 72, 73, 73, 74, 74, 74, 75,
  75, 76, 76, 76, 77, 77, 78, 78, 79, 79, 79, 80, 80, 81, 81, 81, 82, 82, 83, 83,
  84, 84, 84, 85, 85, 86, 86, 86, 87, 87, 88, 88, 89, 89, 89, 90, 90, 91, 91, 91,
  92, 92, 93, 93, 94, 94, 94, 95, 95, 96, 96, 96, 97, 97, 98, 98, 98, 99, 99, 100,
  100, 101, 101, 101, 102, 102, 103, 103, 103, 104, 104, 105, 105, 106, 106, 106, 107, 107, 108, 108,
  108, 109, 109, 110, 110, 111, 111, 111, 112, 112, 113, 113, 113, 114, 114, 115, 115, 116, 116, 116,
  117, 117, 118, 118, 118, 119, 119, 120, 120, 121, 121, 121, 122, 122, 123, 123, 123, 124, 124, 125,
  125, 125, 126, 126, 127, 127, 128, 128, 128, 129, 129, 130, 130, 130, 131, 131, 132, 132, 133, 133,
  133, 134, 134, 135, 135, 135, 136, 136, 137, 137, 138, 138, 138, 139, 139, 140, 140, 140, 141, 141,
  142, 142, 143, 143, 143, 144, 144, 145, 145, 145, 146, 146, 147, 147, 147, 148, 148, 149, 149, 150,
  150, 150, 151, 151, 152, 152, 152, 153, 153, 154, 154, 155, 155, 155, 156, 156, 157, 157, 157, 158,
  158, 159, 159, 160, 160, 160, 161, 161, 162, 162, 162, 163, 163, 164, 164, 165, 165, 165, 166, 166,
  167, 167, 167, 168, 168, 169, 169, 170, 170, 170, 171, 171, 172, 172, 172, 173, 173, 174, 174, 174,
  175, 175, 176, 176, 177, 177, 177, 178, 178, 179, 179, 179, 180, 180, 181, 181, 182, 182, 182, 183,
  183, 184, 184, 184, 185, 185, 186, 186, 187, 187, 187, 188, 188, 189, 189, 189, 190, 190, 191, 191,
  192, 192, 192, 193, 193, 194, 194, 194, 195, 195, 196, 196, 196, 197, 197, 198, 198, 199, 199, 199,
  200, 200, 201, 201, 201, 202, 202, 203, 203, 204, 204, 204, 205, 205, 206, 206, 206, 207, 207, 208,
  208, 209, 209, 209, 210, 210, 211, 211, 211, 212, 212, 213, 213
};

/* precomputed lookup table for 10^1 through 10^19 */
static const uint64_t _gv_mul_normalize_pow10_lookup[19] = {
  10ULL, 100ULL, 1000ULL, 10000ULL, 100000ULL, 1000000ULL, 10000000ULL, 100000000ULL, 1000000000ULL,
  10000000000ULL, 100000000000ULL, 1000000000000ULL, 10000000000000ULL, 100000000000000ULL,
  1000000000000000ULL, 10000000000000000ULL,
  100000000000000000ULL, 1000000000000000000ULL, 10000000000000000000ULL
};

typedef enum fp_value_type
{
  FP_VALUE_TYPE_NUMBER,
  FP_VALUE_TYPE_INFINITE,
  FP_VALUE_TYPE_NAN,
  FP_VALUE_TYPE_ZERO
}
FP_VALUE_TYPE;

typedef enum
{
  NUMERIC_MAG_ZERO,     /* all bytes are zero */
  NUMERIC_MAG_INT,      /* magnitude fits in int range */
  NUMERIC_MAG_BIGINT,       /* magnitude fits in DB_BIGINT range */
  NUMERIC_MAG_NUMERIC       /* magnitude exceeds DB_BIGINT range */
} numeric_magnitude_t;

static inline bool numeric_is_negative (const DB_VALUE * value);
static void numeric_copy (DB_C_NUMERIC dest, DB_C_NUMERIC source);
static void numeric_copy_long (DB_C_NUMERIC dest, DB_C_NUMERIC source, bool is_long_num);
static void numeric_increase (DB_C_NUMERIC answer);
static void numeric_increase_long (DB_C_NUMERIC answer, bool is_long_num);
static void numeric_zero (DB_C_NUMERIC answer, int size);
static void numeric_init_pow_of_10_helper (void);
static void numeric_get_pow_of_10 (int exp, uint8_t * result);
static void numeric_init_digits4_ascii_helper (void);
static inline uint32_t numeric_get_digits4_ascii (uint32_t val);
static int float_numeric_find_first_nz_idx_msb (const uint64_t * word_buf, int calc_words);
static int float_numeric_get_decimal_digit (const uint64_t * word_buf, int calc_words);
static void numeric_double_shift_bit (DB_C_NUMERIC arg1, DB_C_NUMERIC arg2, int numbits, DB_C_NUMERIC lsb,
                      DB_C_NUMERIC msb, bool is_long_num);
static int numeric_compare_pos (DB_C_NUMERIC arg1, DB_C_NUMERIC arg2);
static void numeric_shift_byte (DB_C_NUMERIC arg, int numbytes, DB_C_NUMERIC answer, int length);
static bool numeric_is_zero (DB_C_NUMERIC arg);
static numeric_magnitude_t numeric_classify_magnitude (DB_C_NUMERIC arg, bool is_value_negative);
static bool numeric_overflow (DB_C_NUMERIC arg, int exp);
static void numeric_add (DB_C_NUMERIC arg1, DB_C_NUMERIC arg2, DB_C_NUMERIC answer, int size);
static void float_numeric_add (const uint64_t * dbv1_word, const uint64_t * dbv2_word, uint64_t * result_word,
                   int calc_words);
static int float_numeric_add_fast (const uint64_t * dbv1_word, const uint64_t * dbv2_word, uint64_t * result_word,
                   int calc_words, bool dbv1_sign, bool dbv2_sign, bool * result_sign,
                   uint8_t * result_buf);
static void numeric_sub (DB_C_NUMERIC arg1, DB_C_NUMERIC arg2, DB_C_NUMERIC answer, int size);
static void float_numeric_sub (const uint64_t * arg1_word, const uint64_t * arg2_word, uint64_t * result_word,
                   int calc_words);
static int float_numeric_sub_fast (const uint64_t * dbv1_word, const uint64_t * dbv2_word, uint64_t * result_word,
                   int calc_words, bool dbv1_sign, bool dbv2_sign, bool * result_sign,
                   uint8_t * result_buf);
static void numeric_mul (DB_C_NUMERIC a1, DB_C_NUMERIC a2, DB_C_NUMERIC answer, bool * is_value_negative);
static void float_numeric_mul (const uint64_t * dbv1_word, const uint64_t * dbv2_word, uint64_t * result_word,
                   int calc_words, int calc_nbytes);
static int float_numeric_mul_fast (const uint64_t * dbv1_word, const uint64_t * dbv2_word, uint64_t * result_word,
                   int calc_words, uint8_t * result_buf, int *result_scale);
static void numeric_long_div (DB_C_NUMERIC a1, DB_C_NUMERIC a2, DB_C_NUMERIC answer, DB_C_NUMERIC remainder,
                  bool is_long_num);
static void numeric_div (DB_C_NUMERIC arg1, DB_C_NUMERIC arg2, DB_C_NUMERIC answer, DB_C_NUMERIC remainder,
             bool arg1_value_is_negative, bool arg2_value_is_negative);
static void float_numeric_div_fast (uint64_t dividend_val, uint64_t divisor_val, int prec1, int scale1, int prec2,
                    int scale2, int exponent_diff, uint8_t * result_buf, int *result_prec_out,
                    int *result_scale_out, bool * result_sign);
static void float_numeric_mod_fast (uint64_t dividend_val, uint64_t divisor_val, uint8_t * result_buf,
                    int *result_prec_out, int *result_scale_out, bool * result_sign);
static int knuth_find_first_nz_idx (const knuth_digit_t * digit_buf, int total_digits);
static unsigned int knuth_count_leading_zero_bits (knuth_digit_t value);
static void knuth_normalize_left_shift_msb (knuth_digit_t * buffer, int buffer_size, unsigned int k_bit);
static void knuth_normalize_right_shift_msb (knuth_digit_t * buffer, int buffer_size, unsigned int k_bit);
static knuth_digit_t knuth_estimate_quotient_digit (const knuth_digit_t * u_work, const knuth_digit_t * v_work,
                            int window_offset, int dividend_words, int divisor_words,
                            knuth_digit_t * out_trial_remainder);
static knuth_digit_t knuth_multiply_and_subtract (knuth_digit_t * u_work, const knuth_digit_t * v_work,
                          int window_offset, int divisor_words, knuth_digit_t trial_quotient);
static void float_numeric_knuth_div (knuth_digit_t * dbv1_buf, knuth_digit_t * dbv2_buf, knuth_digit_t * quo_buf,
                     knuth_digit_t * rem_buf, knuth_digit_t calc_words);
static int float_numeric_compare (uint8_t * arg1, uint8_t * arg2, int prec1, int scale1, int prec2, int scale2,
                  bool arg1_sign, bool arg2_sign);
static int numeric_scale_by_ten (DB_C_NUMERIC arg, bool is_long_num);
static int numeric_scale_dec (const DB_C_NUMERIC arg, int dscale, DB_C_NUMERIC answer);
static int numeric_scale_dec_long (DB_C_NUMERIC answer, int dscale, bool is_long_num);
static int numeric_common_prec_scale (const DB_VALUE * dbv1, const DB_VALUE * dbv2, DB_VALUE * dbv1_common,
                      DB_VALUE * dbv2_common);
static int numeric_prec_scale_when_overflow (const DB_VALUE * dbv1, const DB_VALUE * dbv2, DB_VALUE * dbv1_common,
                         DB_VALUE * dbv2_common);
static void numeric_coerce_big_num_to_dec_str (unsigned char *num, char *dec_str);
static int numeric_get_msb_for_dec (int src_prec, int src_scale, unsigned char *src, int *dest_prec, int *dest_scale,
                    DB_C_NUMERIC dest);
static FP_VALUE_TYPE get_fp_value_type (double d);
static int numeric_internal_real_to_num (double adouble, int dst_scale, DB_C_NUMERIC num, int *prec, int *scale,
                     bool is_float, bool * is_value_negative);
static int analyze_numeric_string (const char *astring, int astring_length, INTL_CODESET codeset, bool * negate_value,
                   char *int_digits, int *int_len, char *frac_digits, int *frac_len,
                   int *frac_first_sig_digit, int *frac_last_sig_digit, bool * is_zero);
static void determine_prec_scale (const char *int_digits, int int_len, const char *frac_digits, int frac_len,
                  int frac_first_sig_digit, int frac_last_sig_digit, char *num_string, int *out_prec,
                  int *out_scale);
static void determine_round (char *out_str, int *out_prec, int *out_scale, int tmp_int_len, int tmp_frac_len,
                 int frac_zero_cnt, char next_digit);
static void numeric_get_integral_part (const DB_C_NUMERIC num, const int src_prec, const int src_scale,
                       const int dst_prec, DB_C_NUMERIC dest);
static void numeric_get_fractional_part (const DB_C_NUMERIC num, const int src_scale, const int dst_prec,
                     DB_C_NUMERIC dest);
static bool numeric_is_fraction_part_zero (const DB_VALUE * num_value, const int scale);
static bool numeric_is_longnum_value (DB_C_NUMERIC arg);
static int numeric_longnum_to_shortnum (DB_C_NUMERIC answer, DB_C_NUMERIC long_arg);
static void numeric_shortnum_to_longnum (DB_C_NUMERIC long_answer, DB_C_NUMERIC arg);
static int get_significant_digit (DB_BIGINT i);
static void float_numeric_mul_pow10 (uint64_t * dbv_buf, int calc_words, int calc_bytes, uint64_t multiplier);
static void float_numeric_mul_normalize (uint64_t * dbv_buf, int calc_words, int calc_bytes, int exponent);
static uint64_t float_numeric_div_pow10 (uint64_t * dbv_buf, int calc_words, int calc_bytes, uint64_t divisor);
static int float_numeric_div_normalize (uint64_t * dbv_buf, int calc_words, int calc_bytes, int exponent);
static void float_numeric_increment (uint64_t * calc_buf, int calc_words, uint64_t val);
static int numeric_operation_compare (const uint8_t * dbv1_buf, const uint8_t * dbv2_buf, int calc_bytes);
static int float_numeric_operation_compare (const uint64_t * arg1_word, const uint64_t * arg2_word, int calc_words);
static int float_numeric_check_overflow_and_adjust_scale (int *result_prec, int *result_scale, DB_VALUE * answer);
static int float_numeric_round_and_pack (uint64_t * word_buf, int calc_words, int calc_nbytes,
                     uint8_t * result_buf, int *result_prec, int *result_scale);
static int compare_mantissa_same_exponent (uint64_t * dividend_word, uint64_t * divisor_word,
                       int calc_words, int calc_nbytes, int prec1, int prec2);
static int float_numeric_compare_rem_round_up (const uint64_t * rem, const uint64_t * div, int calc_words);
static inline void numeric_pack_digits4_ascii (char *buf, uint64_t val);
static inline uint64_t numeric_get_uint64_from_be (const void *ptr);
static inline void numeric_put_uint64_to_be (void *ptr, uint64_t val);
static void numeric_bytes_to_words (const uint8_t * src, int src_bytes, uint64_t * dest, int dest_words,
                    int dest_bytes);
static void numeric_words_to_bytes (const uint64_t * src, int src_words, uint8_t * dest);

/*
 * numeric_is_negative () -
 *   return: true, false
 *   arg(in) : DB_VALUE value
 */
static inline bool
numeric_is_negative (const DB_VALUE * value)
{
  assert (value);
  return DB_VALUE_NUMERIC_IS_VALUE_NEGATIVE (value);
}

/*
 * numeric_copy () -
 *   return:
 *   dest(out)  : DB_C_NUMERIC value
 *   source(in) : DB_C_NUMERIC value
 * Note: This routine returns source copied into dest.
 */
static void
numeric_copy (DB_C_NUMERIC dest, DB_C_NUMERIC source)
{
  numeric_copy_long (dest, source, false);
}

/*
 * numeric_copy_long () -
 *   return:
 *   dest(out)  : DB_C_NUMERIC value
 *   source(in) : DB_C_NUMERIC value
 *   is_long_num(in): is long NUMERIC
 * Note: This routine returns source copied into dest.
 */
static void
numeric_copy_long (DB_C_NUMERIC dest, DB_C_NUMERIC source, bool is_long_num)
{
  int num_cnt = 1;

  if (dest != source)
    {
      if (source == NULL || dest == NULL)
    {
      assert (0);
      return;
    }

      if (is_long_num)
    {
      num_cnt = DB_LONG_NUMERIC_MULTIPLIER;
    }
      memcpy (dest, source, DB_NUMERIC_BUF_SIZE * num_cnt);
    }
}

/*
 * numeric_increase () -
 *   return:
 *   answer(in/out) : DB_C_NUMERIC value
 *
 * Note: This routine increments a numeric value.
 */
static void
numeric_increase (DB_C_NUMERIC answer)
{
  numeric_increase_long (answer, false);
}

/*
 * numeric_increase_long () -
 *   return:
 *   answer(in/out) : DB_C_NUMERIC value
 *   is_long_num(in): is long NUMERIC
 *
 * Note: This routine increments a numeric value.
 */
static void
numeric_increase_long (DB_C_NUMERIC answer, bool is_long_num)
{
  int carry = 1;
  int digit;

  if (is_long_num)
    {
      digit = DB_NUMERIC_BUF_SIZE * DB_LONG_NUMERIC_MULTIPLIER - 1;
    }
  else
    {
      digit = DB_NUMERIC_BUF_SIZE - 1;
    }
  /* Loop through answer as long as there is a carry */
  for (; digit >= 0 && carry == 1; digit--)
    {
      answer[digit] += 1;
      carry = (answer[digit] == 0) ? 1 : 0;
    }
}

/*
 * numeric_zero () -
 *   return:
 *   answer(in) : DB_C_NUMERIC value
 *   size(in)   :
 *
 * Note: This routine zeroes out a numeric value and returns the result
 */
static void
numeric_zero (DB_C_NUMERIC answer, int size)
{
  memset (answer, 0, size); /* sizeof(answer[0]) == 1 */
}

/*
 * numeric_init_pow_of_10_helper()
 *
 * Initialize the powers_of_10 lookup table used for precision checks.
 *
 * Each entry stores 10^n as a multi-precision integer in a uint64_t
 * word array (MSB-first). This matches the internal word-based
 * representation of numeric_buf.
 *
 * The table is used to compare against numeric values in word form
 * to determine precision/scale boundaries, avoiding repeated
 * conversions from byte-based representation.
 *
 * Values are built iteratively (10^n = 10^(n-1) * 10) with carry
 * propagation across words, and the number of significant words
 * is tracked for optimization.
 */
__attribute__ ((constructor))
     static void numeric_init_pow_of_10_helper (void)
{
  int i, j;
  uint64_t carry;
  uint128_t temp;

  memset (powers_of_10, 0, (POW10_MAX_INDEX + 1) * POW10_BUF_WORDS * sizeof (uint64_t));

  /* Set first element to 1 */
  powers_of_10[0][POW10_BUF_WORDS - 1] = 1;

  /* Loop through elements setting each one to 10 times the prior */
  for (i = 1; i < POW10_MAX_INDEX + 1; i++)
    {
      carry = 0;
      for (j = POW10_BUF_WORDS - 1; j >= 0; j--)
    {
      temp = (uint128_t) powers_of_10[i - 1][j] * 10 + carry;
      powers_of_10[i][j] = (uint64_t) (temp & 0xFFFFFFFFFFFFFFFFULL);
      carry = (uint64_t) (temp >> 64);
    }
    }
}

/*
 * numeric_get_pow_of_10()
 *
 * Returns 10^exp as a DB_C_NUMERIC (17-byte big-endian format).
 *
 * The internal powers_of_10 table stores values as multi-precision
 * uint64_t words (MSB-first). This function extracts the least
 * significant 3 words and converts them into the NUMERIC byte layout.
 *
 * On little-endian systems, BSWAP64 is applied to ensure correct
 * byte order.
 */
static void
numeric_get_pow_of_10 (int exp, uint8_t * result)
{
  assert (exp >= 0 && exp <= POW10_MAX_INDEX);

  /* convert word-based (host-endian) powers_of_10 into
   * 17-byte big-endian NUMERIC format */
#if OR_BYTE_ORDER == OR_LITTLE_ENDIAN
  const uint64_t *src = &powers_of_10[exp][POW10_BUF_WORDS - NUMERIC_AS_WORDS];
  result[0] = (uint8_t) (src[0] & 0xFF);
  uint64_t temp_word[2];
  temp_word[0] = NUMERIC_BSWAP64 (src[1]);
  temp_word[1] = NUMERIC_BSWAP64 (src[2]);
  memcpy (result + 1, temp_word, sizeof (temp_word));
#else
  memcpy (result, (uint8_t *) powers_of_10[exp] + (POW10_BUF_SIZE - DB_NUMERIC_BUF_SIZE), DB_NUMERIC_BUF_SIZE);
#endif
}

/*
 * numeric_init_digits4_ascii_helper () -
 *   return:
 */
__attribute__ ((constructor))
     static void numeric_init_digits4_ascii_helper (void)
{
  int i;
  uint8_t digit[4];

  for (i = 0; i < 10000; i++)
    {
      digit[0] = '0' + (i / 1000);
      digit[1] = '0' + (i / 100) % 10;
      digit[2] = '0' + (i / 10) % 10;
      digit[3] = '0' + (i % 10);

      _gv_digits4_ascii_lut[i] =
    (uint32_t) digit[0] | ((uint32_t) digit[1] << 8) | ((uint32_t) digit[2] << 16) | ((uint32_t) digit[3] << 24);
    }
}

/*
 * numeric_get_digits4_ascii () - returns pre-calculated ASCII value for a 4-digit number
 *   return : uint32_t containing 4 ASCII bytes
 *   val(in): integer value between 0 and 9999
 *
 * Note: high-performance lookup for 4-byte packed ASCII represented as uint32.
 *       performs lazy initialization if not in SERVER_MODE.
 */
static inline uint32_t
numeric_get_digits4_ascii (uint32_t val)
{
  assert (val < 10000);
  return _gv_digits4_ascii_lut[val];
}

/*
 * float_numeric_find_first_nz_idx_msb() - Find the index of the first non-zero 64-bit word
 *   return        : Index of the first non-zero word,
 *                   or buffer_size if all bytes are zero
 *   buffer(in)    : Byte array stored in MSB-first order
 *   buffer_size(in): Size of the buffer in bytes
 */
static int
float_numeric_find_first_nz_idx_msb (const uint64_t * word_buf, int calc_words)
{
  int i = 0;

  for (i = 0; i < calc_words; i++)
    {
      if (word_buf[i] != 0)
    {
      return i;
    }
    }
  return calc_words;
}

/*
 * float_numeric_get_decimal_digit()
 *
 * Estimate the number of decimal digits of a multi-precision value
 * stored in a uint64_t word buffer (MSB-first).
 *
 * Steps:
 *   1. Find the most significant non-zero word.
 *   2. Compute bit length of the value.
 *   3. Estimate decimal digits using log10(2):
 *        digits = bits * log10(2)
 *      (approximated with integer arithmetic).
 *   4. Refine the estimate by comparing against powers_of_10 LUT.
 */
static inline int
float_numeric_get_decimal_digit (const uint64_t * word_buf, int calc_words)
{
  /* 1. find first non-zero word (MSB side) */
  int first_nz_idx = float_numeric_find_first_nz_idx_msb (word_buf, calc_words);
  if (first_nz_idx == calc_words)
    {
      return 1;
    }

  /* 2. compute bit length (MSB position in the multi-precision value) */
  uint64_t ms_word = word_buf[first_nz_idx];
  int bits = (calc_words - 1 - first_nz_idx) * 64 + (64 - NUMERIC_CLZ64 (ms_word));

  /*
   * 3. estimate decimal digits from bit length:
   *
   *    a value with 'bits' bits satisfies:
   *      2^(bits-1) <= value < 2^bits
   *
   *    taking log10:
   *      (bits - 1) * log10(2) <= log10(value) < bits * log10(2)
   *
   *    therefore, decimal digit count is:
   *      digits = floor(log10(value)) + 1
   *             = floor((bits - 1) * log10(2)) + 1
   *
   *    using fixed-point approximation:
   *      log10(2) = 0.30103 = 1233 / 4096 (= 0.301025390625)
   *
   *    final integer form:
   *      digits = ((bits - 1) * 1233 / 4096) + 1
   *             = ((bits - 1) * 1233 >> 12) + 1
   *
   *    (right shift replaces division for performance)
   */
  int est_digits = (bits == 0) ? 1 : ((bits - 1) * 1233 >> 12) + 1;

  /* 4. refine using powers_of_10: find smallest idx such that value < 10^idx */
  const int offset = POW10_BUF_WORDS - calc_words;
  for (int idx = est_digits; idx <= POW10_MAX_INDEX; idx++)
    {
      if (float_numeric_operation_compare (word_buf, powers_of_10[idx] + offset, calc_words) < 0)
    {
      return idx;
    }
    }
  return POW10_MAX_INDEX;
}

/*
 * numeric_double_shift_bit () -
 *   return:
 *   arg1(in)   : DB_C_NUMERIC
 *   arg2(in)   : DB_C_NUMERIC
 *   numbits(in): integer number of bits to shift
 *   lsb(out)   : DB_C_NUMERIC
 *   msb(out)   : DB_C_NUMERIC
 *   is_long_num(in) : is long NUMERIC.
 *
 * Note: This routine returns lsb, msb shifted by numbits from arg1, arg2.
 *       Bits that are shifted out of arg1 are placed into LSB of arg2.
 *       only arg1 and lsb may be long NUMERIC.
 */
static void
numeric_double_shift_bit (DB_C_NUMERIC arg1, DB_C_NUMERIC arg2, int numbits, DB_C_NUMERIC lsb, DB_C_NUMERIC msb,
              bool is_long_num)
{
  /* the largest buf size of DB_C_NUMERIC */
  unsigned char local_arg1[DB_NUMERIC_BUF_SIZE * DB_LONG_NUMERIC_MULTIPLIER];
  unsigned char local_arg2[DB_NUMERIC_BUF_SIZE];    /* copy of a DB_C_NUMERIC */
  unsigned int digit;
  unsigned int buf_size;

  if (is_long_num)
    {
      buf_size = DB_NUMERIC_BUF_SIZE * DB_LONG_NUMERIC_MULTIPLIER;
    }
  else
    {
      buf_size = DB_NUMERIC_BUF_SIZE;
    }

  /* Copy args into local variables */
  numeric_copy_long (local_arg1, arg1, is_long_num);
  numeric_copy (local_arg2, arg2);

  /* Loop through all but last word of msb shifting bits */
  for (digit = 0; digit < DB_NUMERIC_BUF_SIZE - 1; digit++)
    {
      msb[digit] = (local_arg2[digit] << numbits) | (local_arg2[digit + 1] >> (8 - numbits));
    }

  /* Do last word of msb separately using upper word of lsb */
  msb[DB_NUMERIC_BUF_SIZE - 1] = (local_arg2[DB_NUMERIC_BUF_SIZE - 1] << numbits) | (local_arg1[0] >> (8 - numbits));

  /* Loop through all but last word of lsb shifting bits */
  for (digit = 0; digit < buf_size - 1; digit++)
    {
      lsb[digit] = (local_arg1[digit] << numbits) | (local_arg1[digit + 1] >> (8 - numbits));
    }

  /* Do last word of lsb separately.  */
  lsb[buf_size - 1] = local_arg1[buf_size - 1] << numbits;
}

/*
 * numeric_compare_pos () -
 *   return: Integer flag indicating whether arg1 is less than arg2
 *   arg1(in)   : DB_C_NUMERIC
 *   arg2(in)   : DB_C_NUMERIC
 *
 * Note: This routine compares two positive DB_C_NUMERIC values.
 *       This function returns:
 *          -1   if    arg1 < arg2
 *           0   if    arg1 = arg2 and
 *           1   if    arg1 > arg2.
 */
static int
numeric_compare_pos (DB_C_NUMERIC arg1, DB_C_NUMERIC arg2)
{
  unsigned int digit;

  /* Loop through bytes looking for the largest */
  for (digit = 0; digit < DB_NUMERIC_BUF_SIZE; digit++)
    {
      if (arg1[digit] != arg2[digit])
    {
      return (arg1[digit] > arg2[digit]) ? 1 : (-1);
    }
    }

  /* If all bytes have been compared, then args are equal */
  return (0);
}

/*
 * numeric_shift_byte () -
 *   return:
 *   arg(in)    : DB_C_NUMERIC
 *   numbytes(in): integer number of bytes to shift
 *   answer(out) : DB_C_NUMERIC
 *   length(in) : Length in bytes of answer
 *
 * Note: This routine returns arg shifted by numbytes in answer.  Empty bytes
 *       are zero filled.
 */
static void
numeric_shift_byte (DB_C_NUMERIC arg, int numbytes, DB_C_NUMERIC answer, int length)
{
  int digit;
  int first;
  int last;

  /* Loop through bytes in answer setting to 0 or arg1 */
  first = length - DB_NUMERIC_BUF_SIZE - numbytes;
  last = length - numbytes - 1;
  for (digit = 0; digit < length; digit++)
    {
      if (first <= digit && digit <= last)
    {
      answer[digit] = arg[digit - first];
    }
      else
    {
      answer[digit] = 0;
    }
    }
}

/*
 * numeric_is_zero () -
 *   return: bool
 *   arg(in)    : DB_C_NUMERIC
 *
 * Note: This routine checks if arg = 0.
 *       This function returns:
 *           true   if    arg1 = 0 and
 *           false  otherwise.
 */
static bool
numeric_is_zero (DB_C_NUMERIC arg)
{
  unsigned int digit;

  /* Loop through arg's bits looking for non-zero values */
  for (digit = 0; digit < DB_NUMERIC_BUF_SIZE; digit++)
    {
      if (arg[digit] != 0)
    {
      return (false);
    }
    }

  return (true);
}

/*
 * numeric_classify_magnitude () -
 *   return: magnitude category of arg
 *   arg(in)    : DB_C_NUMERIC
 *   is_value_negative(in): sign of the value
 *
 * Note: Single 17-byte scan locates the most-significant non-zero byte and
 *       classifies the magnitude into ZERO / INT / BIGINT / NUMERIC.
 *       The first non-zero index alone determines the category in most
 *       regions; only the boundary indices (13 for INT, 9 for BIGINT) need
 *       a one-byte threshold check, plus a corner-case check for exactly
 *       -2^31 / -2^63 (magnitude == 0x80...0, negative sign).
 */
static numeric_magnitude_t
numeric_classify_magnitude (DB_C_NUMERIC arg, bool is_value_negative)
{
  int i;

  for (i = 0; i < DB_NUMERIC_BUF_SIZE; i++)
    {
      if (arg[i] != 0)
    {
      break;
    }
    }

  if (i == DB_NUMERIC_BUF_SIZE)
    {
      return NUMERIC_MAG_ZERO;
    }

  /* INT region: top 13 bytes are zero (i >= 13) */
  if (i > 13)
    {
      /* magnitude < 0x01000000, well within int range */
      return NUMERIC_MAG_INT;
    }
  if (i == 13)
    {
      if (arg[13] < 0x80)
    {
      /* magnitude <= 0x7FFFFFFF */
      return NUMERIC_MAG_INT;
    }
      if (arg[13] == 0x80 && is_value_negative && arg[14] == 0 && arg[15] == 0 && arg[16] == 0)
    {
      /* exactly -2^31 */
      return NUMERIC_MAG_INT;
    }
      /* exceeds int range, but top 13 bytes are zero so it fits in 8 bytes */
      return NUMERIC_MAG_BIGINT;
    }

  /* BIGINT region: top 9 bytes are zero (i >= 9) */
  if (i > 9)
    {
      return NUMERIC_MAG_BIGINT;
    }
  if (i == 9)
    {
      if (arg[9] < 0x80)
    {
      return NUMERIC_MAG_BIGINT;
    }
      if (arg[9] == 0x80 && is_value_negative
      && arg[10] == 0 && arg[11] == 0 && arg[12] == 0
      && arg[13] == 0 && arg[14] == 0 && arg[15] == 0 && arg[16] == 0)
    {
      /* exactly -2^63 */
      return NUMERIC_MAG_BIGINT;
    }
      return NUMERIC_MAG_NUMERIC;
    }

  return NUMERIC_MAG_NUMERIC;
}

/*
 * numeric_overflow () -
 *   return: bool
 *   arg(in)    : DB_C_NUMERIC
 *   exp(in)    : exponent (base 10) of domain
 *
 * Note: This routine checks to see if arg overflows a domain of precision exp.
 */
static bool
numeric_overflow (DB_C_NUMERIC arg, int exp)
{
  uint8_t pow10_buf[DB_NUMERIC_BUF_SIZE];
  numeric_get_pow_of_10 (exp, pow10_buf);
  return (numeric_compare_pos (arg, pow10_buf) >= 0) ? true : false;
}

/*
 * numeric_add () -
 *   return:
 *   arg1(in)   : DB_C_NUMERIC
 *   arg2(in)   : DB_C_NUMERIC
 *   answer(out): DB_C_NUMERIC
 *   size(in)   : int
 *
 * Note: This routine adds two numerics and returns the result.  It assumes
 *       that arg1 and arg2 have the same scaling.
 */
static void
numeric_add (DB_C_NUMERIC arg1, DB_C_NUMERIC arg2, DB_C_NUMERIC answer, int size)
{
  unsigned int answer_bit = 0;
  int digit;

  /* Loop through the characters setting answer */
  for (digit = size - 1; digit >= 0; digit--)
    {
      answer_bit = (arg1[digit] + arg2[digit]) + CARRYOVER (answer_bit);
      answer[digit] = GET_LOWER_BYTE (answer_bit);
    }
}

/*
 * float_numeric_add () -
 *   return: none
 *   dbv1_word(in)   : multi-precision operand 1 (word array)
 *   dbv2_word(in)   : multi-precision operand 2 (word array)
 *   result_word(out): result buffer (word array)
 *   calc_words(in)  : number of words
 *
 * Note: Performs multi-precision addition on MSB-first word arrays.
 *       The computation proceeds from least significant word to most
 *       significant word, propagating carry across words.
 */
static void
float_numeric_add (const uint64_t * dbv1_word, const uint64_t * dbv2_word, uint64_t * result_word, int calc_words)
{
  uint64_t carry = 0;
  int digit;
  uint128_t sum;

  for (digit = calc_words - 1; digit >= 0; digit--)
    {
      sum = (uint128_t) dbv1_word[digit] + dbv2_word[digit] + carry;
      result_word[digit] = (uint64_t) sum;
      carry = (uint64_t) (sum >> 64);
    }
}

/*
 * float_numeric_add_fast () -
 *   return: estimated decimal digit count of result
 *   dbv1_word(in)    : operand 1 (word array)
 *   dbv2_word(in)    : operand 2 (word array)
 *   result_word(out) : result buffer (word array)
 *   calc_words(in)   : number of words (expected 3 for fast path)
 *   dbv1_sign(in)    : sign of operand 1
 *   dbv2_sign(in)    : sign of operand 2
 *   result_sign(out) : sign of result
 *   result_buf(out)  : result in 17-byte NUMERIC format
 *
 * Note: Optimized path for 3-word numeric values.
 *       - If signs are equal, performs addition.
 *       - If signs differ, performs subtraction based on magnitude.
 *       - Uses platform-specific fast instructions when available.
 *       The result is converted to byte format and its decimal digit
 *       count is returned.
 */
static int
float_numeric_add_fast (const uint64_t * dbv1_word, const uint64_t * dbv2_word, uint64_t * result_word, int calc_words,
            bool dbv1_sign, bool dbv2_sign, bool * result_sign, uint8_t * result_buf)
{
  if (dbv1_sign == dbv2_sign)
    {
      *result_sign = dbv1_sign;
      uint64_t sum = _addcarry_u64 (0, dbv1_word[2], dbv2_word[2], (unsigned long long *) &result_word[2]);
      result_word[1] = sum;
    }
  else
    {
      if (dbv1_word[2] >= dbv2_word[2])
    {
      *result_sign = dbv1_sign;
      (void) _subborrow_u64 (0, dbv1_word[2], dbv2_word[2], (unsigned long long *) &result_word[2]);
    }
      else
    {
      *result_sign = dbv2_sign;
      (void) _subborrow_u64 (0, dbv2_word[2], dbv1_word[2], (unsigned long long *) &result_word[2]);
    }
    }

  numeric_words_to_bytes (result_word, calc_words, result_buf);

  return float_numeric_get_decimal_digit (result_word, calc_words);
}

/*
 * numeric_sub () -
 *   return:
 *   arg1(in)   : DB_C_NUMERIC
 *   arg2(in)   : DB_C_NUMERIC
 *   answer(out): DB_C_NUMERIC
 *   size(in)   : int
 *
 * Note: This routine subtracts arg2 from arg1 returns the result.
 *       It assumes that arg1 and arg2 have the same scaling.
 */
static void
numeric_sub (DB_C_NUMERIC arg1, DB_C_NUMERIC arg2, DB_C_NUMERIC answer, int size)
{
  unsigned int borrow = 0;
  unsigned int next_borrow = 0;
  int digit;

  for (digit = size - 1; digit >= 0; digit--)
    {
      next_borrow = BORROW_NEXT (arg1[digit], arg2[digit], borrow);
      answer[digit] = (uint8_t) ((unsigned) arg1[digit] - ((unsigned) arg2[digit] + borrow));
      borrow = next_borrow;
    }
}

/*
 * float_numeric_sub () -
 *   return: none
 *   arg1_word(in)   : multi-precision operand 1 (word array)
 *   arg2_word(in)   : multi-precision operand 2 (word array)
 *   result_word(out): result buffer (word array)
 *   calc_words(in)  : number of words
 *
 * Note: Performs multi-precision subtraction (arg1 - arg2) on
 *       MSB-first word arrays. The computation proceeds from least
 *       significant word to most significant word, propagating
 *       borrow across words.
 */
static void
float_numeric_sub (const uint64_t * arg1_word, const uint64_t * arg2_word, uint64_t * result_word, int calc_words)
{
  uint64_t borrow = 0;
  int digit;
  uint128_t diff;

  for (digit = calc_words - 1; digit >= 0; digit--)
    {
      diff = (uint128_t) arg1_word[digit] - arg2_word[digit] - borrow;
      result_word[digit] = (uint64_t) diff;
      borrow = (diff >> 64) ? 1 : 0;
    }
}

/*
 * float_numeric_sub_fast () -
 *   return: estimated decimal digit count of result
 *   dbv1_word(in)    : operand 1 (word array)
 *   dbv2_word(in)    : operand 2 (word array)
 *   result_word(out) : result buffer (word array)
 *   calc_words(in)   : number of words (expected 3 for fast path)
 *   dbv1_sign(in)    : sign of operand 1
 *   dbv2_sign(in)    : sign of operand 2
 *   result_sign(out) : sign of result
 *   result_buf(out)  : result in 17-byte NUMERIC format
 *
 * Note: Implements subtraction by reusing the fast addition path:
 *       A - B is transformed into A + (-B) by flipping the sign of
 *       the second operand.
 */
static int
float_numeric_sub_fast (const uint64_t * dbv1_word, const uint64_t * dbv2_word, uint64_t * result_word, int calc_words,
            bool dbv1_sign, bool dbv2_sign, bool * result_sign, uint8_t * result_buf)
{
  return float_numeric_add_fast (dbv1_word, dbv2_word, result_word, calc_words,
                 dbv1_sign, !dbv2_sign, result_sign, result_buf);
}

/*
 * numeric_mul () -
 *   return:
 *   a1(in)     : DB_C_NUMERIC
 *   a2(in)     : DB_C_NUMERIC
 *   positive_ans(out): bool if the answer's is positive (true)
 *                      or negative (false)
 *   answer(out) : DB_C_NUMERIC
 *   is_value_negative(out): sign of the answer value
 *
 * Note: This routine multiplies two numerics and returns the results.
 */
static void
numeric_mul (DB_C_NUMERIC a1, DB_C_NUMERIC a2, DB_C_NUMERIC answer, bool * is_value_negative)
{
  unsigned int answer_bit;
  int digit1;
  int digit2;
  int shift;
  unsigned char temp_term[2 * DB_NUMERIC_BUF_SIZE]; /* copy of DB_C_NUMERIC */
  unsigned char temp_arg1[2 * DB_NUMERIC_BUF_SIZE]; /* copy of DB_C_NUMERIC */
  unsigned char temp_arg2[2 * DB_NUMERIC_BUF_SIZE]; /* copy of DB_C_NUMERIC */
  unsigned char *arg1;      /* copy of DB_C_NUMERIC */
  unsigned char *arg2;      /* copy of DB_C_NUMERIC */

  assert (is_value_negative);

  /* Initialize the answer */
  numeric_zero (answer, 2 * DB_NUMERIC_BUF_SIZE);

  /* Check if either arg = 0 */
  if (numeric_is_zero (a1) || numeric_is_zero (a2))
    {
      *is_value_negative = false;
      return;
    }

  /* If arg1 is negative, toggle sign and make arg1 positive */
  arg1 = a1;
  arg2 = a2;

  /* Initialize temporary variables */
  numeric_zero (temp_arg2, DB_NUMERIC_BUF_SIZE);
  numeric_copy (temp_arg2 + DB_NUMERIC_BUF_SIZE, arg2);

  /* Loop through the 8-bit digits of temp_arg2 */
  shift = 0;
  for (digit2 = (2 * DB_NUMERIC_BUF_SIZE) - 1; digit2 >= 0; digit2--)
    {
      if (temp_arg2[digit2] != 0)
    {
      answer_bit = 0;
      numeric_shift_byte (arg1, shift, temp_arg1, 2 * DB_NUMERIC_BUF_SIZE);

      /* Loop through the 8-bit digits of temp_arg1 */
      for (digit1 = (2 * DB_NUMERIC_BUF_SIZE - 1); digit1 >= 0; digit1--)
        {
          /* the unsigned int casts are necessary here to avoid 16 bit integer overflow during the multiplication
           * on PC's */
          answer_bit =
        ((unsigned int) temp_arg1[digit1] * (unsigned int) temp_arg2[digit2]) +
        (unsigned int) CARRYOVER (answer_bit);
          temp_term[digit1] = GET_LOWER_BYTE (answer_bit);
        }
      numeric_add (temp_term, answer, answer, 2 * DB_NUMERIC_BUF_SIZE);
    }
      shift++;
    }
}

/*
 * float_numeric_mul () -
 *   return: none
 *   dbv1_word(in)   : operand 1 (word array)
 *   dbv2_word(in)   : operand 2 (word array)
 *   result_word(out): result buffer (word array)
 *   calc_words(in)  : number of words
 *   calc_nbytes(in) : number of bytes (used in non-128bit path)
 *
 * Note: Performs multi-precision multiplication using the classical
 *       O(n^2) algorithm (schoolbook method).
 *       Partial products are accumulated from least significant word
 *       to most significant word, with carry propagation across words.
 */
static void
float_numeric_mul (const uint64_t * dbv1_word, const uint64_t * dbv2_word, uint64_t * result_word, int calc_words,
           int calc_nbytes)
{
  int outer_idx, inner_idx, result_idx, carry_idx;
  int inner_min;
  uint64_t carry;
  uint128_t product, sum;

  const int last = calc_words - 1;
  /* outer: iterate dbv1 from LSW → MSW */
  for (outer_idx = last; outer_idx >= 0; outer_idx--)
    {
      if (dbv1_word[outer_idx] == 0)
    {
      continue;
    }

      carry = 0;
      inner_min = last - outer_idx;
      /* inner: accumulate partial products into result */
      for (inner_idx = last; inner_idx >= inner_min; inner_idx--)
    {
      result_idx = outer_idx + inner_idx - last;
      product = (uint128_t) dbv1_word[outer_idx] * (uint128_t) dbv2_word[inner_idx];
      sum = (uint128_t) result_word[result_idx] + (uint64_t) product + carry;
      result_word[result_idx] = (uint64_t) sum;
      carry = (uint64_t) (sum >> 64) + (uint64_t) (product >> 64);
    }

      /* propagate remaining carry to higher words */
      if (carry)
    {
      carry_idx = (outer_idx + inner_min - last) - 1;
      while (carry && carry_idx >= 0)
        {
          sum = (uint128_t) result_word[carry_idx] + carry;
          result_word[carry_idx] = (uint64_t) sum;
          carry = (uint64_t) (sum >> 64);
          --carry_idx;
        }
    }
    }
}

/*
 * float_numeric_mul_fast () -
 *   return: estimated decimal digit count of result
 *   dbv1_word(in)    : operand 1 (word array)
 *   dbv2_word(in)    : operand 2 (word array)
 *   result_word(out) : result buffer (word array)
 *   calc_words(in)   : number of words (expected 3 for fast path)
 *   result_buf(out)  : result in 17-byte NUMERIC format
 *
 * Note: Optimized multiplication for 64-bit operands (LSW only).
 *       Computes a 128-bit product and stores it into the lower
 *       two words of the result. The result is then converted to
 *       byte format and its decimal digit count is returned.
 */
static int
float_numeric_mul_fast (const uint64_t * dbv1_word, const uint64_t * dbv2_word, uint64_t * result_word, int calc_words,
            uint8_t * result_buf, int *result_scale)
{
  /* fast path: multiply least significant 64-bit words (word[2]) -> 128-bit result */
  uint128_t prod = (uint128_t) dbv1_word[2] * dbv2_word[2];

  result_word[2] = (uint64_t) prod;
  result_word[1] = (uint64_t) (prod >> 64);

  if (*result_scale > DB_MAX_NUMERIC_SCALE)
    {
      int scale_overflow = *result_scale - DB_MAX_NUMERIC_SCALE;
      *result_scale = DB_MAX_NUMERIC_SCALE;

      if (float_numeric_div_normalize (result_word, calc_words, NUMERIC_GET_BYTE_COUNT (calc_words), scale_overflow) >=
      ROUND_HALF_UP_DIGIT)
    {
      (void) float_numeric_increment (result_word, calc_words, 1);
    }
    }

  numeric_words_to_bytes (result_word, calc_words, result_buf);

  return float_numeric_get_decimal_digit (result_word, calc_words);
}

/*
 * numeric_long_div () -
 *   return:
 *   a1(in)     : DB_C_NUMERIC             (numerator)
 *   a2(in)     : DB_C_NUMERIC             (denominator)
 *   answer(in) : DB_C_NUMERIC
 *   remainder(in)      : DB_C_NUMERIC
 *   is_long_num(in)    : is a1 and answer is long NUMERIC
 *
 * Note: This routine divides two numeric values and returns the
 *       result and remainder.  This algorithm is based on the algorithm in
 *       "<Mark's Book>".
 *       Only a1(the dividend) and answer(the quotient) can be long numeric.
 */
static void
numeric_long_div (DB_C_NUMERIC a1, DB_C_NUMERIC a2, DB_C_NUMERIC answer, DB_C_NUMERIC remainder, bool is_long_num)
{
  unsigned int nbit, total_bit;
  unsigned int buf_size;
  /* the largest buf size for DB_C_NUMERIC */
  unsigned char arg1[DB_LONG_NUMERIC_MULTIPLIER * DB_NUMERIC_BUF_SIZE];
  unsigned char arg2[DB_NUMERIC_BUF_SIZE];  /* copy of a DB_C_NUMERIC */
  unsigned char neg_arg2[DB_NUMERIC_BUF_SIZE];  /* copy of a DB_C_NUMERIC */

  /* calculate basic variables */
  if (is_long_num)
    {
      buf_size = DB_NUMERIC_BUF_SIZE * DB_LONG_NUMERIC_MULTIPLIER;
    }
  else
    {
      buf_size = DB_NUMERIC_BUF_SIZE;
    }

  total_bit = buf_size * 8;

  /* Copy inputs to local variables */
  numeric_copy_long (arg1, a1, is_long_num);
  numeric_copy (arg2, a2);

  /* Initialize variables */
  numeric_coerce_int_to_num (0, remainder, NULL);
  numeric_copy_long (answer, arg1, is_long_num);

  /* Shift *answer and *remainder.  Bits shifted out of *answer * are placed into *remainder.  */
    /*****  NEEDS TO BE UPGRADED TO SHIFT SO THAT FIRST NON-ZERO BIT OF *****/
    /*****  REMAINDER IS AT LEAST EQUAL TO FIRST NON_ZERO BIT OF ARG2.  *****/
    /*****  DON'T DO THIS ONE BIT AT A TIME.                            *****/
  for (nbit = 0; nbit < total_bit; nbit++)
    {
      numeric_double_shift_bit (answer, remainder, 1, answer, remainder, is_long_num);

      /* If remainder >= arg2, subtract arg2 from remainder and increment the answer.  */
      if (numeric_compare_pos (remainder, arg2) >= 0)
    {
      numeric_sub (remainder, arg2, remainder, DB_NUMERIC_BUF_SIZE);
      answer[buf_size - 1] += 1;
    }
    }
}

/*
 * numeric_div () -
 *   return:
 *   arg1(in)   : DB_C_NUMERIC             (numerator)
 *   arg2(in)   : DB_C_NUMERIC             (denominator)
 *   answer(in) : DB_C_NUMERIC
 *   remainder(in)      : DB_C_NUMERIC
 *   arg1_value_is_negative(in): sign of the arg1 value
 *   arg2_value_is_negative(in): sign of the arg2 value
 *
 * Note: This routine divides two numeric values and returns
 *       the result and remainder.  The division is broken down into 5 cases.
 *       Given arg1/arg2:
 *       a) if arg2 = 0, then SIGFPE ??, +/- MAX_NUM_DATA ??
 *       b) if arg1 = 0, then answer = remainder = 0
 *       c) if arg1, arg2 can be represented as a int
 *                       then answer = arg1/arg2,  remainder = arg1%arg2
 *       d) Otherwise, perform long division
 */
static void
numeric_div (DB_C_NUMERIC arg1, DB_C_NUMERIC arg2, DB_C_NUMERIC answer, DB_C_NUMERIC remainder,
         bool arg1_value_is_negative, bool arg2_value_is_negative)
{
  numeric_magnitude_t divisor_mag = numeric_classify_magnitude (arg2, arg2_value_is_negative);

  /* Case 1 - arg2 = 0 */
  if (divisor_mag == NUMERIC_MAG_ZERO)
    {
      /* SIGFPE ??, +/- MAX_NUM_DATA ?? */
      return;
    }

  numeric_magnitude_t dividend_mag = numeric_classify_magnitude (arg1, arg1_value_is_negative);

  /* Case 2 - arg1 = 0.  Set answer and remainder to 0.  */
  if (dividend_mag == NUMERIC_MAG_ZERO)
    {
      numeric_coerce_int_to_num (0, remainder, NULL);
      numeric_coerce_int_to_num (0, answer, NULL);
    }

  /* Case 3 - arg1, arg2 are long ints. Do machine divide */
  else if (dividend_mag == NUMERIC_MAG_INT && divisor_mag == NUMERIC_MAG_INT)
    {
      int int_arg1, int_arg2;

      numeric_coerce_num_to_int (arg1, &int_arg1, arg1_value_is_negative);
      numeric_coerce_num_to_int (arg2, &int_arg2, arg2_value_is_negative);
      numeric_coerce_int_to_num ((int_arg1 / int_arg2), answer, NULL);
      numeric_coerce_int_to_num ((int_arg1 % int_arg2), remainder, NULL);
    }

  /* Case 4 - arg1, arg2 fit in DB_BIGINT. Do machine divide.
   * ZERO is already handled above; INT range is a subset of BIGINT range,
   * so any (INT|BIGINT) x (INT|BIGINT) combination fits in DB_BIGINT and
   * only NUMERIC needs to be excluded. */
  else if (dividend_mag != NUMERIC_MAG_NUMERIC && divisor_mag != NUMERIC_MAG_NUMERIC)
    {
      DB_BIGINT bi_arg1, bi_arg2;

      numeric_coerce_num_to_bigint (arg1, 0, &bi_arg1, arg1_value_is_negative);
      numeric_coerce_num_to_bigint (arg2, 0, &bi_arg2, arg2_value_is_negative);
      numeric_coerce_bigint_to_num ((bi_arg1 / bi_arg2), answer, NULL);
      numeric_coerce_bigint_to_num ((bi_arg1 % bi_arg2), remainder, NULL);
    }

  /* Default case: perform long division */
  else
    {
      numeric_long_div (arg1, arg2, answer, remainder, false);
    }
}

/*
 * float_numeric_div_fast () -
 *   dividend_val(in)     : 64-bit dividend
 *   divisor_val(in)      : 64-bit divisor
 *   prec1, scale1(in)    : precision/scale of dividend
 *   prec2, scale2(in)    : precision/scale of divisor
 *   exponent_diff(in)    : exponent difference (base-10)
 *   result_buf(out)      : result in 17-byte NUMERIC format
 *   result_prec_out(out) : resulting precision
 *   result_scale_out(out): resulting scale
 *
 * Note: Fast division path using __int128.
 *       Uses 64-bit mantissa with multi-precision scaling.
 *       The process consists of normalization, MSW-first division, and rounding.
 */
static void
float_numeric_div_fast (uint64_t dividend_val, uint64_t divisor_val,
            int prec1, int scale1, int prec2, int scale2,
            int exponent_diff, uint8_t * result_buf, int *result_prec_out, int *result_scale_out,
            bool * result_sign)
{
  int i;
  int result_prec, result_scale, result_digits, exponent10;
  int word_count = NUMERIC_AS_WORDS + 1;
  int word_bytes = NUMERIC_AS_WORD_BYTES + 8;
  uint128_t temp;

  /* use 4-word buffer to safely handle scaling (up to ~77 digits) */
  uint64_t dividend_word[word_count] = { 0, 0, 0, dividend_val };
  uint64_t quotient_word[word_count] = { 0 };
  uint64_t remainder_word = 0;

  /* 1) compare mantissa */
  int mantissa_compare = -1;
  int prec_diff = prec1 - prec2;
  if (prec_diff > 0)
    {
      /* align divisor to dividend precision; use uint128_t to prevent overflow (19 digits * 10^k) */
      uint128_t scaled_divisor = (uint128_t) divisor_val * _gv_mul_normalize_pow10_lookup[prec_diff - 1];
      mantissa_compare = (dividend_val >= scaled_divisor) ? 1 : -1;
    }
  else if (prec_diff < 0)
    {
      /* align dividend to divisor precision; use uint128_t to prevent overflow (19 digits * 10^k) */
      uint128_t scaled_dividend = (uint128_t) dividend_val * _gv_mul_normalize_pow10_lookup[-prec_diff - 1];
      mantissa_compare = (scaled_dividend >= divisor_val) ? 1 : -1;
    }
  else
    {
      mantissa_compare = (dividend_val >= divisor_val) ? 1 : -1;
    }

  /* 2) mantissa calculations */
  result_digits = (mantissa_compare >= 0) ? (exponent_diff + 1) : exponent_diff;
  result_scale = DB_MAX_NUMERIC_PRECISION - result_digits;
  exponent10 = result_scale + (scale2 - scale1);
  if (result_scale > DB_MAX_NUMERIC_SCALE)
    {
      int scale_overflow = DB_MAX_NUMERIC_SCALE + result_digits;
      exponent10 -= (DB_MAX_NUMERIC_PRECISION - scale_overflow);
      result_scale = DB_MAX_NUMERIC_SCALE;
    }

  /* 3) scale the dividend (normalization). exponent10 > 0 shifts up, < 0 truncates */
  if (exponent10 > 0)
    {
      float_numeric_mul_normalize (dividend_word, word_count, word_bytes, exponent10);
    }
  else if (exponent10 < 0)
    {
      /* reduces digits for normalization; does not perform rounding */
      (void) float_numeric_div_normalize (dividend_word, word_count, word_bytes, -exponent10);
    }

  /* 4) division */
  for (i = 0; i < word_count; i++)
    {
      temp = ((uint128_t) remainder_word << 64) | dividend_word[i];
      quotient_word[i] = (uint64_t) (temp / divisor_val);
      remainder_word = (uint64_t) (temp % divisor_val);
    }

  /* 5) round up if necessary */
  if (((uint128_t) remainder_word * 2) >= divisor_val)
    {
      float_numeric_increment (quotient_word, word_count, 1);
    }

  /* 6) round and pack to DB_NUMERIC_BUF_SIZE bytes */
  result_prec = float_numeric_get_decimal_digit (quotient_word, word_count);
  if (*result_sign && result_prec == 1 && quotient_word[word_count - 1] == 0)
    {
      /* Prevent -0; zero is always treated as positive. */
      *result_sign = false;
    }
  /*
   * Ignoring the return value here is intentional: any scale boundary overflow
   * from round-carry is surfaced by the caller's float_numeric_check_overflow_and_adjust_scale().
   */
  (void) float_numeric_round_and_pack (quotient_word, word_count, word_bytes, result_buf, &result_prec, &result_scale);
  *result_prec_out = result_prec;
  *result_scale_out = result_scale;
}

/*
 * float_numeric_mod_fast () -
 *   dividend_val(in)     : 64-bit dividend
 *   divisor_val(in)      : 64-bit divisor
 *   result_buf(out)      : result in 17-byte NUMERIC format
 *   result_prec_out(out) : resulting precision
 *   result_scale_out(in/out): scale (preserved)
 *
 * Note: Fast modulo path using __int128.
 *       Computes remainder using 64-bit arithmetic and stores it
 *       in NUMERIC format.
 */
static void
float_numeric_mod_fast (uint64_t dividend_val, uint64_t divisor_val,
            uint8_t * result_buf, int *result_prec_out, int *result_scale_out, bool * result_sign)
{
  assert (divisor_val != 0);

  int result_scale = *result_scale_out;
  uint64_t remainder = dividend_val % divisor_val;

  /*
   * although the remainder is 64-bit (8 bytes),
   * it must be stored in a 3-word (192-bit) buffer to conform to
   * CUBRID's 17-byte NUMERIC layout.
   *
   * due to numeric_words_to_bytes() mapping, the least significant
   * 8 bytes correspond to word[2], so the remainder is placed there.
   */
  uint64_t result_word[NUMERIC_AS_WORDS] = { 0, 0, remainder };

  int result_prec = float_numeric_get_decimal_digit (result_word, NUMERIC_AS_WORDS);
  if (*result_sign && result_prec == 1 && result_word[NUMERIC_AS_WORDS - 1] == 0)
    {
      /* Prevent -0; zero is always treated as positive. */
      *result_sign = false;
    }
  /*
   * Ignoring the return value here is intentional: any scale boundary overflow
   * from round-carry is surfaced by the caller's float_numeric_check_overflow_and_adjust_scale().
   */
  (void) float_numeric_round_and_pack (result_word, NUMERIC_AS_WORDS, NUMERIC_AS_WORD_BYTES,
                       result_buf, &result_prec, &result_scale);
  *result_prec_out = result_prec;
  *result_scale_out = result_scale;
}

/*
 * knuth_find_first_nz_idx() - Find the index of the first non-zero word (MSB-first)
 *   return          : Index of the first non-zero word, or total_digits if all are zero
 *   digit_buf(in)   : Array of knuth digits stored in MSB-first order
 *   total_digits(in): Total number of words in the buffer
 */
static int
knuth_find_first_nz_idx (const knuth_digit_t * digit_buf, int total_digits)
{
  int i;

  for (i = 0; i < total_digits; i++)
    {
      if (digit_buf[i] != 0)
    {
      return i;
    }
    }
  return total_digits;
}

/*
 * knuth_count_leading_zero_bits() - Count the number of leading zero bits in a knuth digit
 *   return        : Number of leading zero bits (0 ~ KNUTH_DIGIT_BITS)
 *   value(in) : Word (32-bit or 64-bit) to be examined
 *
 * Note : To quickly determine how many bits the divisor V
 *        must be shifted left so that its most significant
 *        digit (MSD) becomes normalized (MSB = 1).
 */
static unsigned int
knuth_count_leading_zero_bits (knuth_digit_t value)
{
  if (value == 0)
    {
      return KNUTH_DIGIT_BITS;
    }

  return (unsigned int) NUMERIC_CLZ (value);
}

/*
 * knuth_normalize_left_shift_msb() - Left shift an MSB-first word buffer by k bits
 *   return        : void
 *   buffer(in/out): Word array stored in MSB-first order (modified in place)
 *   buffer_size(in): Number of words in the buffer
 *   k_bit(in)     : Number of bits to shift (0..KNUTH_DIGIT_BITS-1)
 *
 * Note : Used in the normalization step of Knuth’s division algorithm
 *        to align the divisor or dividend at the bit level.
 */
static void
knuth_normalize_left_shift_msb (knuth_digit_t * buffer, int buffer_size, unsigned int k_bit)
{
  if (k_bit == 0)
    {
      return;
    }

  int i;
  for (i = 0; i < buffer_size - 1; i++)
    {
      buffer[i] = (buffer[i] << k_bit) | (buffer[i + 1] >> (KNUTH_DIGIT_BITS - k_bit));
    }
  buffer[buffer_size - 1] <<= k_bit;
}

/*
 * knuth_normalize_right_shift_msb() - Right shift an MSB-first word buffer by k bits
 *   return        : void
 *   buffer(in/out): Word array stored in MSB-first order (modified in place)
 *   buffer_size(in): Number of words in the buffer
 *   k_bit(in)     : Number of bits to shift (0..KNUTH_DIGIT_BITS-1)
 *
 * Note: Used in the denormalization step of Knuth’s division algorithm,
 *       when restoring the remainder by shifting back.
 */
static void
knuth_normalize_right_shift_msb (knuth_digit_t * buffer, int buffer_size, unsigned int k_bit)
{
  if (k_bit == 0)
    {
      return;
    }

  knuth_digit_t carry = 0;
  knuth_digit_t next = 0;
  int i = 0;

  for (i = 0; i < buffer_size; i++)
    {
      next = buffer[i] & (((knuth_digit_t) 1 << k_bit) - 1);
      buffer[i] = (buffer[i] >> k_bit) | (carry << (KNUTH_DIGIT_BITS - k_bit));
      carry = next;
    }
}

/*
 * knuth_estimate_quotient_digit() - Knuth’s division algorithm, steps D2–D3: trial quotient estimation and adjustment
 *   return             : Final trial_quotient (0..KNUTH_BASE-1)
 *   u_work(in)         : Dividend work array (MSB-first)
 *   v_work(in)         : Divisor work array (MSB-first)
 *   window_offset(in)  : Starting offset in dividend for quotient estimation
 *   dividend_words(in) : Size of dividend in words
 *   divisor_words(in)  : Size of divisor in words
 *   out_trial_remainder(out): Trial remainder after estimation
 *
 * D2 (Estimation):
 *   - Use the top 2 digits of U (u_high, u_next) to form a double-length numerator
 *   - Compute trial_quotient = numerator / v_high
 *   - Compute trial_remainder = numerator % v_high
 *
 * D3 (Adjustment):
 *   - While trial_quotient * v_next > trial_remainder * KNUTH_BASE + u_next2,
 *     decrement trial_quotient by 1 and update trial_remainder
 *   - Repeat until the condition is satisfied
 *
 * Note:
 *   - The trial_quotient may be as large as (2 * KNUTH_BASE - 1), but each quotient digit must lie in [0..KNUTH_BASE-1].
 *     Therefore, if >= KNUTH_BASE, it is clamped to KNUTH_BASE - 1.
 *   - This step is essential in Knuth’s division to ensure each quotient digit is safely and correctly determined.
 */
static knuth_digit_t
knuth_estimate_quotient_digit (const knuth_digit_t * u_work, const knuth_digit_t * v_work, int window_offset,
                   int dividend_words, int divisor_words, knuth_digit_t * out_trial_remainder)
{
  knuth_digit_t u_high, u_next, u_next2;
  knuth_digit_t v_high, v_next;
  knuth_double_digit_t trial_quotient, trial_remainder;
  knuth_double_digit_t numerator;

  /* extract the top 3 words from U and top 2 words from V */
  u_high = u_work[window_offset];
  u_next = u_work[window_offset + 1];
  u_next2 = (window_offset + 2 < dividend_words + 1) ? u_work[window_offset + 2] : 0;

  v_high = v_work[0];
  v_next = (divisor_words > 1) ? v_work[1] : 0;

  /* D2: quotient digit estimation */
  numerator = ((knuth_double_digit_t) u_high << KNUTH_DIGIT_BITS) | (knuth_double_digit_t) u_next;
  trial_quotient = numerator / v_high;  // may be as large as (2 * KNUTH_BASE - 1)
  trial_remainder = numerator % v_high; // ranges up to v_high - 1

  if (divisor_words > 1)
    {
      /* D3: quotient correction */
      while ((trial_quotient == KNUTH_BASE) || (trial_quotient * v_next > (trial_remainder * KNUTH_BASE + u_next2)))
    {
      --trial_quotient;
      trial_remainder += v_high;
      if (trial_remainder >= KNUTH_BASE)
        {
          break;
        }
    }
    }

  if (trial_quotient >= KNUTH_BASE)
    {
      trial_quotient = KNUTH_BASE - 1;
    }

  *out_trial_remainder = (knuth_digit_t) trial_remainder;
  return (knuth_digit_t) trial_quotient;
}

/*
 * knuth_multiply_and_subtract() 
 *   - Knuth’s division algorithm, steps D4–D6: apply q·V to the dividend window U by subtraction, and perform add-back if needed
 *   return             : Final q (decremented by 1 if add-back occurred)
 *   u_work(in/out)     : Dividend work array U (MSB-first, modified in place)
 *   v_work(in)         : Divisor work array V (MSB-first)
 *   window_offset(in)  : Starting offset of the U window to be updated
 *   divisor_words(in)  : Number of words in the divisor V
 *   trial_quotient(in) : Trial quotient estimated in D2–D3
 *
 * D4 (Multiply & Subtract):
 *   - Compute trial_quotient * V
 *   - Subtract it from U window while propagating carry and borrow
 *
 * D5 (Update topmost word & Test remainder):
 *   - Apply remaining carry and borrow to the topmost word of the U window
 *   - Check if a borrow occurred (result is negative)
 *
 * D6 (Add-back correction):
 *   - If borrow occurred in the topmost word, decrement trial_quotient (q-1)
 *   - Add V back to the U window to restore the remainder
 *
 * Note:
 *   - At most one add-back is needed
 */
static knuth_digit_t
knuth_multiply_and_subtract (knuth_digit_t * u_work, const knuth_digit_t * v_work, int window_offset, int divisor_words,
                 knuth_digit_t trial_quotient)
{
  knuth_double_digit_t product;
  knuth_digit_t carry = 0;
  knuth_digit_t borrow = 0;
  knuth_digit_t temp_u;
  knuth_digit_t sub_val;
  int i;

  /* D4: multiply & subtract */
  for (i = divisor_words - 1; i >= 0; i--)
    {
      product = (knuth_double_digit_t) trial_quotient *v_work[i] + carry;
      carry = (knuth_digit_t) (product >> KNUTH_DIGIT_BITS);

      temp_u = u_work[window_offset + i + 1];
      sub_val = (knuth_digit_t) product;

      if ((knuth_double_digit_t) temp_u < (knuth_double_digit_t) sub_val + borrow)
    {
      u_work[window_offset + i + 1] = temp_u - sub_val - borrow;
      borrow = 1;
    }
      else
    {
      u_work[window_offset + i + 1] = temp_u - sub_val - borrow;
      borrow = 0;
    }
    }

  /* D5: update topmost word & test remainder */
  if (u_work[window_offset] < (knuth_double_digit_t) carry + borrow)
    {
      /* D6: add-back correction */
      u_work[window_offset] = u_work[window_offset] - carry - borrow;

      trial_quotient--;

      knuth_double_digit_t sum;
      knuth_digit_t add_carry = 0;
      for (i = divisor_words - 1; i >= 0; i--)
    {
      sum = (knuth_double_digit_t) u_work[window_offset + i + 1] + v_work[i] + add_carry;
      u_work[window_offset + i + 1] = (knuth_digit_t) sum;
      add_carry = (knuth_digit_t) (sum >> KNUTH_DIGIT_BITS);
    }
      u_work[window_offset] += add_carry;
    }
  else
    {
      /* No add-back needed, just subtract remaining carry/borrow from U[j] */
      u_work[window_offset] -= (carry + borrow);
    }

  return trial_quotient;
}

/*
 * float_numeric_knuth_div() - Divide two big-endian numeric word buffers using
 *                             Knuth's long division algorithm (steps D1–D8)
 *   dbv1_buf(in) : Dividend buffer (MSB-first)
 *   dbv2_buf(in) : Divisor buffer (MSB-first)
 *   quo_buf(out) : Quotient buffer (MSB-first, right-justified)
 *   rem_buf(out) : Remainder buffer (MSB-first, right-justified)
 *   calc_words(in): Working precision in words
 *
 * Note :
 *   1) compute significant (non-zero) word lengths (not part of Knuth D-steps)
 *   2) prepare working buffers
 *   3) D1 normalize
 *   4) D2–D7 main loop (per quotient digit)
 *      - D2–D3: estimate and adjust trial quotient
 *      - D4–D6: subtract q*V from U window. if borrow, add back
 *      - D7: store the finalized quotient digit
 *   5) D8 denormalize
 *   6) extract remainder
 */
static void
float_numeric_knuth_div (knuth_digit_t * dbv1_buf, knuth_digit_t * dbv2_buf, knuth_digit_t * quo_buf,
             knuth_digit_t * rem_buf, knuth_digit_t calc_words)
{
  int dividend_first_nz_index = 0;
  int divisor_first_nz_index = 0;
  int dividend_words = 0;
  int divisor_words = 0;
  int len_diff = 0;

  /*
   * 1) Compute significant (non-zero) word lengths (not part of Knuth D-steps)
   * - Risk if we normalize (D1) directly on the MSB-first buffer:
   *   Skipping leading zero words means shifting in multiples of KNUTH_DIGIT_BITS,
   *   which scales the value by KNUTH_BASE^k and distorts it.
   * - First find the index of the first non-zero word as the proper base.
   */
  dividend_first_nz_index = knuth_find_first_nz_idx (dbv1_buf, calc_words);
  divisor_first_nz_index = knuth_find_first_nz_idx (dbv2_buf, calc_words);
  dividend_words = calc_words - dividend_first_nz_index;
  divisor_words = calc_words - divisor_first_nz_index;

  /*
   * early exit: if dividend < divisor -> quotient = 0, remainder = dividend
   * - First compare by significant byte length
   * - If equal, compare the same-length region with the appropriate compare helper
   *   with float_numeric_operation_compare()
   */
  len_diff = dividend_words - divisor_words;
  if (len_diff < 0
      || (len_diff == 0
      && float_numeric_operation_compare (dbv1_buf + dividend_first_nz_index, dbv2_buf + divisor_first_nz_index,
                          divisor_words) < 0))
    {
      /* quo_buf is already zero-initialized outside */
      memcpy (rem_buf, dbv1_buf, calc_words * sizeof (knuth_digit_t));
      return;
    }

  /* 2) prepare working buffers */
  // U: dividend working buffer (n+1 words, with leading zero space)
  knuth_digit_t u_work[dividend_words + 1];
  u_work[0] = 0;
  // V: divisor working buffer (n words)
  knuth_digit_t v_work[divisor_words];

  memcpy (u_work + 1, dbv1_buf + dividend_first_nz_index, dividend_words * sizeof (knuth_digit_t));
  memcpy (v_work, dbv2_buf + divisor_first_nz_index, divisor_words * sizeof (knuth_digit_t));

  /* 3) D1 normalize */
  unsigned int normalization_bit_count = (divisor_words > 0) ? knuth_count_leading_zero_bits (v_work[0]) : 0;
  if (normalization_bit_count)
    {
      knuth_normalize_left_shift_msb (v_work, divisor_words, normalization_bit_count);
      knuth_normalize_left_shift_msb (u_work, dividend_words + 1, normalization_bit_count);
    }
  unsigned int saved_normalization_bit_count = normalization_bit_count;

  int quo_window_count = len_diff;  // number of quotient digits - 1
  int quo_total_word_count = quo_window_count + 1;  // total number of quotient digits
  int quo_store_start_idx = calc_words - quo_total_word_count;  // starting index for writing quotient in right-justified form
  int quo_digit_index = 0;
  int window_offset = 0;
  knuth_digit_t trial_remainder = 0;
  knuth_digit_t trial_quotient = 0;

  /* 4) D2–D7 main loop (per quotient digit) */
  for (quo_digit_index = 0; quo_digit_index <= quo_window_count; quo_digit_index++)
    {
      window_offset = quo_digit_index;

      /* D2–D3: estimate and adjust trial quotient */
      trial_quotient =
    knuth_estimate_quotient_digit (u_work, v_work, window_offset, dividend_words, divisor_words, &trial_remainder);

      /* D4–D6: subtract q*V from U window. if borrow, add back */
      trial_quotient = knuth_multiply_and_subtract (u_work, v_work, window_offset, divisor_words, trial_quotient);

      /* D7: store the finalized quotient digit */
      quo_buf[quo_store_start_idx + quo_digit_index] = trial_quotient;
    }

  /* 5) D8 denormalize */
  if (saved_normalization_bit_count)
    {
      knuth_normalize_right_shift_msb (u_work, dividend_words + 1, saved_normalization_bit_count);
    }

  /* 6) extract remainder */
  int out_rem_idx = calc_words - divisor_words; // remainder also right-justified
  int i = 0;
  for (i = 0; i < divisor_words; i++)
    {
      rem_buf[out_rem_idx + i] = u_work[quo_total_word_count + i];
    }
}

/*
 * numeric_is_longnum_value ()
 *   return:
 *   arg(in)   : DB_C_NUMERIC
 *
 * Note: This routine check whether the value numeric is long NUMERIC.
 *       Attention: the arg should be long NUMERIC.
 */
static bool
numeric_is_longnum_value (DB_C_NUMERIC arg)
{
  int total_nums = (DB_LONG_NUMERIC_MULTIPLIER - 1) * DB_NUMERIC_BUF_SIZE;
  int i;

  for (i = 0; i < total_nums; i++)
    {
      if (arg[i] != 0)
    {
      return true;
    }
    }

  return false;
}

/*
 * numeric_shortnum_to_longnum ()
 *   return:
 *   long_answer(out): the long NUMERIC
 *   arg(in)         : DB_C_NUMERIC
 *
 * Note: This routine translate a normal NUMERIC to long NUMERIC.
 *       Attention: the long_answer should be long NUMERIC.
 */
static void
numeric_shortnum_to_longnum (DB_C_NUMERIC long_answer, DB_C_NUMERIC arg)
{
  int i;

  for (i = 0; i < DB_LONG_NUMERIC_MULTIPLIER - 1; i++)
    {
      numeric_zero (long_answer + i * DB_NUMERIC_BUF_SIZE, DB_NUMERIC_BUF_SIZE);
    }
  numeric_copy (long_answer + i * DB_NUMERIC_BUF_SIZE, arg);
}


/*
 * numeric_longnum_to_shortnum ()
 *   return:
 *  answer(out): DB_C_NUMERIC
 *   arg(in)   : long NUMERIC
 *
 * Note: This routine translate a long NUMERIC to normal NUMERIC.
 *       Attention: the long_answer should be long NUMERIC.
 */
static int
numeric_longnum_to_shortnum (DB_C_NUMERIC answer, DB_C_NUMERIC long_arg)
{
  if (numeric_is_longnum_value (long_arg))
    {
      return ER_IT_DATA_OVERFLOW;
    }

  numeric_copy (answer, long_arg + (DB_LONG_NUMERIC_MULTIPLIER - 1) * DB_NUMERIC_BUF_SIZE);
  return NO_ERROR;
}

/*
 * float_numeric_compare() - Compare two numeric values (byte-buffer form)
 *   return: -1 if arg1 < arg2, 0 if equal, +1 if arg1 > arg2
 */
static int
float_numeric_compare (uint8_t * arg1, uint8_t * arg2, int prec1, int scale1, int prec2, int scale2, bool arg1_sign,
               bool arg2_sign)
{
  int common_prec, common_scale;
  int scale_adjust1, scale_adjust2;
  int needed_bytes, calc_words, calc_nbytes;
  int pad;
  int cmp_rez = 0;

  /* this comparison function requires that arg1 and arg2 always have the same sign. */
  assert (arg1_sign == arg2_sign);

  common_scale = MAX (scale1, scale2);
  common_prec = MAX (prec1 - scale1, prec2 - scale2) + common_scale;
  scale_adjust1 = common_scale - scale1;
  scale_adjust2 = common_scale - scale2;

  needed_bytes = MAX (_gv_numeric_precision_to_bytes_lookup[common_prec], DB_NUMERIC_BUF_SIZE) + 1;
  calc_words = NUMERIC_GET_WORD_COUNT (needed_bytes);
  calc_nbytes = NUMERIC_GET_BYTE_COUNT (calc_words);

  uint64_t arg1_buf[calc_words] = { 0 };
  uint64_t arg2_buf[calc_words] = { 0 };

  numeric_bytes_to_words (arg1, DB_NUMERIC_BUF_SIZE, arg1_buf, calc_words, calc_nbytes);
  numeric_bytes_to_words (arg2, DB_NUMERIC_BUF_SIZE, arg2_buf, calc_words, calc_nbytes);

  if (scale_adjust1)
    {
      float_numeric_mul_normalize (arg1_buf, calc_words, calc_nbytes, scale_adjust1);
    }
  if (scale_adjust2)
    {
      float_numeric_mul_normalize (arg2_buf, calc_words, calc_nbytes, scale_adjust2);
    }

  /* since we don't convert to absolute values when comparing negative numbers, there's no need to invert the result again */
  cmp_rez = float_numeric_operation_compare (arg1_buf, arg2_buf, calc_words);
  if (arg1_sign)
    {
      cmp_rez = -cmp_rez;
    }

  return cmp_rez;
}

/*
 * numeric_scale_by_ten () -
 *   return: NO_ERROR, or ER_code (ER_IT_DATA_OVERFLOW)
 *   arg(in/out)    : ptr to a DB_NUMERIC structure
 *   is_long_num(in): is long NUMERIC
 *
 * Note: This routine scales arg by a factor of ten.
 */
static int
numeric_scale_by_ten (DB_C_NUMERIC arg, bool is_long_num)
{
  int i, answer;

  answer = 0;

  if (is_long_num)
    {
      i = DB_NUMERIC_BUF_SIZE * DB_LONG_NUMERIC_MULTIPLIER;
    }
  else
    {
      i = DB_NUMERIC_BUF_SIZE;
    }
  while (i--)
    {
      answer = (10 * arg[i]) + CARRYOVER (answer);
      arg[i] = GET_LOWER_BYTE (answer);
    }

  if ((int) arg[0] > 0x7f)
    {
      return ER_IT_DATA_OVERFLOW;
    }

  return NO_ERROR;
}

/*
 * numeric_scale_dec () -
 *   return: NO_ERROR, or ER_code
 *   arg(in)    : ptr to a DB_C_NUMERIC structure
 *   dscale(in) : integer scaling factor (positive)
 *   answer(in) : ptr to a DB_C_NUMERIC structure
 *
 * Note: This routine returns a numeric value that has been scaled by the
 *       given number of decimal places.  The result is returned in answer.
 */
static int
numeric_scale_dec (const DB_C_NUMERIC arg, int dscale, DB_C_NUMERIC answer)
{
  int ret = NO_ERROR;

  if (dscale >= 0)
    {
      numeric_copy (answer, arg);
      ret = numeric_scale_dec_long (answer, dscale, false);
    }

  return ret;
}

/*
 * numeric_scale_dec_long () -
 *   return: NO_ERROR, or ER_code
 *   answer(in/out) : ptr to a DB_C_NUMERIC structure
 *   dscale(in) : integer scaling factor (positive)
 *   is_long_num: is long NUMERIC
 *
 * Note: This routine returns a numeric value that has been scaled by the
 *       given number of decimal places.  The result is returned in answer.
 */
static int
numeric_scale_dec_long (DB_C_NUMERIC answer, int dscale, bool is_long_num)
{
  int loop;
  int ret = NO_ERROR;

  if (dscale >= 0)
    {
      for (loop = 0; loop < dscale && ret == NO_ERROR; loop++)
    {
      ret = numeric_scale_by_ten (answer, is_long_num);
    }
      if (ret != NO_ERROR)
    {
      return ret;
    }
    }

  return ret;
}

/*
 * numeric_common_prec_scale () -
 *   return: NO_ERROR, or ER_code
 *     Errors:
 *       ER_IT_DATA_OVERFLOW          - if scaling would exceed max scale
 *   dbv1(in): ptr to a DB_VALUE structure of type DB_TYPE_NUMERIC
 *   dbv2(in): ptr to a DB_VALUE structure of type DB_TYPE_NUMERIC
 *   dbv1_common(out): ptr to a DB_VALUE structure of type DB_TYPE_NUMERIC
 *   dbv2_common(out): ptr to a DB_VALUE structure of type DB_TYPE_NUMERIC
 *
 * Note: This routine returns two DB_VALUE's of type numeric with the same
 *       scale.  dbv1_common, dbv2_common are set to dbv1, dbv2 respectively
 *       when an error occurs.
 */
static int
numeric_common_prec_scale (const DB_VALUE * dbv1, const DB_VALUE * dbv2, DB_VALUE * dbv1_common, DB_VALUE * dbv2_common)
{
  unsigned char temp[DB_NUMERIC_BUF_SIZE];  /* copy of a DB_C_NUMERIC */
  int scale1, scale2;
  int prec1, prec2;
  int cprec;
  int scale_diff;
  TP_DOMAIN *domain;

  /* If scales already match, merely copy them and return */
  db_get_numeric_precision_and_scale (dbv1, &prec1, &scale1, NULL);
  db_get_numeric_precision_and_scale (dbv2, &prec2, &scale2, NULL);
  if (scale1 == scale2)
    {
      cprec = MAX (prec1, prec2);
      db_make_numeric (dbv1_common, db_locate_numeric (dbv1), cprec, scale1, DB_NUMERIC_BUF_SIZE,
               numeric_is_negative (dbv1), false);
      db_make_numeric (dbv2_common, db_locate_numeric (dbv2), cprec, scale2, DB_NUMERIC_BUF_SIZE,
               numeric_is_negative (dbv2), false);
    }

  /* Otherwise scale and reset the numbers */
  else if (scale1 < scale2)
    {
      scale_diff = scale2 - scale1;
      prec1 = scale_diff + prec1;
      if (prec1 > DB_MAX_FIXED_NUMERIC_PRECISION)
    {
      domain = tp_domain_resolve_default (DB_TYPE_NUMERIC);
      er_set (ER_WARNING_SEVERITY, ARG_FILE_LINE, ER_IT_DATA_OVERFLOW, 1, pr_type_name (TP_DOMAIN_TYPE (domain)));
      return ER_IT_DATA_OVERFLOW;
    }
      numeric_scale_dec (db_locate_numeric (dbv1), scale_diff, temp);
      cprec = MAX (prec1, prec2);
      db_make_numeric (dbv1_common, temp, cprec, scale2, DB_NUMERIC_BUF_SIZE, numeric_is_negative (dbv1), false);
      db_make_numeric (dbv2_common, db_locate_numeric (dbv2), cprec, scale2, DB_NUMERIC_BUF_SIZE,
               numeric_is_negative (dbv2), false);
    }
  else
    {
      scale_diff = scale1 - scale2;
      prec2 = scale_diff + prec2;
      if (prec2 > DB_MAX_FIXED_NUMERIC_PRECISION)
    {
      domain = tp_domain_resolve_default (DB_TYPE_NUMERIC);
      er_set (ER_WARNING_SEVERITY, ARG_FILE_LINE, ER_IT_DATA_OVERFLOW, 1, pr_type_name (TP_DOMAIN_TYPE (domain)));
      return ER_IT_DATA_OVERFLOW;
    }
      numeric_scale_dec (db_locate_numeric (dbv2), scale_diff, temp);
      cprec = MAX (prec1, prec2);
      db_make_numeric (dbv2_common, temp, cprec, scale1, DB_NUMERIC_BUF_SIZE, numeric_is_negative (dbv2), false);
      db_make_numeric (dbv1_common, db_locate_numeric (dbv1), cprec, scale1, DB_NUMERIC_BUF_SIZE,
               numeric_is_negative (dbv1), false);
    }

  return NO_ERROR;
}

/*
 * numeric_prec_scale_when_overflow () -
 *   return: NO_ERROR, or ER_code
 *   dbv1(in)   :
 *   dbv2(in)   :
 *   dbv1_common(out)    :
 *   dbv2_common(out)    :
 */
static int
numeric_prec_scale_when_overflow (const DB_VALUE * dbv1, const DB_VALUE * dbv2, DB_VALUE * dbv1_common,
                  DB_VALUE * dbv2_common)
{
  int prec1, scale1, prec2, scale2;
  int prec, scale;
  unsigned char temp[DB_NUMERIC_BUF_SIZE];
  int ret;
  bool dbv1_is_negative = false, dbv2_is_negative = false;

  db_get_numeric_precision_and_scale (dbv1, &prec1, &scale1, NULL);
  db_get_numeric_precision_and_scale (dbv2, &prec2, &scale2, NULL);

  scale = MAX (scale1, scale2);
  prec = DB_MAX_FIXED_NUMERIC_PRECISION;

  ret = numeric_coerce_num_to_num (dbv1, prec1, scale1, prec, scale, temp, &dbv1_is_negative);
  if (ret != NO_ERROR)
    {
      return ret;
    }
  db_make_numeric (dbv1_common, temp, prec, scale, DB_NUMERIC_BUF_SIZE, dbv1_is_negative, false);

  ret = numeric_coerce_num_to_num (dbv2, prec2, scale2, prec, scale, temp, &dbv2_is_negative);
  if (ret != NO_ERROR)
    {
      return ret;
    }
  db_make_numeric (dbv2_common, temp, prec, scale, DB_NUMERIC_BUF_SIZE, dbv2_is_negative, false);

  return ret;
}

/*
 * numeric_coerce_big_num_to_dec_str () - Convert a large numeric buffer to a decimal string
 *   return      : void
 *   num(in)     : Input buffer (size: 2 * DB_NUMERIC_BUF_SIZE)
 *   dec_str(out): Output string (size: TWICE_NUM_MAX_PREC + 1)
 *
 * Note:
 *   - Converts a double-sized numeric buffer into a decimal string (ASCII).
 *   - The process involves converting bytes to 64-bit words and performing 
 *     successive division by 10^16 to extract 16-digit blocks from right to left.
 *   - ASSUMPTION: The input buffer represents a positive value.
 */
static void
numeric_coerce_big_num_to_dec_str (unsigned char *num, char *dec_str)
{
  /* 
   * big numeric buffer size = 17 * 2 = 34 bytes.
   * to fit this into 64-bit words: ceil(34 / 8) = 5 words (40 bytes).
   */
  uint64_t word_buf[5] = { 0 };
  int calc_words = 5;
  int calc_nbytes = 40;
  uint64_t chunk;
  char *p_digits;
  int i, j;
  bool all_zero = true;

  assert (num);
  assert (dec_str);

  /* 1. pre-fill with '0' for legacy alignment */
  memset (dec_str, '0', TWICE_NUM_MAX_PREC);
  dec_str[TWICE_NUM_MAX_PREC] = '\0';

  /* 2. copy source to a working buffer for successive division. */
  numeric_bytes_to_words (num, DB_NUMERIC_BUF_SIZE * 2, word_buf, calc_words, calc_nbytes);

  /* 3. check if the entire buffer is 0 */
  for (i = 0; i < calc_words; i++)
    {
      if (word_buf[i] != 0)
    {
      all_zero = false;
      break;
    }
    }

  if (all_zero)
    {
      return;
    }

  /* 4. extract digits in 16-digit blocks from right to left (up to 6 iterations, covers 96 digits > Big Num 82 digits) */
  p_digits = dec_str + TWICE_NUM_MAX_PREC - 16;

  /* 
   * maximum decimal digits for 34-byte Big Num is ~ 82.
   * number of 16-digit chunks to cover it: ceil(82 / 16) = 6. 
   */
  const int max_chunks = 6;
  for (i = 0; i < max_chunks; i++)
    {
      /* extract the remainder of division by 10^16 as a 64-bit integer chunk. */
      chunk = float_numeric_div_pow10 (word_buf, calc_words, calc_nbytes, _gv_mul_normalize_pow10_lookup[15]);

      /* convert and store 16 decimal digits as ASCII at once */
      numeric_pack_digits4_ascii (p_digits, chunk);
      p_digits -= 16;

      /* early exit if no more digits remain (quotient is zero) */
      all_zero = true;
      for (j = 0; j < calc_words; j++)
    {
      if (word_buf[j] != 0)
        {
          all_zero = false;
          break;
        }
    }

      if (all_zero)
    {
      break;
    }
    }
}

/*
 * numeric_get_msb_for_dec () -
 *   return:
 *     Errors:
 *       ER_IT_DATA_OVERFLOW          - if src exceeds max precision
 *   src_prec(in)       : int precision of src
 *   src_scale(in)      : int scale of src
 *   src(in)    : buffer to NUMERIC twice the length of the maximum
 *   dest_prec(out)      : ptr to a int precision of dest
 *   dest_scale(out)     : ptr to a int scale of dest
 *   dest(out)   : DB_C_NUMERIC
 *
 * Note: This routine returns a DB_C_NUMERIC along with the precision and
 *       scale of the MSB of the source.  Round-off occurs as long as the scale
 *       of the destination >= 0.
 * Note: it is assumed that src represents a positive number
 */
static int
numeric_get_msb_for_dec (int src_prec, int src_scale, unsigned char *src, int *dest_prec, int *dest_scale,
             DB_C_NUMERIC dest)
{
  int ret = NO_ERROR;
  char dec_digits[TWICE_NUM_MAX_PREC + 2];

  /* If src precision fits without truncation, merely set dest to the lower half of the source buffer and return */
  if (src_prec <= DB_MAX_FIXED_NUMERIC_PRECISION)
    {
      numeric_copy (dest, &(src[DB_NUMERIC_BUF_SIZE]));
      *dest_prec = src_prec;
      *dest_scale = src_scale;
    }

  /* The remaining cases are for when the precision of the source overflows. */

  /* Case 1: The scale of the source does *not* overflow */
  else if (src_scale <= DB_MAX_FIXED_NUMERIC_PRECISION)
    {
      /* If upper half of *src is zero, merely copy, reset precision, and return */
      if (numeric_is_zero (src) && src[DB_NUMERIC_BUF_SIZE] <= 0x7F)
    {
      numeric_copy (dest, &(src[DB_NUMERIC_BUF_SIZE]));
      *dest_prec = DB_MAX_FIXED_NUMERIC_PRECISION;
      *dest_scale = src_scale;
    }
      else
    {
      /* Can't truncate answer - expected results must maintain the proper amount of scaling */
      return ER_IT_DATA_OVERFLOW;
    }
    }

  /* Case 2: The scale of the source overflows. This means the number can't overflow as long as truncation occurs.
   * Reduce the scale and precision by the same amount. */
  else
    {
      int truncation_diff = src_prec - DB_MAX_FIXED_NUMERIC_PRECISION;

      *dest_scale = src_scale - truncation_diff;
      *dest_prec = DB_MAX_FIXED_NUMERIC_PRECISION;

      /* Truncate the obsolete trailing digits. (Note: numeric_coerce_big_num_to_dec_str is guaranteed ro return a
       * NULL-terminated buffer that is TWICE_NUM_MAX_PREC characters long.) */
      numeric_coerce_big_num_to_dec_str (src, dec_digits);
      dec_digits[TWICE_NUM_MAX_PREC - truncation_diff] = '\0';
      numeric_coerce_dec_str_to_num (dec_digits, dest, NULL);
    }

  return ret;
}

/*
 * numeric_db_value_add () -
 *   return: NO_ERROR, or ER_code
 *     Errors:
 *       ER_OBJ_INVALID_ARGUMENTS - if dbv1, dbv2, or answer are NULL or
 *                                  are not DB_TYPE_NUMERIC
 *   dbv1(in)   : ptr to a DB_VALUE structure of type DB_TYPE_NUMERIC
 *   dbv2(in)   : ptr to a DB_VALUE structure of type DB_TYPE_NUMERIC
 *   answer(out): ptr to a DB_VALUE structure of type DB_TYPE_NUMERIC
 *
 * Note: This routine adds the numeric values of two DB_VALUE structs and
 * returns the results in answer. The answer will be returned as either the
 * common type of dbv1 and dbv2 or as the common type of dbv1 and dbv2 with
 * an extra decimal place of precision if the sum requires it due to carry.
 * The answer is set to a NULL-valued DB_C_NUMERIC's when an error occurs.
 *
 */
int
numeric_db_value_add (const DB_VALUE * dbv1, const DB_VALUE * dbv2, DB_VALUE * answer)
{
  DB_VALUE dbv1_common, dbv2_common;
  int ret = NO_ERROR;
  unsigned int prec;
  unsigned char temp[DB_NUMERIC_BUF_SIZE];  /* Copy of a DB_C_NUMERIC */
  TP_DOMAIN *domain;
  bool result_sign = false;

  /* Check for bad inputs */
  if (answer == NULL)
    {
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv1 == NULL || DB_VALUE_TYPE (dbv1) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv2 == NULL || DB_VALUE_TYPE (dbv2) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }

  /* Check for NULL values */
  if (DB_IS_NULL (dbv1) || DB_IS_NULL (dbv2))
    {
      db_value_domain_init (answer, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);
      return NO_ERROR;
    }

  /* Coerce, if necessary, to make prec & scale match */
  ret = numeric_common_prec_scale (dbv1, dbv2, &dbv1_common, &dbv2_common);
  if (ret == ER_IT_DATA_OVERFLOW)
    {
      ret = numeric_prec_scale_when_overflow (dbv1, dbv2, &dbv1_common, &dbv2_common);
      if (ret != NO_ERROR)
    {
      goto exit_on_error;
    }
      else
    {
      er_clear ();
    }
    }
  else if (ret != NO_ERROR)
    {
      goto exit_on_error;
    }

  /* Perform the addition */
  if (numeric_is_negative (dbv1) == numeric_is_negative (dbv2))
    {
      numeric_add (db_locate_numeric (&dbv1_common), db_locate_numeric (&dbv2_common), temp, DB_NUMERIC_BUF_SIZE);
      result_sign = numeric_is_negative (dbv1);
    }
  else
    {
      if (numeric_compare_pos (db_locate_numeric (&dbv1_common), db_locate_numeric (&dbv2_common)) >= 0)
    {
      // |arg1| >= |arg2|
      numeric_sub (db_locate_numeric (&dbv1_common), db_locate_numeric (&dbv2_common), temp, DB_NUMERIC_BUF_SIZE);
      result_sign = numeric_is_negative (dbv1);
    }
      else
    {
      // |arg1| < |arg2|
      numeric_sub (db_locate_numeric (&dbv2_common), db_locate_numeric (&dbv1_common), temp, DB_NUMERIC_BUF_SIZE);
      result_sign = numeric_is_negative (dbv2);
    }
    }

  /*
   * Update the domin information of the answer. Check to see if precision
   * needs to be updated due to carry
   */
  prec = DB_VALUE_PRECISION (&dbv1_common);
  if (numeric_overflow (temp, prec))
    {
      if (prec < DB_MAX_NUMERIC_PRECISION)
    {
      prec++;
    }
      else
    {
      domain = tp_domain_resolve_default (DB_TYPE_NUMERIC);
      er_set (ER_WARNING_SEVERITY, ARG_FILE_LINE, ER_IT_DATA_OVERFLOW, 1, pr_type_name (TP_DOMAIN_TYPE (domain)));
      ret = ER_IT_DATA_OVERFLOW;
      goto exit_on_error;
    }
    }

  if (result_sign && numeric_is_zero (temp))
    {
      /* Prevent -0; zero is always treated as positive. */
      result_sign = false;
    }
  db_make_numeric (answer, temp, prec, DB_VALUE_SCALE (&dbv1_common), DB_NUMERIC_BUF_SIZE, result_sign, true);

  return ret;

exit_on_error:

  db_value_domain_init (answer, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);

  return (ret == NO_ERROR && (ret = er_errid ()) == NO_ERROR) ? ER_FAILED : ret;
}

/*
 * float_numeric_db_value_add() - Add two NUMERIC values
 *   return : NO_ERROR on success, or error code
 *
 * Note:
 *   - The legacy numeric_db_value_add() function was limited to 16 bytes and up to 38 digits.
 *   - float_numeric_db_value_add() supports the extended NUMERIC range, 
 *     allowing operations with larger precision.
 *   - If the result precision exceeds DB_MAX_NUMERIC_PRECISION,
 *     the value is rounded and stored using only DB_MAX_NUMERIC_PRECISION significant digits.
 */
int
float_numeric_db_value_add (const DB_VALUE * dbv1, const DB_VALUE * dbv2, DB_VALUE * answer)
{
  int ret = NO_ERROR;
  int scale1, scale2, result_scale;
  int prec1, prec2, result_prec, calc_prec1, calc_prec2;
  int needed_bytes, calc_words, calc_nbytes;
  uint8_t result_buf[DB_NUMERIC_BUF_SIZE];
  bool result_sign = false;

  /* Check for bad inputs */
  if (answer == NULL)
    {
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv1 == NULL || DB_VALUE_TYPE (dbv1) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv2 == NULL || DB_VALUE_TYPE (dbv2) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }

  /* Check for NULL values */
  if (DB_IS_NULL (dbv1) || DB_IS_NULL (dbv2))
    {
      db_value_domain_init (answer, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);
      return NO_ERROR;
    }

  db_get_numeric_precision_and_scale (dbv1, &prec1, &scale1, NULL);
  db_get_numeric_precision_and_scale (dbv2, &prec2, &scale2, NULL);
  calc_prec1 = prec1 - scale1;
  calc_prec2 = prec2 - scale2;

  result_scale = MAX (scale1, scale2);
  result_prec = MAX (calc_prec1, calc_prec2) + result_scale;

  /* 1) compute common precision/scale and scale adjustments */
  int scale_adjust1 = result_scale - scale1;
  int scale_adjust2 = result_scale - scale2;

  /* 2) determine working buffer size */
  needed_bytes = MAX (_gv_numeric_precision_to_bytes_lookup[result_prec], DB_NUMERIC_BUF_SIZE) + 1;
  calc_words = NUMERIC_GET_WORD_COUNT (needed_bytes);
  calc_nbytes = NUMERIC_GET_BYTE_COUNT (calc_words);

  /* 3) initialize new calculation buffers and pad absolute values */
  uint64_t dbv1_word[calc_words] = { 0 };
  uint64_t dbv2_word[calc_words] = { 0 };
  uint64_t result_word[calc_words] = { 0 };

  numeric_bytes_to_words (db_locate_numeric (dbv1), DB_NUMERIC_BUF_SIZE, dbv1_word, calc_words, calc_nbytes);
  numeric_bytes_to_words (db_locate_numeric (dbv2), DB_NUMERIC_BUF_SIZE, dbv2_word, calc_words, calc_nbytes);

  /* 4) scale adjustments */
  if (scale_adjust1)
    {
      float_numeric_mul_normalize (dbv1_word, calc_words, calc_nbytes, scale_adjust1);
    }
  if (scale_adjust2)
    {
      float_numeric_mul_normalize (dbv2_word, calc_words, calc_nbytes, scale_adjust2);
    }

  /* fast path */
  if (calc_words == NUMERIC_AS_WORDS && (dbv1_word[0] | dbv1_word[1] | dbv2_word[0] | dbv2_word[1]) == 0)
    {
      result_prec =
    float_numeric_add_fast (dbv1_word, dbv2_word, result_word, calc_words, numeric_is_negative (dbv1),
                numeric_is_negative (dbv2), &result_sign, result_buf);
      if (result_sign && result_prec == 1 && result_word[calc_words - 1] == 0)
    {
      /* Prevent -0; zero is always treated as positive. */
      result_sign = false;
    }
      db_make_numeric (answer, result_buf, result_prec, result_scale, DB_NUMERIC_BUF_SIZE, result_sign, true);
      return ret;
    }

  /* 5) addition */
  if (numeric_is_negative (dbv1) == numeric_is_negative (dbv2))
    {
      (void) float_numeric_add (dbv1_word, dbv2_word, result_word, calc_words);
      result_sign = numeric_is_negative (dbv1); // result sign = input sign
    }
  else
    {
      if (float_numeric_operation_compare (dbv1_word, dbv2_word, calc_words) >= 0)
    {
      // |arg1| >= |arg2|
      (void) float_numeric_sub (dbv1_word, dbv2_word, result_word, calc_words);
      result_sign = numeric_is_negative (dbv1); // result sign = sign of larger number
    }
      else
    {
      // |arg1| < |arg2|
      (void) float_numeric_sub (dbv2_word, dbv1_word, result_word, calc_words);
      result_sign = numeric_is_negative (dbv2); // result sign = sign of larger number
    }
    }

  /* 6) check and recalculate precision/scale of the addition result */
  result_prec = float_numeric_get_decimal_digit (result_word, calc_words);
  if (result_sign && result_prec == 1 && result_word[calc_words - 1] == 0)
    {
      /* Prevent -0; zero is always treated as positive. */
      result_sign = false;
    }

  ret = float_numeric_check_overflow_and_adjust_scale (&result_prec, &result_scale, answer);
  if (ret != NO_ERROR)
    {
      return ret;
    }

  /* 7) round and pack to DB_NUMERIC_BUF_SIZE bytes */
  ret = float_numeric_round_and_pack (result_word, calc_words, calc_nbytes, result_buf, &result_prec, &result_scale);
  if (ret != NO_ERROR)
    {
      TP_DOMAIN *domain = tp_domain_resolve_default (DB_TYPE_NUMERIC);
      er_set (ER_WARNING_SEVERITY, ARG_FILE_LINE, ER_IT_DATA_OVERFLOW, 1, pr_type_name (TP_DOMAIN_TYPE (domain)));
      db_value_domain_init (answer, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);
      return ret;
    }

  /* 8) store result */
  db_make_numeric (answer, result_buf, result_prec, result_scale, DB_NUMERIC_BUF_SIZE, result_sign, true);

  return ret;
}

/*
 * numeric_db_value_sub () -
 *   return: NO_ERROR, or ER_code
 *     Errors:
 *       ER_OBJ_INVALID_ARGUMENTS - if dbv1, dbv2, or answer are NULL or
 *                                  are not DB_TYPE_NUMERIC
 *   dbv1(in)   : ptr to a DB_VALUE structure of type DB_TYPE_NUMERIC
 *   dbv2(in)   : ptr to a DB_VALUE structure of type DB_TYPE_NUMERIC
 *   answer(out): ptr to a DB_VALUE structure of type DB_TYPE_NUMERIC
 *
 * Note: This routine subtracts the numeric values of two DB_VALUE's and
 * returns the results in answer. The answer will be returned as either the
 * common type of dbv1 and dbv2 or as the common type of dbv1 and dbv2 with
 * an extra decimal place of precision if the sum requires it due to carry.
 *
 * The answer is set to a NULL-valued DB_C_NUMERIC's when an error occurs.
 */
int
numeric_db_value_sub (const DB_VALUE * dbv1, const DB_VALUE * dbv2, DB_VALUE * answer)
{
  DB_VALUE dbv1_common, dbv2_common;
  int ret = NO_ERROR;
  unsigned int prec;
  unsigned char temp[DB_NUMERIC_BUF_SIZE];  /* Copy of a DB_C_NUMERIC */
  TP_DOMAIN *domain;
  bool result_sign = false;

  /* Check for bad inputs */
  if (answer == NULL)
    {
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv1 == NULL || DB_VALUE_TYPE (dbv1) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv2 == NULL || DB_VALUE_TYPE (dbv2) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }

  /* Check for NULL values */
  if (DB_IS_NULL (dbv1) || DB_IS_NULL (dbv2))
    {
      db_value_domain_init (answer, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);
      return NO_ERROR;
    }

  /* Coerce, if necessary, to make prec & scale match */
  ret = numeric_common_prec_scale (dbv1, dbv2, &dbv1_common, &dbv2_common);
  if (ret == ER_IT_DATA_OVERFLOW)
    {
      ret = numeric_prec_scale_when_overflow (dbv1, dbv2, &dbv1_common, &dbv2_common);
      if (ret != NO_ERROR)
    {
      goto exit_on_error;
    }
      else
    {
      er_clear ();
    }
    }
  else if (ret != NO_ERROR)
    {
      goto exit_on_error;
    }

  /* Perform the subtraction */
  if (numeric_is_negative (dbv1) == numeric_is_negative (dbv2))
    {
      if (numeric_compare_pos (db_locate_numeric (&dbv1_common), db_locate_numeric (&dbv2_common)) >= 0)
    {
      // |arg1| >= |arg2|
      numeric_sub (db_locate_numeric (&dbv1_common), db_locate_numeric (&dbv2_common), temp, DB_NUMERIC_BUF_SIZE);
      result_sign = numeric_is_negative (dbv1);
    }
      else
    {
      // |arg1| < |arg2|
      numeric_sub (db_locate_numeric (&dbv2_common), db_locate_numeric (&dbv1_common), temp, DB_NUMERIC_BUF_SIZE);
      result_sign = !numeric_is_negative (dbv2);
    }
    }
  else
    {
      numeric_add (db_locate_numeric (&dbv1_common), db_locate_numeric (&dbv2_common), temp, DB_NUMERIC_BUF_SIZE);
      result_sign = numeric_is_negative (dbv1);
    }

  /*
   * Update the domin information of the answer. Check to see if precision
   * needs to be updated due to carry
   */
  prec = DB_VALUE_PRECISION (&dbv1_common);
  if (numeric_overflow (temp, prec))
    {
      if (prec < DB_MAX_FIXED_NUMERIC_PRECISION)
    {
      prec++;
    }
      else
    {
      domain = tp_domain_resolve_default (DB_TYPE_NUMERIC);
      er_set (ER_WARNING_SEVERITY, ARG_FILE_LINE, ER_IT_DATA_OVERFLOW, 1, pr_type_name (TP_DOMAIN_TYPE (domain)));
      ret = ER_IT_DATA_OVERFLOW;
      goto exit_on_error;
    }
    }

  if (result_sign && numeric_is_zero (temp))
    {
      /* Prevent -0; zero is always treated as positive. */
      result_sign = false;
    }
  db_make_numeric (answer, temp, prec, DB_VALUE_SCALE (&dbv1_common), DB_NUMERIC_BUF_SIZE, result_sign, true);

  return ret;

exit_on_error:

  db_value_domain_init (answer, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);

  return (ret == NO_ERROR && (ret = er_errid ()) == NO_ERROR) ? ER_FAILED : ret;
}

/*
 * float_numeric_db_value_sub() - Subtract two NUMERIC values
 *   return : NO_ERROR on success, or error code
 *
 * Note:
 *   - The legacy numeric_db_value_sub() function was limited to 16 bytes and up to 38 digits.
 *   - float_numeric_db_value_sub() supports the extended NUMERIC range, 
 *     allowing operations with larger precision.
 *   - If the result precision exceeds DB_MAX_NUMERIC_PRECISION,
 *     the value is rounded and stored using only DB_MAX_NUMERIC_PRECISION significant digits.
 */
int
float_numeric_db_value_sub (const DB_VALUE * dbv1, const DB_VALUE * dbv2, DB_VALUE * answer)
{
  int ret = NO_ERROR;
  int scale1, scale2, result_scale;
  int prec1, prec2, result_prec, calc_prec1, calc_prec2;
  int needed_bytes, calc_words, calc_nbytes;
  uint8_t result_buf[DB_NUMERIC_BUF_SIZE];
  bool result_sign = false;

  /* Check for bad inputs */
  if (answer == NULL)
    {
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv1 == NULL || DB_VALUE_TYPE (dbv1) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv2 == NULL || DB_VALUE_TYPE (dbv2) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }

  /* Check for NULL values */
  if (DB_IS_NULL (dbv1) || DB_IS_NULL (dbv2))
    {
      db_value_domain_init (answer, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);
      return NO_ERROR;
    }

  db_get_numeric_precision_and_scale (dbv1, &prec1, &scale1, NULL);
  db_get_numeric_precision_and_scale (dbv2, &prec2, &scale2, NULL);
  calc_prec1 = prec1 - scale1;
  calc_prec2 = prec2 - scale2;

  result_scale = MAX (scale1, scale2);
  result_prec = MAX (calc_prec1, calc_prec2) + result_scale;

  /* 1) compute common precision/scale and scale adjustments */
  int scale_adjust1 = result_scale - scale1;
  int scale_adjust2 = result_scale - scale2;

  /* 2) determine working buffer size */
  needed_bytes = MAX (_gv_numeric_precision_to_bytes_lookup[result_prec], DB_NUMERIC_BUF_SIZE) + 1;
  calc_words = NUMERIC_GET_WORD_COUNT (needed_bytes);
  calc_nbytes = NUMERIC_GET_BYTE_COUNT (calc_words);

  /* 3) initialize new calculation buffers and pad absolute values */
  uint64_t dbv1_word[calc_words] = { 0 };
  uint64_t dbv2_word[calc_words] = { 0 };
  uint64_t result_word[calc_words] = { 0 };

  numeric_bytes_to_words (db_locate_numeric (dbv1), DB_NUMERIC_BUF_SIZE, dbv1_word, calc_words, calc_nbytes);
  numeric_bytes_to_words (db_locate_numeric (dbv2), DB_NUMERIC_BUF_SIZE, dbv2_word, calc_words, calc_nbytes);

  /* 4) scale adjustments */
  if (scale_adjust1)
    {
      float_numeric_mul_normalize (dbv1_word, calc_words, calc_nbytes, scale_adjust1);
    }
  if (scale_adjust2)
    {
      float_numeric_mul_normalize (dbv2_word, calc_words, calc_nbytes, scale_adjust2);
    }

  /* fast path */
  if (calc_words == NUMERIC_AS_WORDS && (dbv1_word[0] | dbv1_word[1] | dbv2_word[0] | dbv2_word[1]) == 0)
    {
      result_prec =
    float_numeric_sub_fast (dbv1_word, dbv2_word, result_word, calc_words, numeric_is_negative (dbv1),
                numeric_is_negative (dbv2), &result_sign, result_buf);
      if (result_sign && result_prec == 1 && result_word[calc_words - 1] == 0)
    {
      /* Prevent -0; zero is always treated as positive. */
      result_sign = false;
    }
      db_make_numeric (answer, result_buf, result_prec, result_scale, DB_NUMERIC_BUF_SIZE, result_sign, true);
      return ret;
    }

  /* 5) subtraction */
  if (numeric_is_negative (dbv1) == numeric_is_negative (dbv2))
    {
      if (float_numeric_operation_compare (dbv1_word, dbv2_word, calc_words) >= 0)
    {
      // |arg1| >= |arg2|
      (void) float_numeric_sub (dbv1_word, dbv2_word, result_word, calc_words);
      result_sign = numeric_is_negative (dbv1); // result sign = sign of larger number
    }
      else
    {
      // |arg1| < |arg2|
      (void) float_numeric_sub (dbv2_word, dbv1_word, result_word, calc_words);
      result_sign = !numeric_is_negative (dbv2);    // result sign = sign of larger number
    }
    }
  else
    {
      (void) float_numeric_add (dbv1_word, dbv2_word, result_word, calc_words);
      result_sign = numeric_is_negative (dbv1); // result sign = input sign
    }

  /* 6) check and recalculate precision/scale of the subtraction result */
  result_prec = float_numeric_get_decimal_digit (result_word, calc_words);
  if (result_sign && result_prec == 1 && result_word[calc_words - 1] == 0)
    {
      /* Prevent -0; zero is always treated as positive. */
      result_sign = false;
    }

  ret = float_numeric_check_overflow_and_adjust_scale (&result_prec, &result_scale, answer);
  if (ret != NO_ERROR)
    {
      return ret;
    }

  /* 7) round and pack to DB_NUMERIC_BUF_SIZE bytes */
  ret = float_numeric_round_and_pack (result_word, calc_words, calc_nbytes, result_buf, &result_prec, &result_scale);
  if (ret != NO_ERROR)
    {
      TP_DOMAIN *domain = tp_domain_resolve_default (DB_TYPE_NUMERIC);
      er_set (ER_WARNING_SEVERITY, ARG_FILE_LINE, ER_IT_DATA_OVERFLOW, 1, pr_type_name (TP_DOMAIN_TYPE (domain)));
      db_value_domain_init (answer, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);
      return ret;
    }

  /* 8) store result */
  db_make_numeric (answer, result_buf, result_prec, result_scale, DB_NUMERIC_BUF_SIZE, result_sign, true);

  return ret;
}

/*
 * numeric_db_value_mul () -
 *   return: NO_ERROR, or ER_code
 *     Errors:
 *       ER_OBJ_INVALID_ARGUMENTS - if dbv1, dbv2, or answer are NULL or
 *                                  are not DB_TYPE_NUMERIC
 *   dbv1(in)   : ptr to a DB_VALUE structure of type DB_TYPE_NUMERIC
 *   dbv2(in)   : ptr to a DB_VALUE structure of type DB_TYPE_NUMERIC
 *   answer(out): ptr to a DB_VALUE structure of type DB_TYPE_NUMERIC
 *
 * Note: This routine multiplies the numeric values of two DB_VALUE's and
 * returns the results in answer. The answer will be returned as either the
 * common type of dbv1 and dbv2 or as the common type of dbv1 and dbv2 with
 * a extra decimal places of precision if the product requires it to avoid
 * loss of data.
 *
 * The answer is set to a NULL-valued DB_C_NUMERIC's when an error occurs.
 */
int
numeric_db_value_mul (const DB_VALUE * dbv1, const DB_VALUE * dbv2, DB_VALUE * answer)
{
  int ret = NO_ERROR;
  int prec;
  int scale;
  unsigned char temp[2 * DB_NUMERIC_BUF_SIZE];  /* Copy of a DB_C_NUMERIC */
  unsigned char result[DB_NUMERIC_BUF_SIZE];    /* Copy of a DB_C_NUMERIC */
  bool result_sign = false;

  /* Check for bad inputs */
  if (answer == NULL)
    {
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv1 == NULL || DB_VALUE_TYPE (dbv1) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv2 == NULL || DB_VALUE_TYPE (dbv2) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }

  /* Check for NULL values */
  if (DB_IS_NULL (dbv1) || DB_IS_NULL (dbv2))
    {
      db_value_domain_init (answer, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);
      return NO_ERROR;
    }

  result_sign = numeric_is_negative (dbv1) ^ numeric_is_negative (dbv2);

  /* Perform the multiplication */
  numeric_mul (db_locate_numeric (dbv1), db_locate_numeric (dbv2), temp, &result_sign);
  /* Check for overflow.  Reset precision & scale if necessary */
  prec = db_get_numeric_precision (dbv1, NULL) + db_get_numeric_precision (dbv2, NULL) + 1;
  scale = db_get_numeric_scale (dbv1, NULL) + db_get_numeric_scale (dbv2, NULL);
  ret = numeric_get_msb_for_dec (prec, scale, temp, &prec, &scale, result);
  if (ret != NO_ERROR)
    {
      goto exit_on_error;
    }

  /* If no error, make the answer */
  db_make_numeric (answer, result, prec, scale, DB_NUMERIC_BUF_SIZE, result_sign, true);

  return ret;

exit_on_error:

  db_value_domain_init (answer, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);

  return (ret == NO_ERROR && (ret = er_errid ()) == NO_ERROR) ? ER_FAILED : ret;
}

/*
 * float_numeric_db_value_mul() - Multiply two NUMERIC values
 *   return : NO_ERROR on success, or error code
 *
 * Note:
 *   - The legacy numeric_db_value_mul() function was limited to 16 bytes and up to 38 digits.
 *   - float_numeric_db_value_mul() supports the extended NUMERIC range, 
 *     allowing operations with larger precision.
 *   - If the result precision exceeds DB_MAX_NUMERIC_PRECISION,
 *     the value is rounded and stored using only DB_MAX_NUMERIC_PRECISION significant digits.
 */
int
float_numeric_db_value_mul (const DB_VALUE * dbv1, const DB_VALUE * dbv2, DB_VALUE * answer)
{
  int ret = NO_ERROR;
  int needed_bytes, calc_words, calc_nbytes;
  int scale1, scale2, result_scale;
  int prec1, prec2, result_prec;
  uint8_t result_buf[DB_NUMERIC_BUF_SIZE];
  bool result_sign = false;

  /* Check for bad inputs */
  if (answer == NULL)
    {
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv1 == NULL || DB_VALUE_TYPE (dbv1) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv2 == NULL || DB_VALUE_TYPE (dbv2) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }

  /* Check for NULL values */
  if (DB_IS_NULL (dbv1) || DB_IS_NULL (dbv2))
    {
      db_value_domain_init (answer, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);
      return NO_ERROR;
    }

  /* 
   * The following cases return 0 immediately without any operation
   * ex) 0 * 0 = 0
   *     0 * v = 0
   *     v * 0 = 0
   */
  if (numeric_is_zero (db_locate_numeric (dbv1)) || numeric_is_zero (db_locate_numeric (dbv2)))
    {
      memset (result_buf, 0, DB_NUMERIC_BUF_SIZE);
      result_prec = 1;
      result_scale = 0;
      db_make_numeric (answer, result_buf, result_prec, result_scale, DB_NUMERIC_BUF_SIZE, false, true);
      return NO_ERROR;
    }

  db_get_numeric_precision_and_scale (dbv1, &prec1, &scale1, NULL);
  db_get_numeric_precision_and_scale (dbv2, &prec2, &scale2, NULL);
  result_scale = scale1 + scale2;
  result_prec = prec1 + prec2;

  /* 1) determine common sign of the result */
  result_sign = numeric_is_negative (dbv1) ^ numeric_is_negative (dbv2);

  /* 2) determine working buffer size */
  needed_bytes = MAX (_gv_numeric_precision_to_bytes_lookup[result_prec], DB_NUMERIC_BUF_SIZE) + 1;
  calc_words = NUMERIC_GET_WORD_COUNT (needed_bytes);
  calc_nbytes = NUMERIC_GET_BYTE_COUNT (calc_words);

  /* 3) initialize new calculation buffers and pad absolute values */
  uint64_t dbv1_word[calc_words] = { 0 };
  uint64_t dbv2_word[calc_words] = { 0 };
  uint64_t result_word[calc_words] = { 0 };

  numeric_bytes_to_words (db_locate_numeric (dbv1), DB_NUMERIC_BUF_SIZE, dbv1_word, calc_words, calc_nbytes);
  numeric_bytes_to_words (db_locate_numeric (dbv2), DB_NUMERIC_BUF_SIZE, dbv2_word, calc_words, calc_nbytes);

  /* fast path */
  if (calc_words == NUMERIC_AS_WORDS && (dbv1_word[0] | dbv1_word[1] | dbv2_word[0] | dbv2_word[1]) == 0)
    {
      result_prec = float_numeric_mul_fast (dbv1_word, dbv2_word, result_word, calc_words, result_buf, &result_scale);
      if (result_sign && result_prec == 1 && result_word[calc_words - 1] == 0)
    {
      /* Prevent -0; zero is always treated as positive. */
      result_sign = false;
    }
      db_make_numeric (answer, result_buf, result_prec, result_scale, DB_NUMERIC_BUF_SIZE, result_sign, true);
      return ret;
    }

  /* 4) scale adjustments */
  // multiplication does not require scale adjustments

  /* 5) multiplication */
  float_numeric_mul (dbv1_word, dbv2_word, result_word, calc_words, calc_nbytes);

  /* 6) check and recalculate precision/scale of the multiplication result */
  if (result_scale > DB_MAX_NUMERIC_SCALE)
    {
      int scale_overflow = result_scale - DB_MAX_NUMERIC_SCALE;
      result_scale = DB_MAX_NUMERIC_SCALE;

      if (float_numeric_div_normalize (result_word, calc_words, calc_nbytes, scale_overflow) >= ROUND_HALF_UP_DIGIT)
    {
      (void) float_numeric_increment (result_word, calc_words, 1);
    }
    }
  result_prec = float_numeric_get_decimal_digit (result_word, calc_words);
  if (result_sign && result_prec == 1 && result_word[calc_words - 1] == 0)
    {
      /* Prevent -0; zero is always treated as positive. */
      result_sign = false;
    }

  ret = float_numeric_check_overflow_and_adjust_scale (&result_prec, &result_scale, answer);
  if (ret != NO_ERROR)
    {
      return ret;
    }

  /* 7) round and pack to DB_NUMERIC_BUF_SIZE bytes */
  ret = float_numeric_round_and_pack (result_word, calc_words, calc_nbytes, result_buf, &result_prec, &result_scale);
  if (ret != NO_ERROR)
    {
      TP_DOMAIN *domain = tp_domain_resolve_default (DB_TYPE_NUMERIC);
      er_set (ER_WARNING_SEVERITY, ARG_FILE_LINE, ER_IT_DATA_OVERFLOW, 1, pr_type_name (TP_DOMAIN_TYPE (domain)));
      db_value_domain_init (answer, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);
      return ret;
    }

  /* 8) store result */
  db_make_numeric (answer, result_buf, result_prec, result_scale, DB_NUMERIC_BUF_SIZE, result_sign, true);

  return ret;
}

/*
 * numeric_db_value_div () -
 *   return: NO_ERROR, or ER_code
 *     Errors:
 *       ER_OBJ_INVALID_ARGUMENTS - if dbv1, dbv2, or answer are NULL or
 *                                  are not DB_TYPE_NUMERIC
 *   dbv1(in)   : ptr to a DB_VALUE structure of type DB_TYPE_NUMERIC
 *   dbv2(in)   : ptr to a DB_VALUE structure of type DB_TYPE_NUMERIC
 *   answer(out): ptr to a DB_VALUE structure of type DB_TYPE_NUMERIC
 *
 * Note: This routine divides the numeric values of two DB_VALUE's and
 * returns the results in answer. The answer will be returned as either the
 * common type of dbv1 and dbv2 or as the common type of dbv1 and dbv2 with
 * a extra decimal places of precision if the quotient requires it to avoid
 * loss of data.
 *
 * The answer is set to a NULL-valued DB_C_NUMERIC's when an error occurs.
 */
int
numeric_db_value_div (const DB_VALUE * dbv1, const DB_VALUE * dbv2, DB_VALUE * answer)
{
  int prec;
  int max_scale, scale1, scale2;
  unsigned char long_dbv1_copy[DB_LONG_NUMERIC_MULTIPLIER * DB_NUMERIC_BUF_SIZE];
  unsigned char long_temp_quo[DB_LONG_NUMERIC_MULTIPLIER * DB_NUMERIC_BUF_SIZE];
  unsigned char dbv1_copy[DB_NUMERIC_BUF_SIZE]; /* Copy of a DB_C_NUMERIC */
  unsigned char dbv2_copy[DB_NUMERIC_BUF_SIZE]; /* Copy of a DB_C_NUMERIC */
  unsigned char temp_quo[DB_NUMERIC_BUF_SIZE];  /* Copy of a DB_C_NUMERIC */
  unsigned char temp_rem[DB_NUMERIC_BUF_SIZE];  /* Copy of a DB_C_NUMERIC */
  int scale, scaleup = 0;
  int ret = NO_ERROR;
  TP_DOMAIN *domain;
  DB_C_NUMERIC divisor_p;
  bool result_sign = false;

  /* Check for bad inputs */
  if (answer == NULL)
    {
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv1 == NULL || DB_VALUE_TYPE (dbv1) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv2 == NULL || DB_VALUE_TYPE (dbv2) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }

  /* Check for NULL values */
  if (DB_IS_NULL (dbv1) || DB_IS_NULL (dbv2))
    {
      db_value_domain_init (answer, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);
      return NO_ERROR;
    }

  result_sign = numeric_is_negative (dbv1) ^ numeric_is_negative (dbv2);

  /* In order to maintain the proper number of scaling in the output, find the maximum scale of the two args and make
   * sure that the scale of dbv1 exceeds the scale of dbv2 by that amount. */
  numeric_shortnum_to_longnum (long_dbv1_copy, db_locate_numeric (dbv1));
  scale1 = db_get_numeric_scale (dbv1, NULL);
  scale2 = db_get_numeric_scale (dbv2, NULL);
  max_scale = MAX (scale1, scale2);
  if (scale2 > 0)
    {
      scaleup = (max_scale + scale2) - scale1;
      ret = numeric_scale_dec_long (long_dbv1_copy, scaleup, true);
      if (ret != NO_ERROR)
    {           /* overflow */
      goto exit_on_error;
    }
    }

  /*
   * Update the domain information of the answer. Check to see if precision
   * needs to be updated due to carry
   */
  prec = db_get_numeric_precision (dbv1, NULL) + scaleup;
  scale = max_scale;
  if (prec > DB_MAX_NUMERIC_PRECISION)
    {
      prec = DB_MAX_NUMERIC_PRECISION;
    }

  if (scale < DB_LEGACY_DEFAULT_NUMERIC_DIVISION_SCALE)
    {
      int new_scale, new_prec;
      int scale_delta;
      scale_delta = DB_LEGACY_DEFAULT_NUMERIC_DIVISION_SCALE - scale;
      new_scale = scale + scale_delta;
      new_prec = prec + scale_delta;
      if (new_prec > DB_MAX_NUMERIC_PRECISION)
    {
      new_scale -= (new_prec - DB_MAX_NUMERIC_PRECISION);
      new_prec = DB_MAX_NUMERIC_PRECISION;
    }

      ret = numeric_scale_dec_long (long_dbv1_copy, new_scale - scale, true);
      if (ret != NO_ERROR)
    {
      goto exit_on_error;
    }

      scale = new_scale;
      prec = new_prec;
    }

  if (numeric_is_longnum_value (long_dbv1_copy))
    {
      /* only the dividend and quotient maybe long numeric, divisor must be numeric */
      numeric_long_div (long_dbv1_copy, db_locate_numeric (dbv2), long_temp_quo, temp_rem, true);
      ret = numeric_longnum_to_shortnum (temp_quo, long_temp_quo);
      if (ret != NO_ERROR)
    {
      goto exit_on_error;
    }
    }
  else
    {
      numeric_longnum_to_shortnum (dbv1_copy, long_dbv1_copy);
      numeric_div (dbv1_copy, db_locate_numeric (dbv2), temp_quo, temp_rem, numeric_is_negative (dbv1),
           numeric_is_negative (dbv2));
    }

  /* round! Check if remainder is larger than or equal to 2*divisor. i.e. rem / divisor >= 0.5 */
  divisor_p = db_locate_numeric (dbv2);

  numeric_add (temp_rem, temp_rem, temp_rem, DB_NUMERIC_BUF_SIZE);
  if (numeric_compare_pos (temp_rem, divisor_p) >= 0)
    {
      numeric_increase (temp_quo);
    }

  if (numeric_overflow (temp_quo, prec))
    {
      if (prec < DB_MAX_NUMERIC_PRECISION)
    {
      prec++;
    }
      else
    {
      domain = tp_domain_resolve_default (DB_TYPE_NUMERIC);
      er_set (ER_WARNING_SEVERITY, ARG_FILE_LINE, ER_IT_DATA_OVERFLOW, 1, pr_type_name (TP_DOMAIN_TYPE (domain)));
      ret = ER_IT_DATA_OVERFLOW;
      goto exit_on_error;
    }
    }

  if (result_sign && numeric_is_zero (temp_quo))
    {
      /* Prevent -0; zero is always treated as positive. */
      result_sign = false;
    }

  db_make_numeric (answer, temp_quo, prec, scale, DB_NUMERIC_BUF_SIZE, result_sign, true);

  return ret;

exit_on_error:

  db_value_domain_init (answer, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);

  return (ret == NO_ERROR && (ret = er_errid ()) == NO_ERROR) ? ER_FAILED : ret;
}

/*
 * float_numeric_db_value_div() - divide two NUMERIC values
 *   return : NO_ERROR on success, or error code
 *
 * Note:
 *   - The legacy numeric_db_value_div() function was limited to 16 bytes and up to 38 digits.
 *   - float_numeric_db_value_div() supports the extended NUMERIC range, 
 *     allowing operations with larger precision.
 *   - If the result precision exceeds DB_MAX_NUMERIC_PRECISION,
 *     the value is rounded and stored using only DB_MAX_NUMERIC_PRECISION significant digits.
 */
int
float_numeric_db_value_div (const DB_VALUE * dbv1, const DB_VALUE * dbv2, DB_VALUE * answer)
{
  int ret = NO_ERROR;
  int result_prec;
  int result_scale;
  int prec1, prec2;
  int scale1, scale2;
  int needed_bytes, calc_words, calc_nbytes;
  int exponent10;
  uint8_t result_buf[DB_NUMERIC_BUF_SIZE];
  bool result_sign = false;

  /* Check for bad inputs */
  if (answer == NULL)
    {
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv1 == NULL || DB_VALUE_TYPE (dbv1) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv2 == NULL || DB_VALUE_TYPE (dbv2) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }

  /* Check for NULL values */
  if (DB_IS_NULL (dbv1) || DB_IS_NULL (dbv2))
    {
      db_value_domain_init (answer, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);
      return NO_ERROR;
    }

  /* 
   * The following cases return 0 immediately without any operation
   * ex) 12 / 0 = err (=already handled in parsing)
   *     0 / 12 = 0
   */
  if (numeric_is_zero (db_locate_numeric (dbv1)))
    {
      memset (result_buf, 0, DB_NUMERIC_BUF_SIZE);
      result_prec = 1;
      result_scale = 0;
      db_make_numeric (answer, result_buf, result_prec, result_scale, DB_NUMERIC_BUF_SIZE, false, true);
      return NO_ERROR;
    }

  /* 1) compute exact precision values for mantissa calculations */
  db_get_numeric_precision_and_scale (dbv1, &prec1, &scale1, NULL);
  db_get_numeric_precision_and_scale (dbv2, &prec2, &scale2, NULL);

  uint64_t dbv1_word[NUMERIC_AS_WORDS] = { 0 };
  uint64_t dbv2_word[NUMERIC_AS_WORDS] = { 0 };

  numeric_bytes_to_words (db_locate_numeric (dbv1), DB_NUMERIC_BUF_SIZE, dbv1_word, NUMERIC_AS_WORDS,
              NUMERIC_AS_WORD_BYTES);
  numeric_bytes_to_words (db_locate_numeric (dbv2), DB_NUMERIC_BUF_SIZE, dbv2_word, NUMERIC_AS_WORDS,
              NUMERIC_AS_WORD_BYTES);

  prec1 = float_numeric_get_decimal_digit (dbv1_word, NUMERIC_AS_WORDS);
  prec2 = float_numeric_get_decimal_digit (dbv2_word, NUMERIC_AS_WORDS);

  /* 2) determine common sign of the result */
  result_sign = numeric_is_negative (dbv1) ^ numeric_is_negative (dbv2);

  /* 3) compute exact exponent values for mantissa calculations */
  int dividend_exponent = (prec1 - 1) - scale1;
  int divisor_exponent = (prec2 - 1) - scale2;
  int exponent_diff = dividend_exponent - divisor_exponent;

  /* fast path */
  if (prec1 <= 19 && prec2 <= 19)
    {
      float_numeric_div_fast (dbv1_word[2], dbv2_word[2],
                  prec1, scale1, prec2, scale2,
                  exponent_diff, result_buf, &result_prec, &result_scale, &result_sign);

      ret = float_numeric_check_overflow_and_adjust_scale (&result_prec, &result_scale, answer);
      if (ret == NO_ERROR)
    {
      db_make_numeric (answer, result_buf, result_prec, result_scale, DB_NUMERIC_BUF_SIZE, result_sign, true);
    }
      return ret;
    }

  /* 4) compare mantissa */
  int mantissa_compare =
    compare_mantissa_same_exponent (dbv1_word, dbv2_word, NUMERIC_AS_WORDS, NUMERIC_AS_WORD_BYTES, prec1, prec2);
  int result_digits = (mantissa_compare >= 0) ? (exponent_diff + 1) : exponent_diff;

  /* 5) mantissa calculations */
  result_prec = DB_MAX_NUMERIC_PRECISION;
  result_scale = result_prec - result_digits;
  exponent10 = (result_scale) + (scale2 - scale1);
  if (result_scale > DB_MAX_NUMERIC_SCALE)
    {
      int scale_overflow = DB_MAX_NUMERIC_SCALE + result_digits;
      exponent10 -= (DB_MAX_NUMERIC_PRECISION - scale_overflow);
      result_scale = DB_MAX_NUMERIC_SCALE;
    }

  ret = float_numeric_check_overflow_and_adjust_scale (&result_prec, &result_scale, answer);
  if (ret != NO_ERROR)
    {
      return ret;
    }

  /* 6) initialize new calculation buffers and pad absolute values */
  needed_bytes = _gv_numeric_precision_to_bytes_lookup[(exponent10 > 0) ? exponent10 : 0];
  needed_bytes += ((int) DB_NUMERIC_BUF_SIZE + 2);
  calc_words = NUMERIC_GET_WORD_COUNT (needed_bytes);
  calc_nbytes = NUMERIC_GET_BYTE_COUNT (calc_words);

  uint64_t dividend_work[calc_words] = { 0 };
  uint64_t divisor_work[calc_words] = { 0 };
  uint64_t quotient_work[calc_words] = { 0 };
  uint64_t remainder_work[calc_words] = { 0 };

  numeric_bytes_to_words (db_locate_numeric (dbv1), DB_NUMERIC_BUF_SIZE, dividend_work, calc_words, calc_nbytes);
  numeric_bytes_to_words (db_locate_numeric (dbv2), DB_NUMERIC_BUF_SIZE, divisor_work, calc_words, calc_nbytes);

  /* 7) only dividend_work is scaled (div_pow10 if negative) */
  if (exponent10 > 0)
    {
      float_numeric_mul_normalize (dividend_work, calc_words, calc_nbytes, exponent10);
    }
  else if (exponent10 < 0)
    {
      /* reduces digits for normalization; does not perform rounding */
      (void) float_numeric_div_normalize (dividend_work, calc_words, calc_nbytes, -exponent10);
    }

  /* 8) division */
  float_numeric_knuth_div ((knuth_digit_t *) dividend_work, (knuth_digit_t *) divisor_work,
               (knuth_digit_t *) quotient_work, (knuth_digit_t *) remainder_work, calc_words);

  /* 9) round up if necessary */
  if (float_numeric_compare_rem_round_up (remainder_work, divisor_work, calc_words) >= 0)
    {
      (void) float_numeric_increment (quotient_work, calc_words, 1);
    }

  /* 10) check and recalculate precision/scale of the division result */
  result_prec = float_numeric_get_decimal_digit (quotient_work, calc_words);
  /* 
   * step 5) mantissa calculation (result_scale = 40 - result_digits) constrains
   * precision to 40 or less. result_prec must not exceed 40 after division.
   */
  assert (result_prec <= DB_MAX_NUMERIC_PRECISION);
  if (result_sign && result_prec == 1 && quotient_work[calc_words - 1] == 0)
    {
      /* Prevent -0; zero is always treated as positive. */
      result_sign = false;
    }
  ret = float_numeric_check_overflow_and_adjust_scale (&result_prec, &result_scale, answer);
  if (ret != NO_ERROR)
    {
      return ret;
    }

  /* 11) round and pack to DB_NUMERIC_BUF_SIZE bytes */
  ret = float_numeric_round_and_pack (quotient_work, calc_words, calc_nbytes, result_buf, &result_prec, &result_scale);
  if (ret != NO_ERROR)
    {
      TP_DOMAIN *domain = tp_domain_resolve_default (DB_TYPE_NUMERIC);
      er_set (ER_WARNING_SEVERITY, ARG_FILE_LINE, ER_IT_DATA_OVERFLOW, 1, pr_type_name (TP_DOMAIN_TYPE (domain)));
      db_value_domain_init (answer, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);
      return ret;
    }

  /* 12) store result */
  db_make_numeric (answer, result_buf, result_prec, result_scale, DB_NUMERIC_BUF_SIZE, result_sign, true);

  return ret;
}

/*
 * numeric_db_value_is_positive () -
 *   return: 1 (>= 0), 0 (< 0), error code (error)
 *   dbvalue(in): ptr to a DB_VALUE of type DB_TYPE_NUMERIC
 */
int
numeric_db_value_is_positive (const DB_VALUE * dbvalue)
{
  int ret;

  /* Check for bad inputs */
  if (dbvalue == NULL || DB_VALUE_TYPE (dbvalue) != DB_TYPE_NUMERIC || DB_IS_NULL (dbvalue))
    {
      return ER_OBJ_INVALID_ARGUMENTS;
    }

  ret = numeric_is_negative (dbvalue);

  return !ret;
}

/*
 * numeric_db_value_compare () -
 *   return: NO_ERROR, or ER_code
 *     Errors:
 *       ER_OBJ_INVALID_ARGUMENTS - if dbv1, dbv2, or answer are NULL or
 *                                  are not DB_TYPE_NUMERIC (for dbv*) or
 *                  DB_TYPE_INTEGER (for answer);
 *   dbv1(in)   : ptr to a DB_VALUE of type DB_TYPE_NUMERIC
 *   dbv2(in)   : ptr to a DB_VALUE of type DB_TYPE_NUMERIC
 *   answer(out): ptr to a DB_VALUE of type DB_TYPE_INTEGER
 *
 * Note: This routine compares two numeric DB_VALUE's and sets the value of
 * answer accordingly. This function returns:
 *          -1   if    dbv1 < dbv2
 *           0   if    dbv1 = dbv2 and
 *           1   if    dbv1 > dbv2.
 */
int
numeric_db_value_compare (const DB_VALUE * dbv1, const DB_VALUE * dbv2, DB_VALUE * answer)
{
  int ret = NO_ERROR;
  int prec1 = 0, prec2 = 0, scale1 = 0, scale2 = 0;
  int cmp_rez = 0;

  /* Check for bad inputs */
  if (answer == NULL)
    {
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv1 == NULL || DB_VALUE_TYPE (dbv1) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }
  if (dbv2 == NULL || DB_VALUE_TYPE (dbv2) != DB_TYPE_NUMERIC)
    {
      db_make_null (answer);
      return ER_OBJ_INVALID_ARGUMENTS;
    }

  /* Check for NULL values */
  if (DB_IS_NULL (dbv1) || DB_IS_NULL (dbv2))
    {
      db_value_domain_init (answer, DB_TYPE_INTEGER, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);
      return NO_ERROR;
    }

  db_get_numeric_precision_and_scale (dbv1, &prec1, &scale1, NULL);
  db_get_numeric_precision_and_scale (dbv2, &prec2, &scale2, NULL);

  if (numeric_is_negative (dbv1) != numeric_is_negative (dbv2))
    {
      ret = db_make_int (answer, (numeric_is_negative (dbv1) ? -1 : 1));
    }
  else if (prec1 == prec2 && scale1 == scale2)
    {
      /* Simple case. Just compare two numbers. */
      cmp_rez = numeric_operation_compare (db_locate_numeric (dbv1), db_locate_numeric (dbv2), DB_NUMERIC_BUF_SIZE);
      if (numeric_is_negative (dbv1))
    {
      cmp_rez = -cmp_rez;
    }
      ret = db_make_int (answer, cmp_rez);
    }
  else
    {
      /* positive vs positive, negative vs negative
       * 
       * compare after scale correction inside the function
       * ex) result :
       *      if(arg1 < arg2) = -1
       *      if(arg1 = arg2) = 0
       *      if(arg1 > arg2) = 1
       */
      cmp_rez =
    float_numeric_compare (db_locate_numeric (dbv1), db_locate_numeric (dbv2), prec1, scale1, prec2, scale2,
                   numeric_is_negative (dbv1), numeric_is_negative (dbv2));
      ret = db_make_int (answer, cmp_rez);
    }

  return ret;
}

/*
 * numeric_coerce_int_to_num () -
 *   return:
 *   arg(in)    : unsigned int value
 *   answer(out): DB_C_NUMERIC
 *   is_value_negative(out): sign of the value
 *
 * Note: This routine converts 32 bit integer into DB_C_NUMERIC format and
 * returns the result.
 */
void
numeric_coerce_int_to_num (int arg, DB_C_NUMERIC answer, bool * is_value_negative)
{
  uint32_t digit;
  unsigned int tmp_arg;

  tmp_arg = (arg < 0) ? -(unsigned int) arg : (unsigned int) arg;
  if (is_value_negative)
    {
      *is_value_negative = (arg < 0);
    }

  /* Copy the lower 32 bits into answer [13] ~ [16] (4 bytes) */
  digit = NUMERIC_BSWAP32 (tmp_arg);
  memcpy (answer + (DB_NUMERIC_BUF_SIZE - sizeof (int)), &digit, sizeof (digit));

  /* Pad extra bytes of answer accordingly [0] ~ [12] (13 bytes) */
  memset (answer, 0, DB_NUMERIC_BUF_SIZE - sizeof (int));
}

/*
 * numeric_coerce_bigint_to_num () -
 *   return:
 *   arg(in)    : unsigned bigint value
 *   answer(out): DB_C_NUMERIC
 *   is_value_negative(out): sign of the value
 *
 * Note: This routine converts 64 bit integer into DB_C_NUMERIC format and
 * returns the result.
 */
void
numeric_coerce_bigint_to_num (DB_BIGINT arg, DB_C_NUMERIC answer, bool * is_value_negative)
{
  uint64_t *digit;
  UINT64 tmp_arg;

  tmp_arg = (arg < 0) ? -(UINT64) arg : (UINT64) arg;
  if (is_value_negative)
    {
      *is_value_negative = (arg < 0);
    }

  /* Copy the lower 64 bits into answer [9] ~ [16] (8 bytes) */
  numeric_put_uint64_to_be (answer + (DB_NUMERIC_BUF_SIZE - sizeof (DB_BIGINT)), tmp_arg);

  /* Pad extra bytes of answer accordingly [0] ~ [8] (9 bytes) */
  memset (answer, 0, DB_NUMERIC_BUF_SIZE - sizeof (DB_BIGINT));
}

/*
 * numeric_coerce_num_to_int () -
 *   return:
 *   arg(in)    : ptr to a DB_C_NUMERIC
 *   answer(out): ptr to an integer
 *   is_value_negative(in): sign of the value
 *
 * Note: This routine converts a numeric into an integer returns the result.
 * If arg overflows answer, answer is set to +/- MAXINT.
 */
void
numeric_coerce_num_to_int (DB_C_NUMERIC arg, int *answer, const bool is_value_negative)
{
  uint32_t digit;

  /* Copy the lower 32 bits into answer. */
  memcpy (&digit, arg + (DB_NUMERIC_BUF_SIZE - sizeof (int)), sizeof (digit));
  *answer = (int) NUMERIC_BSWAP32 (digit);

  /* Apply sign
   *  Negating INT_MIN (-2147483648) is signed-overflow UB in C.
   *  When the magnitude is exactly 0x80000000 and the value is negative,
   *  the result must be INT_MIN, so skip the negation for that case. */
  if (is_value_negative)
    {
      if ((uint32_t) (*answer) == 0x80000000U)
    {
      *answer = INT_MIN;
    }
      else
    {
      *answer = -(*answer);
    }
    }
}

/*
 * numeric_coerce_num_to_bigint () - Convert a NUMERIC value to a 64-bit BIGINT
 *   return          : NO_ERROR on success, or ER_IT_DATA_OVERFLOW 
 *   arg(in)         : Pointer to a DB_C_NUMERIC buffer
 *   scale(in)       : Number of decimal positions to adjust
 *   answer(out)     : Pointer to the resulting DB_BIGINT
 *   is_value_negative(in): Sign of the input value
 *
 * Note: Performs scale adjustment (division by 10^scale and rounding) and 
 *       converts the resulting value into a signed 64-bit integer.
 *       If the value exceeds the 64-bit BIGINT range, ER_IT_DATA_OVERFLOW 
 *       is returned.
 */
int
numeric_coerce_num_to_bigint (DB_C_NUMERIC arg, int scale, DB_BIGINT * answer, const bool is_value_negative)
{
  uint64_t work_buf[NUMERIC_AS_WORDS] = { 0 };
  int rounding_digit = 0;
  uint64_t magnitude;

  if (scale >= (int) (sizeof (powers_of_10) / sizeof (powers_of_10[0])))
    {
      return ER_IT_DATA_OVERFLOW;
    }

  /* 1. load DB_C_NUMERIC (Big-endian bytes) into word-aligned work_buf */
  work_buf[0] = (uint64_t) arg[0];
  memcpy (&work_buf[1], arg + 1, 16);
  work_buf[1] = NUMERIC_BSWAP64 (work_buf[1]);
  work_buf[2] = NUMERIC_BSWAP64 (work_buf[2]);

  /* 2. perform scale adjustment if scale > 0 (divide by 10^scale) */
  if (scale > 0)
    {
      rounding_digit = float_numeric_div_normalize (work_buf, NUMERIC_AS_WORDS, NUMERIC_AS_WORD_BYTES, scale);

      /* 3. round up if the fractional remainder is >= 5 */
      if (rounding_digit >= ROUND_HALF_UP_DIGIT)
    {
      float_numeric_increment (work_buf, NUMERIC_AS_WORDS, 1);
    }
    }

  /* 4. check if the value fits within 64 bits (upper words must be zero) */
  if (work_buf[0] != 0 || work_buf[1] != 0)
    {
      return ER_IT_DATA_OVERFLOW;
    }

  /* 5. BIGINT range check considering the sign
   *    positive range: [0, 2^63 - 1] (0x7FFFFFFFFFFFFFFF)
   *    negative range: [0, 2^63] (0x8000000000000000)
   */
  magnitude = work_buf[2];
  if (magnitude > (is_value_negative ? 0x8000000000000000ULL : 0x7FFFFFFFFFFFFFFFULL))
    {
      return ER_IT_DATA_OVERFLOW;
    }

  /* 6. form final signed BIGINT result
   *    INT64_MIN (magnitude == 0x8000000000000000) is a special case:
   *    negating (DB_BIGINT)INT64_MIN is signed-overflow UB in C.
   *    Since the stored value is already the absolute magnitude,
   *    we can produce the correct two's-complement INT64_MIN directly. */
  if (is_value_negative)
    {
      if (magnitude == 0x8000000000000000ULL)
    {
      *answer = INT64_MIN;
    }
      else
    {
      *answer = -(DB_BIGINT) magnitude;
    }
    }
  else
    {
      *answer = (DB_BIGINT) magnitude;
    }

  return NO_ERROR;
}

/*
 * numeric_coerce_dec_str_to_num () -
 *   return:
 *   dec_str(in): char * holds positive decimal digits as ASCII chars
 *   result(out): ptr to a DB_C_NUMERIC
 *   is_value_negative(out): sign of the value
 *
 * Note: 
 *   - Converts a character string containing decimal digits into a NUMERIC format.
 *
 *   - Uses Horner's Method for efficient accumulation:
 *     Instead of power-of-10 multiplication for each digit, it iteratively 
 *     multiplies the current sum and adds the next part.
 *     Horizontal Example: 123 = (1 * 10 + 2) * 10 + 3
 *
 *   - For optimal performance, the string is processed in 16-digit chunks 
 *     (using 64-bit arithmetic) to minimize the number of high-precision 
 *     numeric operations.
 */
void
numeric_coerce_dec_str_to_num (const char *dec_str, DB_C_NUMERIC result, bool * is_value_negative)
{
  int i, ntot_digits, ndigits;
  uint64_t chunk_value;
  bool is_negative = false, has_non_zero = false;
  const char *curr = dec_str;

  uint64_t result_word[NUMERIC_AS_WORDS] = { 0 };

  numeric_zero (result, DB_NUMERIC_BUF_SIZE);

  if (*curr == '-')
    {
      is_negative = true;
      curr++;
    }

  ntot_digits = (int) strlen (curr);

  /* fast path : bigint max(19) -1 = 18 */
  if (ntot_digits <= 18)
    {
      uint64_t tmp_digits = 0;
      while (*curr)
    {
      tmp_digits = tmp_digits * 10 + (*curr++ - '0');
    }

      numeric_coerce_bigint_to_num (tmp_digits, result, NULL);

      if (is_value_negative)
    {
      *is_value_negative = (is_negative && tmp_digits > 0);
    }
      return;
    }

  /* maximize performance by processing in 16-digit chunks (uint64 threshold), 
   * resulting in at most 3 iterations for 38-digit values. */
  ndigits = ntot_digits % 16;
  if (ndigits == 0 && ntot_digits > 0)
    {
      ndigits = 16;
    }

  while (ntot_digits > 0)
    {
      chunk_value = 0;
      for (i = 0; i < ndigits; i++)
    {
      chunk_value = chunk_value * 10 + (*curr++ - '0');
    }

      if (chunk_value != 0)
    {
      float_numeric_increment (result_word, NUMERIC_AS_WORDS, chunk_value);
      has_non_zero = true;
    }

      ntot_digits -= ndigits;
      if (ntot_digits > 0)
    {
      float_numeric_mul_pow10 (result_word, NUMERIC_AS_WORDS, NUMERIC_AS_WORD_BYTES,
                   _gv_mul_normalize_pow10_lookup[15]);
      ndigits = 16;
    }
    }

  numeric_words_to_bytes (result_word, NUMERIC_AS_WORDS, result);

  if (is_value_negative)
    {
      *is_value_negative = is_negative && has_non_zero;
    }
}

/*
 * numeric_coerce_num_to_dec_str () -
 *   return:
 *   num(in)    : DB_VALUE
 *   dec_str(out): returned string of decimal digits as ASCII chars
 *
 * Note: This routine converts a DB_C_NUMERIC into a character string that
 *       contains the decimal digits of the numeric encoded as ASCII characters.
 *
 *       it efficiently extracts digits using successive division by 10^16 ~ 10^19.
 *
 *       since a 17-byte (136-bit) numeric buffer represents up to approximately 
 *       40 decimal digits, 3 iterations (reaching 48 digits) are sufficient 
 *       to extract all significant digits.
 *
 *       the output is zero-padded to TWICE_NUM_MAX_PREC characters to maintain 
 *       compatibility with existing CUBRID decimal formatting routines.
 */
void
numeric_coerce_num_to_dec_str (const DB_VALUE * num_value, char *dec_str)
{
  uint64_t work_word[NUMERIC_AS_WORDS] = { 0 };
  uint64_t chunk;
  char *p_digits;
  int i, j;
  bool all_zero = true;

  assert (num_value);
  assert (dec_str);

  /* 1. handle negative sign */
  if (numeric_is_negative (num_value))
    {
      *dec_str++ = '-';
    }

  /* 2. pre-fill with '0' for legacy alignment */
  memset (dec_str, '0', TWICE_NUM_MAX_PREC);
  dec_str[TWICE_NUM_MAX_PREC] = '\0';

  /* 3. fast exit for zero */
  if (numeric_is_zero (db_locate_numeric (num_value)))
    {
      return;
    }

  /* 4. copy to local buffer for division */
  numeric_bytes_to_words (db_locate_numeric (num_value), DB_NUMERIC_BUF_SIZE, work_word, NUMERIC_AS_WORDS,
              NUMERIC_AS_WORD_BYTES);

  /* 5. successive division in 3 blocks of 16-digits
   *    17-byte buffer capacity (~40 digits) < 3 iterations (48 digits) */
  p_digits = dec_str + TWICE_NUM_MAX_PREC - 16;

  /* 
   * maximum decimal digits for 17-byte numeric is 40. 
   * 3 iterations cover up to 48 digits (16 * 3). 
   */
  for (i = 0; i < NUMERIC_AS_WORDS; i++)
    {
      /* extract remaining digits as a single 64-bit integer */
      chunk =
    float_numeric_div_pow10 (work_word, NUMERIC_AS_WORDS, NUMERIC_AS_WORD_BYTES,
                 _gv_mul_normalize_pow10_lookup[15]);

      /* convert and store 16 decimal digits as ASCII at once */
      numeric_pack_digits4_ascii (p_digits, chunk);
      p_digits -= 16;       /* move block unit to the left */

      /* exit loop if no more digits to divide (prevent unnecessary 3rd iteration) */
      all_zero = true;
      for (j = 0; j < NUMERIC_AS_WORDS; j++)
    {
      if (work_word[j] != 0)
        {
          all_zero = false;
          break;
        }
    }

      if (all_zero)
    {
      break;
    }
    }
}

/*
 * float_numeric_normalize_for_hash() - Normalize a NUMERIC value for hashing
 *   num(in)       : original numeric value
 *   calc_buf(out) : normalized buffer (size: DB_NUMERIC_BUF_SIZE)
 *   precision(in) : precision of the input
 *   scale(in)     : scale of the input
 *
 * Note: 
 *   - This function ensures that equivalent numeric values (e.g., 1.0 and 1.00) 
 *     result in the same binary representation to produce identical hash values.
 *   - Case scale > 0: Trailing zeros are removed by dividing by powers of 10 
 *     using an optimized "Binary Skip" approach (e.g., 10^16, 10^8, etc.).
 *   - Case scale < 0: The value is multiplied by 10 to increase its precision 
 *     up to the maximum 40 digits.
 */
void
float_numeric_normalize_for_hash (DB_C_NUMERIC num, uint8_t * calc_buf, int precision, int scale)
{
  int tmp_scale = 0;
  uint64_t word_buf[NUMERIC_AS_WORDS] = { 0 };

  assert (scale != 0);

  if (numeric_is_zero (num))
    {
      memset (calc_buf, 0, DB_NUMERIC_BUF_SIZE);
      return;
    }

  numeric_bytes_to_words (num, DB_NUMERIC_BUF_SIZE, word_buf, NUMERIC_AS_WORDS, NUMERIC_AS_WORD_BYTES);

  if (scale > 0)
    {
      /* divide by 10 repeatedly
       * remove trailing zeros by repeatedly dividing by powers of 10.
       * we use a chunked division (Binary Skip) approach with 10^19/16, 10^8, etc.,
       * to efficiently process multiple zeros in a single pass.
       */
      int i, step;
      uint64_t divisor, rem;
      static const int scale_steps[] = { 19, 8, 4, 2, 1 };
      static const int lookup_idx[] = { 18, 7, 3, 1, 0 };
      const int num_steps = 5;
      uint64_t backup_word[NUMERIC_AS_WORDS] = { 0 };

      tmp_scale = (scale > DB_MAX_NUMERIC_PRECISION ? DB_MAX_NUMERIC_PRECISION : scale);

      for (i = 0; i < num_steps; i++)
    {
      step = scale_steps[i];
      divisor = _gv_mul_normalize_pow10_lookup[lookup_idx[i]];
      while (tmp_scale >= step)
        {
          /* backup the current word state to restore if division fails (remainder != 0) */
          backup_word[0] = word_buf[0];
          backup_word[1] = word_buf[1];
          backup_word[2] = word_buf[2];

          rem = float_numeric_div_pow10 (word_buf, NUMERIC_AS_WORDS, NUMERIC_AS_WORD_BYTES, divisor);
          if (rem == 0)
        {
          tmp_scale -= step;
        }
          else
        {
          /* if the remainder is non-zero, restore the previous state and try a smaller step */
          word_buf[0] = backup_word[0];
          word_buf[1] = backup_word[1];
          word_buf[2] = backup_word[2];
          break;
        }
        }
    }
    }
  else
    {
      /* multiply by 10
       * multiplication increases precision, but limited to
       * DB_MAX_NUMERIC_PRECISION(40) digits maximum.
       */
      int max_multiply = DB_MAX_NUMERIC_PRECISION - precision;
      tmp_scale = (-(scale) > max_multiply) ? max_multiply : -(scale);

      assert (tmp_scale > 0);

      if (tmp_scale > 0)
    {
      float_numeric_mul_normalize (word_buf, NUMERIC_AS_WORDS, NUMERIC_AS_WORD_BYTES, tmp_scale);
    }
    }

  numeric_words_to_bytes (word_buf, NUMERIC_AS_WORDS, calc_buf);
}

/*
 * numeric_coerce_num_to_double () -
 *   return:
 *   num(in)    : DB_VALUE
 *   scale(in)  : integer value of the scale
 *   adouble(out): ptr to the returned double value
 *
 * Note: This routine converts a DB_C_NUMERIC into a double precision value.
 */
void
numeric_coerce_num_to_double (const DB_VALUE * num_value, int scale, double *adouble)
{
  char num_string[TWICE_NUM_MAX_PREC + 2];  /* 2: Sign, Null terminate */

  assert (num_value);

  /* Convert the numeric to a decimal string */
  numeric_coerce_num_to_dec_str (num_value, num_string);

  /* Convert the decimal string into a double */
  /* Problem at precision with line below */
  /* 123.445 was converted to 123.444999999999999999 */
  *adouble = atof (num_string) / pow (10.0, scale);

  /* TODO: [CUBRIDSUS-2637] revert to early code for now. adouble = atof (num_string); for (i = 0; i < scale; i++)
   * adouble /= 10; */
}

/*
 * numeric_is_fraction_part_zero () - check if fractional part of a numeric is
 *                    equal to 0
 * return : boolean
 * num (in)   : numeric value
 * scale (in) : scale of the numeric
 */
static bool
numeric_is_fraction_part_zero (const DB_VALUE * num_value, const int scale)
{
  int i, len = 0;
  char dec_str[NUMERIC_MAX_STRING_SIZE];

  assert (num_value);

  numeric_coerce_num_to_dec_str (num_value, dec_str);
  len = strlen (dec_str);
  for (i = 0; i < scale; i++)
    {
      if (dec_str[len - scale + i] != '0')
    {
      return false;
    }
    }
  return true;
}

/*
 * numeric_internal_double_to_num () -
 *   return: NO_ERROR, or ER_code
 *   adouble(in)        :
 *   dst_scale(in)      :
 *   num(in)    :
 *   prec(in)   :
 *   scale(in)  :
 *   is_value_negative(out): sign of the value
 */
int
numeric_internal_double_to_num (double adouble, int dst_scale, DB_C_NUMERIC num, int *prec, int *scale,
                bool * is_value_negative)
{
  assert (is_value_negative);
  return numeric_internal_real_to_num (adouble, dst_scale, num, prec, scale, false, is_value_negative);
}

/*
 * float_numeric_db_value_mod() - modulo two NUMERIC values and return the remainder
 *   return : NO_ERROR on success, or error code
 *
 * Note:
 *   - perform mod operation with double in existing db_mod_dbval.
 *   - float_numeric_db_value_mod() supports the extended NUMERIC range, 
 *     allowing operations with larger precision.
 *   - If the result precision exceeds DB_MAX_NUMERIC_PRECISION,
 *     the value is rounded and stored using only DB_MAX_NUMERIC_PRECISION significant digits.
 */
int
float_numeric_db_value_mod (const DB_VALUE * value1, const DB_VALUE * value2, DB_VALUE * result)
{
  int ret = NO_ERROR;
  int prec1 = 0, prec2 = 0, scale1 = 0, scale2 = 0;
  int extra_prec, needed_bytes, calc_words, calc_nbytes;
  int result_prec, result_scale;
  uint8_t result_buf[DB_NUMERIC_BUF_SIZE];
  bool result_sign = false;

  /* 
   * The following cases return 0 immediately without any operation
   * ex) 12 / 0 = err (=already handled in parsing)
   *     0 / 12 = 0
   */
  if (numeric_is_zero (db_locate_numeric (value1)))
    {
      memset (result_buf, 0, DB_NUMERIC_BUF_SIZE);
      result_prec = 1;
      result_scale = DB_VALUE_SCALE (value1);
      db_make_numeric (result, result_buf, result_prec, result_scale, DB_NUMERIC_BUF_SIZE, false, true);
      return NO_ERROR;
    }

  /* 1) compute exact precision values for mantissa calculations */
  db_get_numeric_precision_and_scale (value1, &prec1, &scale1, NULL);
  db_get_numeric_precision_and_scale (value2, &prec2, &scale2, NULL);

  /* 2) determine common sign of the result */
  result_sign = numeric_is_negative (value1);

  /* 3) compute exact exponent values for mantissa calculations */
  int dividend_exponent = (scale2 > scale1) ? (scale2 - scale1) : 0;
  int divisor_exponent = (scale1 > scale2) ? (scale1 - scale2) : 0;

  /* 4) determine exact scale for the result (MOD is independent of the quotient scale) */
  result_scale = (scale1 > scale2) ? scale1 : scale2;

  /* 5) initialize new calculation buffers and pad absolute values */
  extra_prec = (dividend_exponent > divisor_exponent) ? dividend_exponent : divisor_exponent;
  needed_bytes = _gv_numeric_precision_to_bytes_lookup[extra_prec];
  needed_bytes += ((int) DB_NUMERIC_BUF_SIZE + 2);
  calc_words = NUMERIC_GET_WORD_COUNT (needed_bytes);
  calc_nbytes = NUMERIC_GET_BYTE_COUNT (calc_words);

  uint64_t dividend_work[calc_words] = { 0 };
  uint64_t divisor_work[calc_words] = { 0 };
  uint64_t quotient_work[calc_words] = { 0 };
  uint64_t remainder_work[calc_words] = { 0 };

  numeric_bytes_to_words (db_locate_numeric (value1), DB_NUMERIC_BUF_SIZE, dividend_work, calc_words, calc_nbytes);
  numeric_bytes_to_words (db_locate_numeric (value2), DB_NUMERIC_BUF_SIZE, divisor_work, calc_words, calc_nbytes);

  /* 6) scale adjustments */
  if (dividend_exponent > 0)
    {
      float_numeric_mul_normalize (dividend_work, calc_words, calc_nbytes, dividend_exponent);
    }
  if (divisor_exponent > 0)
    {
      float_numeric_mul_normalize (divisor_work, calc_words, calc_nbytes, divisor_exponent);
    }

  /* fast path */
  if (calc_words == NUMERIC_AS_WORDS && (dividend_work[0] | dividend_work[1] | divisor_work[0] | divisor_work[1]) == 0)
    {
      float_numeric_mod_fast (dividend_work[2], divisor_work[2], result_buf, &result_prec, &result_scale, &result_sign);
      ret = float_numeric_check_overflow_and_adjust_scale (&result_prec, &result_scale, result);
      if (ret == NO_ERROR)
    {
      db_make_numeric (result, result_buf, result_prec, result_scale, DB_NUMERIC_BUF_SIZE, result_sign, true);
    }
      return ret;
    }

  float_numeric_knuth_div ((knuth_digit_t *) dividend_work, (knuth_digit_t *) divisor_work,
               (knuth_digit_t *) quotient_work, (knuth_digit_t *) remainder_work, calc_words);

  /* 7) check and recalculate precision/scale of the remainder result */
  result_prec = float_numeric_get_decimal_digit (remainder_work, calc_words);
  if (result_sign && result_prec == 1 && remainder_work[calc_words - 1] == 0)
    {
      /* Prevent -0; zero is always treated as positive. */
      result_sign = false;
    }
  ret = float_numeric_check_overflow_and_adjust_scale (&result_prec, &result_scale, result);
  if (ret != NO_ERROR)
    {
      return ret;
    }

  /* 8) round and pack to DB_NUMERIC_BUF_SIZE bytes */
  ret = float_numeric_round_and_pack (remainder_work, calc_words, calc_nbytes, result_buf, &result_prec, &result_scale);
  if (ret != NO_ERROR)
    {
      TP_DOMAIN *domain = tp_domain_resolve_default (DB_TYPE_NUMERIC);
      er_set (ER_WARNING_SEVERITY, ARG_FILE_LINE, ER_IT_DATA_OVERFLOW, 1, pr_type_name (TP_DOMAIN_TYPE (domain)));
      db_value_domain_init (result, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);
      return ret;
    }

  /* 9) store result */
  db_make_numeric (result, result_buf, result_prec, result_scale, DB_NUMERIC_BUF_SIZE, result_sign, true);

  return ret;
}

/*
 * numeric_internal_float_to_num () - converts a float to a DB_C_NUMERIC
 *
 * return: NO_ERROR or ER_code
 * afloat(in): floating-point value to be converted to NUMERIC
 * dst_scale(in): expected scale for the destination NUMERIC type
 * num(in): an allocated DB_C_NUMERIC to be filled with the converted numeric
 *      value
 * prec(out): resulting precision of the converted value
 * scale(out): resulting scale of the converted value
 */
int
numeric_internal_float_to_num (float afloat, int dst_scale, DB_C_NUMERIC num, int *prec, int *scale,
                   bool * is_value_negative)
{
  assert (is_value_negative);
  return numeric_internal_real_to_num (afloat, dst_scale, num, prec, scale, true, is_value_negative);
}

/*
 * fp_value_type() - returns the type of a given value of type double, as one
 *           of the above enumerators.
 *
 * returns: the type of the passed-in floating-point value
 * d(in):   floating-point value whose type is to be returned
 */
FP_VALUE_TYPE
get_fp_value_type (double d)
{
#ifdef WINDOWS
  /* actually the following symbols are dependent on the _MSC macro, not the WINDOWS macro */
  switch (_fpclass (d))
    {
    case _FPCLASS_NINF: /* -Inf */
    case _FPCLASS_PINF: /* +Inf */
      return FP_VALUE_TYPE_INFINITE;

    case _FPCLASS_SNAN: /* signaling NaN */
    case _FPCLASS_QNAN: /* quiet NaN */
      return FP_VALUE_TYPE_NAN;

    case _FPCLASS_NZ:       /* -0 */
    case _FPCLASS_PZ:       /* +0 */
      return FP_VALUE_TYPE_ZERO;

    default:
      return FP_VALUE_TYPE_NUMBER;
    }
#else
  switch (std::fpclassify (d))
    {
    case FP_INFINITE:
      return FP_VALUE_TYPE_INFINITE;
    case FP_NAN:
      return FP_VALUE_TYPE_NAN;
    case FP_ZERO:
      return FP_VALUE_TYPE_ZERO;
    default:
      return FP_VALUE_TYPE_NUMBER;
    }
#endif
}

/*
 * numeric_internal_real_to_num() - converts a floating point value (float or
 *                  double) to a DB_C_NUMERIC.
 *
 * return: NO_ERROR or ER_code
 * adouble(in): floating-point value to be converted to NUMERIC. May be either
 *      float promoted to double, or a double.
 * dst_scale(in):   expected scale of the destination NUMERIC data type
 * prec(out):       resulting precision of the converted value
 * scale(out):      resulting scale of the converted value
 * is_float(in):    indicates adouble is a float promoted to double
 * is_value_negative(out): sign of the value
 */
int
numeric_internal_real_to_num (double adouble, int dst_scale, DB_C_NUMERIC num, int *prec, int *scale, bool is_float,
                  bool * is_value_negative)
{
  char numeric_str[MAX (TP_DOUBLE_AS_CHAR_LENGTH + 1, NUMERIC_MAX_STRING_SIZE)];
  int i = 0;

  assert (is_value_negative);

  switch (get_fp_value_type (adouble))
    {
    case FP_VALUE_TYPE_INFINITE:
      return ER_IT_DATA_OVERFLOW;
    case FP_VALUE_TYPE_NAN:
    case FP_VALUE_TYPE_ZERO:
      /* currently CUBRID returns 0 for a NaN converted to NUMERIC (??) */
      *scale = dst_scale;
      *prec = dst_scale ? dst_scale : 1;

      while (i < *prec)
    {
      numeric_str[i++] = '0';
    }
      numeric_str[i] = '\0';

      numeric_coerce_dec_str_to_num (numeric_str, num, is_value_negative);
      return NO_ERROR;
    default:
      /* compare against pow(10, DB_MAX_NUMERIC_PRECISION) to check for overflow/underflow before actual conversion */
      if (NUMERIC_ABS (adouble) > DB_NUMERIC_OVERFLOW_LIMIT)
    {
      return ER_IT_DATA_OVERFLOW;
    }
      else
    {
      if (NUMERIC_ABS (adouble) < DB_NUMERIC_UNDERFLOW_LIMIT)
        {
          /* the floating-point number underflows any possible CUBRID NUMERIC domain type, so just return 0 with no
           * other conversion */
          *scale = dst_scale;
          *prec = dst_scale ? dst_scale : 1;

          while (i < *prec)
        {
          numeric_str[i++] = '0';
        }
          numeric_str[i] = '\0';

          numeric_coerce_dec_str_to_num ("0", num, is_value_negative);
          return NO_ERROR;
        }
      else
        {
          /* adouble might fit into a CUBRID NUMERIC domain type with sufficient precision. Invoke _dtoa() to get
           * the sequence of digits and the decimal point position */
          int decpt, sign;
          char *rve;
          int ndigits;

          if (is_float)
        {
          _dtoa (adouble, 0, TP_FLOAT_MANTISA_DECIMAL_PRECISION, &decpt, &sign, &rve, numeric_str + 1, 0);

          numeric_str[TP_FLOAT_MANTISA_DECIMAL_PRECISION + 1] = '\0';
        }
          else
        {
          _dtoa (adouble, 0, TP_DOUBLE_MANTISA_DECIMAL_PRECISION, &decpt, &sign, &rve, numeric_str + 1, 0);

          numeric_str[TP_DOUBLE_MANTISA_DECIMAL_PRECISION + 1] = '\0';
        }

          /* shift the digits in the sequence to make room for and to reach the decimal point */
          ndigits = strlen (numeric_str + 1);

          if (decpt <= 0)
        {
          /*
           * ex) 1.234e-10  (= 0.0000000001234)
           *   - digits="1234", ndigits=4, decpt=-9
           *   - scale = -decpt + ndigits = 9 + 4 = 13
           *
           * legacy:
           *   - materialize leading fractional zeros by shifting digits and prepending '0'
           *     -> "0000000001234" (then truncation by DB_MAX_NUMERIC_PRECISION may drop significant digits)
           *
           * new:
           *   - keep only significant digits and derive domain from (decpt, ndigits)
           *     -> "1234" with (prec=4, scale=13)
           */
          *scale = -decpt + ndigits;
          *prec = ndigits;

          if (*prec > DB_MAX_NUMERIC_PRECISION)
            {
              *scale -= (*prec - DB_MAX_NUMERIC_PRECISION);
              *prec = DB_MAX_NUMERIC_PRECISION;
            }

          /* keep only significant digits */
          numeric_str[1 + *prec] = '\0';
        }
          else
        {
          /* the numer is greater than 1, either insert the decimal point at the correct position in the digits
           * sequence, or append 0s to the digits from left to right until the decimal point is reached. */

          if (decpt > (DB_MAX_NUMERIC_PRECISION - DB_MIN_NUMERIC_SCALE))
            {
              /* should not happen since overflow has been checked for previously */
              return ER_IT_DATA_OVERFLOW;
            }
          else
            {
              if (decpt < ndigits)
            {
              *prec = ndigits;
              *scale = ndigits - decpt;
            }
              else
            {
              /* append 0s to the digits sequence until the decimal point is reached */

              char *dst = numeric_str + 1 + decpt, *src = numeric_str + 1 + ndigits;

              while (src != dst)
                {
                  *src++ = '0';
                }

              *src = '\0';

              *prec = decpt;
              *scale = 0;
            }

              if (*prec > DB_MAX_NUMERIC_PRECISION)
            {
              /* this path is taken only for inputs with a positive exponent (e.g., 1.0e+?).
               * in this case, the scale may be adjusted to a negative value. */
              *scale += (DB_MAX_NUMERIC_PRECISION - *prec);
              *prec = DB_MAX_NUMERIC_PRECISION;
            }
            }
        }

          /* append zeroes until dst_scale is reached */
          while (*prec < DB_MAX_NUMERIC_PRECISION && *scale < dst_scale)
        {
          numeric_str[1 + *prec] = '0';
          (*prec)++;
          (*scale)++;
        }

          numeric_str[1 + *prec] = '\0';

          /* The number without sign is now written in decimal in numeric_str */

          if (sign)
        {
          numeric_str[0] = '-';
          numeric_coerce_dec_str_to_num (numeric_str, num, is_value_negative);
        }
          else
        {
          numeric_coerce_dec_str_to_num (numeric_str + 1, num, is_value_negative);
        }

          return NO_ERROR;
        }
    }
      break;
    }
}

/*
 * analyze_numeric_string() () -
 *   return:
 *   astring(in) : Input numeric string to analyze
 *   astring_length(in) : Length of input string
 *   codeset(in) : International codeset for character handling
 *   negate_value(out) : Whether the value should be negated
 *   int_digits(out) : Extracted integer part digits
 *   int_len(out) : Length of integer part
 *   frac_digits(out) : Extracted fractional part digits
 *   frac_len(out) : Length of fractional part
 *   frac_first_sig_digit(out) : First non-zero position in fractional part
 *   frac_last_sig_digit(out) : Last non-zero position in fractional part
 *
 * Note: Parse and analyze numeric string to extract integer/fractional components
 */
static int
analyze_numeric_string (const char *astring, int astring_length, INTL_CODESET codeset, bool * negate_value,
            char *int_digits, int *int_len, char *frac_digits, int *frac_len, int *frac_first_sig_digit,
            int *frac_last_sig_digit, bool * is_zero)
{
  const int int_buf_max = (DB_MAX_NUMERIC_PRECISION - DB_MIN_NUMERIC_SCALE);    /* 254 */
  const int frac_buf_max = DB_MAX_NUMERIC_SCALE;    /* 252, excludes leading "0." */
  int int_count = 0;
  int frac_count = 0;
  int parse_pos = 0;
  int skip = 1;
  char current_char = '\0';
  bool has_digit = false;
  bool pad_character_zero = false;
  bool sign_found = false;
  bool trailing_spaces = false;

  *frac_first_sig_digit = *frac_last_sig_digit = -1;
  *negate_value = false;

  /* Step 1: Handle spaces, signs, and leading zeros */
  while (parse_pos < astring_length)
    {
      current_char = astring[parse_pos];

      if (current_char >= '1' && current_char <= '9')
    {
      has_digit = true;
      break;
    }
      else if (current_char == '0')
    {
      /* leading pad '0' found */
      pad_character_zero = true;
      parse_pos++;
      continue;
    }
      else if (current_char == '.')
    {
      break;
    }
      else if (current_char == '+' || current_char == '-')
    {
      /* Sign is only allowed before any digit (rejects duplicates and signs after a leading zero, e.g. '0-1') */
      if (sign_found || pad_character_zero)
        {
          return DOMAIN_INCOMPATIBLE;
        }
      sign_found = true;
      if (current_char == '-')
        {
          *negate_value = true;
        }
      parse_pos++;
      continue;
    }
      else if (intl_is_space (astring + parse_pos, NULL, codeset, &skip))
    {
      /* A space after a sign without any digit is invalid (e.g. '-   1'). */
      if (sign_found && !pad_character_zero)
        {
          return DOMAIN_INCOMPATIBLE;
        }
      /* A space after a leading zero ends the leading phase (e.g. '0   1' is invalid). */
      if (pad_character_zero)
        {
          trailing_spaces = true;
          break;
        }
      parse_pos += skip;    /* Skip spaces */
      continue;
    }
      else
    {
      /* Stray Non-numeric compatible character */
      return DOMAIN_INCOMPATIBLE;
    }
    }

  /* Step 2: Parse integer part */
  while (parse_pos < astring_length)
    {
      current_char = astring[parse_pos];

      if (current_char >= '0' && current_char <= '9' && !trailing_spaces)
    {
      pad_character_zero = false;
      has_digit = true; /* Case: "0.0000" */
      if (int_count >= int_buf_max) /* overflow when integer part exceeds the storable digit bound */
        {
          return ER_IT_DATA_OVERFLOW;
        }
      int_digits[int_count++] = current_char;
      parse_pos++;
      continue;
    }
      else if (current_char == '.' && !trailing_spaces)
    {
      /* '.' is only allowed immediately after a digit, not after trailing spaces (e.g. '1   .' is invalid) */
      parse_pos++;
      break;
    }
      else if (trailing_spaces && !intl_is_space (astring + parse_pos, NULL, codeset, &skip))
    {
      return DOMAIN_INCOMPATIBLE;
    }
      else if (intl_is_space (astring + parse_pos, NULL, codeset, &skip))
    {
      if (!trailing_spaces)
        {
          trailing_spaces = true;
        }
      parse_pos += skip;    /* Skip spaces */
      continue;
    }
      else if (current_char == ',')
    {
      /* Accept ',' character on integer part. */
      parse_pos++;
      continue;
    }
      else
    {
      return DOMAIN_INCOMPATIBLE;
    }
    }

  /* Step 3: Parse fractional part */
  while (parse_pos < astring_length)
    {
      current_char = astring[parse_pos];

      if (current_char >= '0' && current_char <= '9' && !trailing_spaces)
    {
      has_digit = true; /* Case: "0.0000" */
      if (frac_count <= frac_buf_max)   /* accept one extra digit beyond the storable bound, reserved for the round-decision */
        {
          frac_digits[frac_count] = current_char;
          /* Track non-zero digits */
          if (current_char != '0' || *frac_first_sig_digit >= 0)
        {
          if (*frac_first_sig_digit < 0)
            {
              *frac_first_sig_digit = frac_count;
              pad_character_zero = false;
            }
          *frac_last_sig_digit = frac_count;
        }
          frac_count++;
        }
      else if (*frac_first_sig_digit < 0)
        {
          /* fractional underflow: no significant digit found within representable bound → zero */
          pad_character_zero = true;
        }
      /* else: sig already found beyond bound — simply truncated; determine_prec_scale handles round/ovf */
      parse_pos++;
      continue;
    }
      else if (trailing_spaces && !intl_is_space (astring + parse_pos, NULL, codeset, &skip))
    {
      return DOMAIN_INCOMPATIBLE;
    }
      else if (intl_is_space (astring + parse_pos, NULL, codeset, &skip))
    {
      if (!trailing_spaces)
        {
          trailing_spaces = true;
        }
      parse_pos += skip;    /* Skip spaces */
      continue;
    }
      else
    {
      return DOMAIN_INCOMPATIBLE;
    }
    }

  if (pad_character_zero)
    {
      /* Zero padding case examples:
       * 0 : pad_character_zero(t), has_digit(f), int_count(0), frac_count(0)
       * 0.0 : pad_character_zero(t), has_digit(t), int_count(0), frac_count(1)
       */
      int_digits[0] = '\0';
      frac_digits[0] = '\0';
      *int_len = 1;
      *frac_len = frac_count;
      *is_zero = true;
    }
  else if (!has_digit && int_count == 0 && frac_count == 0)
    {
      /*
       * no valid digit was found in input.
       * reject strings that do not contain any numeric digit.
       *
       * examples:
       *   '+', '-'                  (sign only)
       *   '.', ' . ', '   .   '     (decimal point only)
       *   '', ' ', '    '               (whitespace only)
       *   '+.', '-.', ' + . '       (sign + non-digit combinations)
       */
      return DOMAIN_INCOMPATIBLE;
    }
  else
    {
      /* Normal case: add null terminators based on filled length */
      int_digits[int_count] = '\0';
      frac_digits[frac_count] = '\0';
      *int_len = int_count;
      *frac_len = frac_count;
    }

  return NO_ERROR;
}

/*
 * determine_prec_scale() () -
 *   return:
 *   int_digits(in) : Integer part digits
 *   int_len(in) : Length of integer part
 *   frac_digits(in) : Fractional part digits
 *   frac_len(in) : Length of fractional part
 *   frac_first_sig_digit(in) : First non-zero position in fractional part
 *   frac_last_sig_digit(in) : Last non-zero position in fractional part
 *   out_num_string(out) : Output numeric string
 *   out_prec(out) : Calculated precision
 *   out_scale(out) : Calculated scale
 *
 * Note: Calculate precision and scale based on numeric string analysis
 */
static void
determine_prec_scale (const char *int_digits, int int_len, const char *frac_digits, int frac_len,
              int frac_first_sig_digit, int frac_last_sig_digit, char *out_num_string, int *out_prec,
              int *out_scale)
{
  int total = int_len + frac_len;
  const char *tmp_int_digits = NULL, *tmp_frac_digits = NULL;
  int tmp_int_len = 0, tmp_frac_len = 0;
  char next_digit = '0';
  int tmp_prec = 0, tmp_scale = 0;
  int frac_zero_cnt = 0;
  bool need_round = false;

  /* Step 1: Calculate temporary precision/scale and determine copy positions for each case */
  if (frac_len == 0)
    {
      /* Case 1: Only integer part exists */
      if (int_len <= DB_MAX_NUMERIC_PRECISION)
    {
      /* Case 1-A: When length is within 40 digits, use only the integer part */
      tmp_prec = int_len;
      tmp_scale = 0;
      tmp_int_digits = int_digits;
      tmp_int_len = int_len;
    }
      else
    {
      /* Case 1-B: Apply negative scale based on trailing zero count */
      tmp_prec = DB_MAX_NUMERIC_PRECISION;
      tmp_scale = DB_MAX_NUMERIC_PRECISION - int_len;
      tmp_int_digits = int_digits;
      tmp_int_len = DB_MAX_NUMERIC_PRECISION;
      /* Get the 41th digit for rounding decision (array index 40 since arrays start from 0) */
      next_digit = tmp_int_digits[DB_MAX_NUMERIC_PRECISION];
      need_round = true;
    }
    }
  else if (int_len == 0)
    {
      /* Case 2: Only fractional part exists */
      int nz_len = frac_last_sig_digit - frac_first_sig_digit + 1;
      if (frac_len <= DB_MAX_NUMERIC_PRECISION)
    {
      /* Case 2-A: When length is within 40 digits, use only the fractional part. 
         Precision is defined from the left-most nonzero digit to the right-most known digit. */
      tmp_prec = nz_len;
      tmp_scale = frac_len;
      tmp_frac_digits = frac_digits;
      tmp_frac_len = frac_len;
    }
      else
    {
      /* Case 2-B: Skip leading zeros in the fractional part and use up to MAX_PRECISION digits */
      if (nz_len > DB_MAX_NUMERIC_PRECISION)
        {
          tmp_prec = DB_MAX_NUMERIC_PRECISION;
          tmp_scale = MIN (frac_len, DB_MAX_NUMERIC_SCALE);
          tmp_frac_digits = frac_digits + frac_first_sig_digit;
          tmp_frac_len = DB_MAX_NUMERIC_PRECISION;
          next_digit = frac_digits[frac_first_sig_digit + DB_MAX_NUMERIC_PRECISION];
          need_round = true;
          frac_zero_cnt = frac_len - nz_len;    /* Count pure zeros */
        }
      else
        {
          tmp_prec = nz_len;
          tmp_scale = frac_len;
          tmp_frac_digits = frac_digits + frac_first_sig_digit;
          tmp_frac_len = nz_len;
          need_round = false;
          if (tmp_scale > DB_MAX_NUMERIC_SCALE)
        {
          next_digit = tmp_frac_digits[nz_len - 1];
          frac_zero_cnt = frac_len - nz_len;    /* Count pure zeros */
          need_round = true;
        }
        }
    }
    }
  else
    {
      /* Case 3: Both integer and fractional parts exist */
      if (total <= DB_MAX_NUMERIC_PRECISION)
    {
      /* Case 3-A: When total length is within 40 digits, use both integer and fractional parts */
      tmp_prec = total;
      tmp_scale = frac_len;
      tmp_int_digits = int_digits;
      tmp_int_len = int_len;
      tmp_frac_digits = frac_digits;
      tmp_frac_len = frac_len;
    }
      else
    {
      /* Case 3-B: When total length exceeds 40 digits, determine whether to round or apply negative scale 
         depending on which part (integer vs fractional) has more digits */
      int drop_total = total - DB_MAX_NUMERIC_PRECISION;
      if (drop_total <= frac_len)
        {
          /* Case 3-B-1: Fractional part is larger – round based on the fractional part */
          int keep_frac = frac_len - drop_total;
          tmp_prec = int_len + keep_frac;
          tmp_scale = keep_frac;
          tmp_int_digits = int_digits;
          tmp_int_len = int_len;
          tmp_frac_digits = frac_digits;
          tmp_frac_len = keep_frac;
          next_digit = frac_digits[keep_frac];
          need_round = true;
        }
      else
        {
          /* Case 3-B-2: Integer part is larger – handle by trimming the integer part */
          int drop_int = drop_total - frac_len;
          int keep_int = int_len - drop_int;
          tmp_prec = keep_int;
          tmp_scale = -drop_int;
          tmp_int_digits = int_digits;
          tmp_int_len = keep_int;
          next_digit = int_digits[keep_int];
          need_round = true;
        }
    }
    }

  /* Step 2: Range validation (before memcpy) */
  if (tmp_prec > DB_MAX_NUMERIC_PRECISION || tmp_scale < DB_MIN_NUMERIC_SCALE)
    {
      // Error handling done outside
      *out_prec = tmp_prec;
      *out_scale = tmp_scale;
      return;
    }

  /* Step 3: Actual string copy */
  char *tmp_num_string = out_num_string;
  if (tmp_int_len)
    {
      memcpy (tmp_num_string, tmp_int_digits, tmp_int_len);
      tmp_num_string += tmp_int_len;
    }

  if (tmp_frac_len)
    {
      memcpy (tmp_num_string, tmp_frac_digits, tmp_frac_len);
      tmp_num_string += tmp_frac_len;
    }
  *tmp_num_string = '\0';

  /* Step 4: Rounding and digit adjustment */
  if (need_round)
    {
      (void) determine_round (out_num_string, &tmp_prec, &tmp_scale, tmp_int_len, tmp_frac_len, frac_zero_cnt,
                  next_digit);
    }

  *out_prec = tmp_prec;
  *out_scale = tmp_scale;
}

/*
 * determine_round() () -
 *   return:
 *   out_str(in/out) : Numeric string to be rounded
 *   out_prec(in/out) : Precision (total significant digits)
 *   out_scale(in/out) : Scale (fractional digits)
 *   tmp_int_len(in) : Integer part length
 *   tmp_frac_len(in) : Fractional part length
 *   frac_zero_cnt(in) : Leading zeros in fractional part
 *   rounding_digit(in) : Next digit for rounding decision
 *
 * Note: Round numeric string and adjust precision/scale
 */
static void
determine_round (char *out_str, int *out_prec, int *out_scale, int tmp_int_len, int tmp_frac_len, int frac_zero_cnt,
         char next_digit)
{
  int prec = *out_prec;
  int scale = *out_scale;
  int digit_pos = prec - 1;
  int max_frac_zero_cnt = 212;  // DB_MAX_NUMERIC_SCALE(252) - DB_MAX_NUMERIC_PRECISION(40)

  // Step 1: Scale exceeds maximum limit (252) - truncate first (pure decimal case only)
  if (scale > DB_MAX_NUMERIC_SCALE && tmp_int_len == 0 && frac_zero_cnt > max_frac_zero_cnt)
    {
      prec--;
      if (prec > 0)
    {
      // Normal truncate: 0.000...000123 (scale 253) -> 0.000...00012 (scale 252)
      out_str[prec] = '\0';
      digit_pos = prec - 1;
    }
      else
    {
      // Underflow case: 0.000...0001 (prec 1, scale 253) -> 0.000...0000 (prec 1, scale 252)
      out_str[0] = '0';
      out_str[1] = '\0';
      frac_zero_cnt--;
      prec = 1;
      digit_pos = 0;
    }
    }

  // Step 2: Perform rounding based on the truncated digit
  if (next_digit >= '5')
    {
      while (digit_pos >= 0 && out_str[digit_pos] == '9')
    {
      out_str[digit_pos] = '0';
      digit_pos--;
    }

      if (digit_pos >= 0)
    {
      // Normal case: 123.456...5 -> 123.456...6
      out_str[digit_pos] += 1;
    }
      else
    {
      // All digits were '9', overflow occurred - create "1000..." pattern
      out_str[0] = '1';
      if (tmp_int_len == 0)
        {
          if (frac_zero_cnt == 0)
        {
          // Pure decimal case: 0.9999... -> 1.000...
          tmp_int_len = 1;
          prec = DB_MAX_NUMERIC_PRECISION;
          scale = (prec - 1);
        }
          else
        {
          // Pure decimal case: 0.000...000999 -> 0.000...001
          frac_zero_cnt--;

          if (scale > DB_MAX_NUMERIC_SCALE)
            {
              prec++;
            }
          memset (out_str + 1, '0', prec - 1);
        }
        }
      else
        {
          // Integer with decimal: 99999.99999... -> 100000.0
          memset (out_str + 1, '0', prec);
          scale -= 1;
        }
    }
    }

  // Step 3: Add null terminator
  out_str[prec] = '\0';

  // Step 4: Recalculate scale based on rounding result
  if (tmp_int_len == 0)
    {
      // Pure decimal case: 0.8888... -> 0.888...9
      scale = frac_zero_cnt + prec;
    }

  *out_prec = prec;
  *out_scale = scale;
}

/*
 * numeric_coerce_string_to_num () -
 *   return:
 *   astring(in) : ptr to the input character string
 *   astring_length(in) : length of the input character string
 *   codeset(in) : codeset of string
 *   result(out) : DB_VALUE of type numeric
 *
 * Note: This routine converts a string into a DB_VALUE.
 *   It is not localized in relation to fractional and digit
 *   grouping symbols.
 */
int
numeric_coerce_string_to_num (const char *astring, int astring_length, INTL_CODESET codeset, DB_VALUE * result)
{
  char num_string[DB_MAX_NUMERIC_PRECISION + 1];
  unsigned char num[DB_NUMERIC_BUF_SIZE];
  bool negate_value = false;
  bool is_zero = false;
  int prec, scale;
  int int_len, frac_len;
  int frac_first_sig_digit, frac_last_sig_digit;
  char int_digits[NUMERIC_MAX_STRING_SIZE]; /* Integer part valid digits */
  char frac_digits[NUMERIC_MAX_STRING_SIZE];    /* Fractional part valid digits */
  int ret = NO_ERROR;
  TP_DOMAIN *domain;

  /* Parse and compute precision/scale */
  ret =
    analyze_numeric_string (astring, astring_length, codeset, &negate_value, int_digits, &int_len, frac_digits,
                &frac_len, &frac_first_sig_digit, &frac_last_sig_digit, &is_zero);
  if (ret != NO_ERROR)
    {
      if (ret == ER_IT_DATA_OVERFLOW)
    {
      domain = tp_domain_resolve_default (DB_TYPE_NUMERIC);
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_IT_DATA_OVERFLOW, 1, pr_type_name (TP_DOMAIN_TYPE (domain)));
    }
      goto exit_on_error;
    }

  assert (int_len > 0 || frac_len > 0);
  if (is_zero)
    {
      /* Zero case */
      prec = 1;
      scale = MIN (frac_len, DB_MAX_NUMERIC_SCALE);
      num_string[0] = '0';
      num_string[1] = '\0';
      negate_value = false;
    }
  else
    {
      (void) determine_prec_scale (int_digits, int_len, frac_digits, frac_len, frac_first_sig_digit,
                   frac_last_sig_digit, num_string, &prec, &scale);
      assert (scale <= DB_MAX_NUMERIC_SCALE);
      /* If there is no overflow, try to parse the decimal string */
      if (prec > DB_MAX_NUMERIC_PRECISION || scale < DB_MIN_NUMERIC_SCALE)
    {
      domain = tp_domain_resolve_default (DB_TYPE_NUMERIC);
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_IT_DATA_OVERFLOW, 1, pr_type_name (TP_DOMAIN_TYPE (domain)));
      ret = ER_IT_DATA_OVERFLOW;
      goto exit_on_error;
    }
    }

  /* Convert decimal string to base-256 binary */
  numeric_coerce_dec_str_to_num (num_string, num, NULL);

  db_make_numeric (result, num, prec, scale, DB_NUMERIC_BUF_SIZE, negate_value, true);

  return ret;

exit_on_error:

  db_value_domain_init (result, DB_TYPE_NUMERIC, DB_DEFAULT_NUMERIC_PRECISION, DB_DEFAULT_NUMERIC_SCALE);

  return (ret == NO_ERROR && (ret = er_errid ()) == NO_ERROR) ? ER_FAILED : ret;
}

/*
 * numeric_coerce_num_to_num () -
 *   return: NO_ERROR, or ER_code
 *   src_value(in)       : Pointer to the source DB_VALUE
 *   src_prec(in)        : Precision of the source numeric
 *   src_scale(in)       : Scale of the source numeric
 *   dest_prec(in)       : Target precision
 *   dest_scale(in)      : Target scale
 *   dest_num(out)       : Resulting DB_C_NUMERIC buffer
 *   dest_num_is_negative(out): Pointer to store the sign of the result
 * Note: This routine converts a numeric of a given precision and scale to
 * another precision and scale.
 */
int
numeric_coerce_num_to_num (const DB_VALUE * src_value, int src_prec, int src_scale, int dest_prec, int dest_scale,
               DB_C_NUMERIC dest_num, bool * dest_num_is_negative)
{
  int ret = NO_ERROR;
  int src_actual_prec = 0;
  int final_prec = 0;
  bool is_value_negative = false;
  bool round_up = false;

  assert (src_value);
  assert (dest_num_is_negative);

  is_value_negative = numeric_is_negative (src_value);

  /* 1. trivial case: copy immediately if no conversion is needed */
  if (dest_prec == DB_DEFAULT_NUMERIC_PRECISION || (src_prec <= dest_prec && src_scale == dest_scale))
    {
      numeric_copy (dest_num, db_locate_numeric (src_value));
      *dest_num_is_negative = is_value_negative;
      return NO_ERROR;
    }

  /* 2. for fixed numeric values, check the actual number of significant digits in the input. */
  src_actual_prec = numeric_get_precision_digits (db_locate_numeric (src_value));

  /* 3. fast zero check: if the significant digit count is 1 and the last byte of the buffer is 0, 
   *    it is guaranteed to be zero (zero is treated as precision 1). */
  if (src_actual_prec == 1 && (db_locate_numeric (src_value))[DB_NUMERIC_BUF_SIZE - 1] == 0)
    {
      numeric_zero (dest_num, DB_NUMERIC_BUF_SIZE);
      *dest_num_is_negative = false;
      return NO_ERROR;
    }

  uint64_t result_word[NUMERIC_AS_WORDS] = { 0 };
  int scale_diff = dest_scale - src_scale;
  int required_prec = src_actual_prec + scale_diff;

  /* 4. pre-check for overflow and underflow (guaranteed zero). */
  if (required_prec > dest_prec)
    {
      ret = ER_IT_DATA_OVERFLOW;
      goto exit_on_error;
    }
  else if (required_prec < 0)
    {
      numeric_zero (dest_num, DB_NUMERIC_BUF_SIZE);
      *dest_num_is_negative = false;
      return NO_ERROR;
    }

  /* 5. prepare a temporary working buffer (17 bytes) and copy. */
  numeric_bytes_to_words (db_locate_numeric (src_value), DB_NUMERIC_BUF_SIZE, result_word, NUMERIC_AS_WORDS,
              NUMERIC_AS_WORD_BYTES);

  /* 6. scale adjustment (aligning the decimal position). */
  if (scale_diff > 0)
    {
      /* increase scale: multiply by 10^delta. */
      float_numeric_mul_normalize (result_word, NUMERIC_AS_WORDS, NUMERIC_AS_WORD_BYTES, scale_diff);
    }
  else if (scale_diff < 0)
    {
      /* decrease scale: perform truncation and rounding decisions. */
      int drop = -scale_diff;
      uint8_t last_digit = float_numeric_div_normalize (result_word, NUMERIC_AS_WORDS, NUMERIC_AS_WORD_BYTES, drop);

      /* half-up rounding */
      if (last_digit >= ROUND_HALF_UP_DIGIT)
    {
      (void) float_numeric_increment (result_word, NUMERIC_AS_WORDS, 1);
      round_up = true;
    }
    }

  /* 7. determine the final precision (final_prec) and re-check for overflow during rounding. */
  final_prec = (required_prec == 0) ? 1 : required_prec;
  if (round_up)
    {
      /* scan the actual buffer to accurately check for precision changes after rounding (e.g., 9.9 -> 10.0). */
      final_prec = float_numeric_get_decimal_digit (result_word, NUMERIC_AS_WORDS);
      if (final_prec > dest_prec)
    {
      ret = ER_IT_DATA_OVERFLOW;
      goto exit_on_error;
    }
    }

  /* 8. save result and prevent negative zero. */
  numeric_words_to_bytes (result_word, NUMERIC_AS_WORDS, dest_num);

  /* if final_prec is 1 and LSB is 0, it's always zero (covers cases where computation result becomes zero). */
  if (is_value_negative && final_prec == 1 && dest_num[DB_NUMERIC_BUF_SIZE - 1] == 0)
    {
      is_value_negative = false;
    }
  *dest_num_is_negative = is_value_negative;

  return NO_ERROR;

exit_on_error:

  return (ret == NO_ERROR && (ret = er_errid ()) == NO_ERROR) ? ER_FAILED : ret;
}

/*
 * get_significant_digit () -
 *   return: significant digit of integer value
 *   i(in) :
 */
static int
get_significant_digit (DB_BIGINT i)
{
  int n = 0;

  do
    {
      n++;
      i /= 10;
    }
  while (i != 0);

  return n;
}

/*
 * float_numeric_mul_pow10() - Multiply a base-256 big-endian buffer by multiplier
 *
 * Note:
 *   - Used for scale adjustment by shifting the decimal position right.
 *   - The multiplier must be a power of 10 that fits in a uint64_t.
 *   - Processes in 64-bit word chunks using __int128 for high performance.
 *   - Expects no overflow after multiplication (assert(carry == 0)).
 */
static void
float_numeric_mul_pow10 (uint64_t * dbv_buf, int calc_words, int calc_bytes, uint64_t multiplier)
{
  int i = 0;
  uint128_t res = 0;
  uint128_t carry = 0;

  uint64_t *word = dbv_buf;
  for (i = calc_words - 1; i >= 0; i--)
    {
      res = (uint128_t) word[i] * multiplier + carry;
      word[i] = (uint64_t) res;
      carry = res >> 64;
    }

  assert (carry == 0);
}

/*
 * float_numeric_mul_normalize() - multiply a base-256 big-endian buffer by 10^exponent
 *
 * Purpose:
 *  - adjust the internal decimal scale by multiplying the stored coefficient by 10^exponent.
 *    this is mainly used to align decimal positions between two values before arithmetic or
 *    formatting (e.g., shifting digits into the integer part for negative scales, or
 *    rescaling the counterpart operand so both share a common decimal position).
 *
 * Note:
 *  - the exponent is processed in chunks to prevent internal arithmetic overflow.
 *
 * Reason:
 *   - 64-bit word-wise multiplication using __int128:
 *     Temp = (word * 10^k) + carry must fit in uint128_t (~3.4 * 10^38).
 *     - If k=19: Max temp ~ 2^64 * 10^19 ~ 1.84 * 10^38 -> SAFE.
 *     - If k=20: Max temp ~ 2^64 * 10^20 ~ 1.84 * 10^39 -> Potential overflow.
 *     ==> Safe chunk size is 19.
 */
static void
float_numeric_mul_normalize (uint64_t * dbv_buf, int calc_words, int calc_bytes, int exponent)
{
  int step = 0;
  uint64_t multiplier = 0;

  assert (exponent > 0);

  while (exponent > 0)
    {
      step = (exponent > 19) ? 19 : exponent;
      multiplier = _gv_mul_normalize_pow10_lookup[step - 1];    // 10^step

      float_numeric_mul_pow10 (dbv_buf, calc_words, calc_bytes, multiplier);
      exponent -= step;
    }
}

/*
 * float_numeric_div_pow10() - Divide a base-256 big-endian buffer by divisor
 *
 * Note:
 *   - Used for scale reduction, rounding, and normalization.
 *   - The divisor must be a power of 10 that fits in a uint64_t.
 *   - Processes in 64-bit word chunks using __int128 for high performance.
 */
static uint64_t
float_numeric_div_pow10 (uint64_t * dbv_buf, int calc_words, int calc_bytes, uint64_t divisor)
{
  uint64_t rem10 = 0;
  int i = 0;

  uint128_t temp = 0;
  uint64_t *word_ptr = dbv_buf;

  for (i = 0; i < calc_words; i++)
    {
      temp = ((uint128_t) rem10 << 64) | word_ptr[i];
      word_ptr[i] = (uint64_t) (temp / divisor);
      rem10 = (uint64_t) (temp % divisor);
    }

  return rem10;
}

/*
 * float_numeric_div_normalize() - divide a base-256 big-endian buffer by 10^exponent
 *
 * Important:
 *   - this function MUST NOT be used to detect or remove trailing zeros.
 *     it adjusts decimal position by dividing the internal coefficient by 10^exponent
 *     for normalization and rounding, but it does not preserve information needed
 *     for trailing-zero analysis.
 *
 * Purpose:
 *   1. Adjust the coefficient by dividing it by 10^exponent.
 *   2. Return the MSB of the discarded fractional part to decide rounding.
 *
 *  example:
 *    a 50-digit value with exponent = 7 becomes a 43-digit value.
 *    the function returns the most significant digit of the discarded 7-digit block
 *    (i.e., the 44th digit) for rounding decision.
 *
 * Note:
 *   - Processes the exponent in chunks (19 digits) to prevent overflow,
 *     consistent with float_numeric_mul_normalize().
 */
static int
float_numeric_div_normalize (uint64_t * dbv_buf, int calc_words, int calc_bytes, int exponent)
{
  uint64_t last_rem = 0;
  uint64_t divisor = 0;
  int step = 0;
  int last_step = 0;

  assert (exponent > 0);

  while (exponent > 0)
    {
      step = exponent > 19 ? 19 : exponent;
      divisor = _gv_mul_normalize_pow10_lookup[step - 1];

      last_rem = float_numeric_div_pow10 (dbv_buf, calc_words, calc_bytes, divisor);
      last_step = step;
      exponent -= step;
    }

  if (last_step <= 1)
    {
      return (int) last_rem;    // 0..9
    }

  divisor = _gv_mul_normalize_pow10_lookup[last_step - 2];  // 10^(last_step-1)
  return (int) (last_rem / divisor);    // 0..9
}

/*
 * float_numeric_increment() - Increment a base-256 big-endian buffer by val
 *
 * Note:
 *   - Used for rounding, mainly to increment by 1
 */
static void
float_numeric_increment (uint64_t * calc_buf, int calc_words, uint64_t val)
{
  int i = 0;
  uint64_t temp = 0;
  uint64_t next_carry = 0;
  uint64_t carry = val;
  for (i = calc_words - 1; i >= 0 && carry; i--)
    {
      temp = calc_buf[i] + carry;
      next_carry = (temp < calc_buf[i]);
      calc_buf[i] = temp;
      carry = next_carry;
    }
}

/*
 * numeric_operation_compare() - Compare two byte-based numeric buffers
 *   return        : 1 if arg1 > arg2, -1 if arg1 < arg2, 0 if equal
 *   dbv1_buf(in)  : First byte buffer (MSB-first)
 *   dbv2_buf(in)  : Second byte buffer (MSB-first)
 *   calc_bytes(in): Number of bytes to compare
 */
static int
numeric_operation_compare (const uint8_t * dbv1_buf, const uint8_t * dbv2_buf, int calc_bytes)
{
  int cmp_result = memcmp (dbv1_buf, dbv2_buf, calc_bytes);
  if (cmp_result == 0)
    {
      return 0;
    }
  /* memcmp returns <0 if first differing byte in dbv1_buf is less than in dbv2_buf */
  return (cmp_result < 0) ? -1 : 1;
}

/*
 * float_numeric_operation_compare() - Compare two word-based numeric buffers
 *   return        : 1 if arg1 > arg2, -1 if arg1 < arg2, 0 if equal
 *   arg1_word(in) : First word buffer (MSB-first)
 *   arg2_word(in) : Second word buffer (MSB-first)
 *   calc_words(in): Number of words to compare
 *
 * Note: Performs high-speed word-by-word comparison starting from the MSB.
 */
static int
float_numeric_operation_compare (const uint64_t * arg1_word, const uint64_t * arg2_word, int calc_words)
{
  int digit;

  for (digit = 0; digit < calc_words; digit++)
    {
      if (arg1_word[digit] > arg2_word[digit])
    {
      return 1;
    }
      else if (arg1_word[digit] < arg2_word[digit])
    {
      return -1;
    }
    }
  return 0;
}

/*
 * float_numeric_check_overflow_and_adjust_scale() - Check precision overflow and adjust scale
 *
 * calc_buf(in)      : working buffer for calculation
 * calc_bytes(in)    : size of the working buffer
 * result_prec(in/out): result precision (calculated from calc_buf, may be adjusted)
 * result_scale(in/out): result scale (may be reduced if precision overflows)
 * answer(out)       : DB_VALUE to initialize on overflow error
 *
 * return: NO_ERROR on success, ER_IT_DATA_OVERFLOW on overflow
 *
 * Note:
 *   - Calculates the decimal digit count from calc_buf
 *   - If precision exceeds DB_MAX_NUMERIC_PRECISION, adjusts result_scale
 *   - If adjusted scale is below DB_MIN_NUMERIC_SCALE, returns overflow error
 */
static int
float_numeric_check_overflow_and_adjust_scale (int *result_prec, int *result_scale, DB_VALUE * answer)
{
  int error = NO_ERROR;
  int precision = *result_prec;
  int scale = *result_scale;

  assert (precision > 0);

  if (precision > DB_MAX_NUMERIC_PRECISION)
    {
      scale -= (precision - DB_MAX_NUMERIC_PRECISION);
      *result_scale = scale;
    }

  if (scale < DB_MIN_NUMERIC_SCALE)
    {
      TP_DOMAIN *domain = tp_domain_resolve_default (DB_TYPE_NUMERIC);
      er_set (ER_WARNING_SEVERITY, ARG_FILE_LINE, ER_IT_DATA_OVERFLOW, 1, pr_type_name (TP_DOMAIN_TYPE (domain)));
      db_value_domain_init (answer, DB_TYPE_NUMERIC, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE);
      error = ER_IT_DATA_OVERFLOW;
    }

  return error;
}

/*
 * float_numeric_round_and_pack() - Round and pack intermediate NUMERIC buffer
 *
 * calc_buf(in/out): working buffer for calculation
 * calc_bytes(in)  : size of the working buffer
 * result_buf(out) : final result buffer (DB_NUMERIC_BUF_SIZE)
 * result_prec(in/out): result precision (adjusted after rounding)
 * result_scale(in/out): result scale (may be reduced if rounding overflows)
 *
 * Note:
 *   - Used to reduce extended numeric precision down to DB_MAX_NUMERIC_PRECISION,
 *     apply half-up rounding, and pack into the fixed-size NUMERIC buffer.
 */
static int
float_numeric_round_and_pack (uint64_t * word_buf, int calc_words, int calc_nbytes, uint8_t * result_buf,
                  int *result_prec, int *result_scale)
{
  int last_digit = 0;
  int drop = 0;
  int round_prec = 0;

  drop = *result_prec - DB_MAX_NUMERIC_PRECISION;
  if (drop <= 0)
    {
      numeric_words_to_bytes (word_buf, calc_words, result_buf);
      return NO_ERROR;
    }

  /* if more than 41 digits, truncate to 40 digits and round at the 41st digit */
  *result_prec = DB_MAX_NUMERIC_PRECISION;

  /*
   * divide the value up to 40 digits and store the 41th digit in last_digit for rounding check.
   * reduces digits and returns the most significant digit of the truncated portion for rounding.
   */
  last_digit = float_numeric_div_normalize (word_buf, calc_words, calc_nbytes, drop);

  /* half-up rounding: if last_digit >= 5, increment the buffer by 1 */
  if (last_digit >= ROUND_HALF_UP_DIGIT)
    {
      (void) float_numeric_increment (word_buf, calc_words, 1);
      round_prec = float_numeric_get_decimal_digit (word_buf, calc_words);
      if (round_prec > DB_MAX_NUMERIC_PRECISION)
    {
      /* reduces digits for normalization; does not perform rounding */
      (void) float_numeric_div_normalize (word_buf, calc_words, calc_nbytes, 1);
      (*result_scale)--;
      if (*result_scale < DB_MIN_NUMERIC_SCALE)
        {
          return ER_IT_DATA_OVERFLOW;
        }
      round_prec = DB_MAX_NUMERIC_PRECISION;
    }
    }

  /* copy the final DB_MAX_NUMERIC_PRECISION-digit value into result_buf (DB_NUMERIC_BUF_SIZE) */
  numeric_words_to_bytes (word_buf, calc_words, result_buf);

  return NO_ERROR;
}

/*
 * compare_mantissa_same_exponent() - Compare the mantissas of two NUMERIC values (only valid when exponents are equal)
 *   return: 1: dividend > divisor
 *          -1: dividend < divisor  
 *           0: equal
 */
static int
compare_mantissa_same_exponent (uint64_t * dividend_word, uint64_t * divisor_word,
                int calc_words, int calc_nbytes, int prec1, int prec2)
{
  assert (prec1 <= DB_MAX_NUMERIC_PRECISION);
  assert (prec2 <= DB_MAX_NUMERIC_PRECISION);

  int prec_diff = prec1 - prec2;

  /*
   * normalize precision by multiplying the shorter side by 10^diff (avoid division)
   * - prec1 > prec2 : divisor *= 10^(prec1-prec2)
   * - prec1 < prec2 : dividend *= 10^(prec2-prec1)
   */
  if (prec_diff > 0)
    {
      (void) float_numeric_mul_normalize (divisor_word, calc_words, calc_nbytes, prec_diff);
    }
  else if (prec_diff < 0)
    {
      (void) float_numeric_mul_normalize (dividend_word, calc_words, calc_nbytes, -prec_diff);
    }

  return float_numeric_operation_compare (dividend_word, divisor_word, calc_words);
}

/*
 * float_numeric_compare_rem_round_up() - compare remainder with divisor to determine rounding
 *   return        : 1 (round up), -1 (do not round up), 0 (equal)
 *   rem(in)       : Remainder buffer from division (MSB-first)
 *   div(in)       : Divisor buffer (MSB-first)
 *   calc_words(in): Number of words in the buffers
 *
 * Note:
 *   - Determines rounding by checking: (2 * Remainder >= Divisor)
 *   - Correctly handles potential overflow during doubling by checking the MSB first.
 *   - Performs high-speed word-by-word comparison starting from the MSB.
 */
static int
float_numeric_compare_rem_round_up (const uint64_t * rem, const uint64_t * div, int calc_words)
{
  int i;
  uint64_t next_carry, temp_rem;

  /* 1. if the most significant bit (MSB) is 1, then (2 * Remainder) will 
   *    always be greater than or equal to the divisor (since R < V). 
   */
  if (rem[0] >> 63)
    {
      assert (float_numeric_operation_compare (rem, div, calc_words) < 0);
      return 1;
    }

  /* 2. compare doubled remainder with divisor word-by-word from MSB to LSB. */
  for (i = 0; i < calc_words; i++)
    {
      /* double the current word including the carry bit from the next word. */
      next_carry = (i + 1 < calc_words) ? (rem[i + 1] >> 63) : 0;
      temp_rem = (rem[i] << 1) | next_carry;

      /* compare the doubled word with the divisor word immediately. */
      if (temp_rem > div[i])
    {
      return 1;
    }
      else if (temp_rem < div[i])
    {
      return -1;
    }
    }
  return 0;
}

/*
 * numeric_pack_digits4_ascii () -
 *   return:
 *   buf(out) : buffer to store ASCII digits
 *   val(in)  : 64-bit integer value (0 ~ 10^16 - 1)
 * 
 * Note:
 *   - Converts 16-digit decimal value to ASCII string in O(1) time.
 *   - Uses 64-bit division once and 32-bit division twice.
 *   - Uses LUT for fast ASCII conversion (no loops).
 */
static inline void
numeric_pack_digits4_ascii (char *buf, uint64_t val)
{
  uint64_t h8, l8;
  uint32_t digits[4];

  assert (buf);

  /* step 1: split 16-digit value into two 8-digit groups (one 64-bit division).
   * separates the total 16 digits (val) into upper 8 digits (h8) and lower 8 digits (l8). */
  h8 = val / 100000000ULL;
  l8 = val % 100000000ULL;

  /* step 2: split 8-digit groups into 4-digit blocks (32-bit math for better parallelism). */
  digits[0] = (uint32_t) (h8 / 10000);
  digits[1] = (uint32_t) (h8 % 10000);
  digits[2] = (uint32_t) (l8 / 10000);
  digits[3] = (uint32_t) (l8 % 10000);

  /* step 3: reference LUT to pack 4-byte ASCII blocks at once.
   * stores d1, d2, d3, d4 in sequence to the buffer starting from buf+0. */
  *(uint32_t *) (buf + 0) = numeric_get_digits4_ascii (digits[0]);
  *(uint32_t *) (buf + 4) = numeric_get_digits4_ascii (digits[1]);
  *(uint32_t *) (buf + 8) = numeric_get_digits4_ascii (digits[2]);
  *(uint32_t *) (buf + 12) = numeric_get_digits4_ascii (digits[3]);
}

/* 
 * Read a 64-bit value from a big-endian byte array and convert it to host order
 * (BSWAP64 is a no-op on big-endian CPUs).
 */
static inline uint64_t
numeric_get_uint64_from_be (const void *ptr)
{
  uint64_t val;
  memcpy (&val, ptr, sizeof (val));
  return NUMERIC_BSWAP64 (val);
}

/* 
 * Write a 64-bit value in host order to a big-endian byte array
 * (BSWAP64 ensures correct byte order regardless of CPU endianness).
 */
static inline void
numeric_put_uint64_to_be (void *ptr, uint64_t val)
{
  uint64_t swapped = NUMERIC_BSWAP64 (val);
  memcpy (ptr, &swapped, sizeof (swapped));
}

/*
 * numeric_bytes_to_words() - Convert a NUMERIC byte buffer to a word-based buffer
 *   src(in)       : Source byte array (MSB-first)
 *   src_bytes(in) : Number of bytes in source
 *   dest(out)     : Destination word array (MSB-first)
 *   dest_words(in): Number of words in destination
 *   dest_bytes(in): Total size of destination in bytes
 *
 * Endianness & layout transformation:
 *
 *   [CUBRID NUMERIC: 17-byte big-endian]
 *     b0  b1  b2  ...              b16
 *     <- MSB                   LSB ->
 *
 *   - Group into 64-bit words (MSB-first order preserved; memcpy only):
 *     w0            w1             w2
 *     [b0]       [b1..b8]       [b9..b16]
 *     <- MSB                   LSB ->
 *
 *   - Each 64-bit chunk is loaded and converted to host order:
 *     (memcpy + optional BSWAP64, unlike the memcpy-only step above)
 *
 *     Example (8-byte chunk):
 *       big-endian bytes : 01 02 03 04 05 06 07 08
 *
 *       little-endian CPU:
 *         - 08 07 06 05 04 03 02 01 (reversed via BSWAP64)
 *
 *       big-endian CPU:
 *         - 01 02 03 04 05 06 07 08 (no change)
 *
 * Summary:
 *   - Word order remains big-endian (MSW -> LSW)
 *   - Each word is stored in host endianness for efficient arithmetic
 *   - This hybrid layout enables fast 64-bit operations
 *
 * Note:
 *   - Includes a fast path optimized for the standard 17-byte layout.
 */
static void
numeric_bytes_to_words (const uint8_t * src, int src_bytes, uint64_t * dest, int dest_words, int dest_bytes)
{
  /* [FAST PATH] optimized for the standard 17-byte NUMERIC layout */
  if (src_bytes == DB_NUMERIC_BUF_SIZE && dest_words >= NUMERIC_AS_WORDS)
    {
      /* zero higher words if the destination buffer is larger than 3 words */
      if (dest_words > NUMERIC_AS_WORDS)
    {
      memset (dest, 0, dest_bytes);
    }

      /* map 17-byte layout directly:
       *   src[0]      → MSB (1 byte)
       *   src[1..8]   → middle 64-bit word
       *   src[9..16]  → LSB 64-bit word
       */
      dest[dest_words - 3] = (uint64_t) src[0];
      memcpy (&dest[dest_words - 2], src + 1, 16);
      dest[dest_words - 2] = NUMERIC_BSWAP64 (dest[dest_words - 2]);
      dest[dest_words - 1] = NUMERIC_BSWAP64 (dest[dest_words - 1]);
      return;
    }

  /* [VARIABLE PATH] generic conversion for arbitrary byte lengths */
  memset (dest, 0, dest_bytes);
  int full_words = NUMERIC_GET_FULL_WORDS (src_bytes);
  int rem_bytes = NUMERIC_GET_REM_BYTES (src_bytes);
  int current_word_idx = dest_words - 1;
  int i;

  /* load full 64-bit words from LSB side (right-aligned) */
  for (i = 0; i < full_words && current_word_idx >= 0; i++)
    {
      dest[current_word_idx--] = numeric_get_uint64_from_be (src + src_bytes - 8 * (i + 1));
    }

  /* handle remaining leading bytes (< 8 bytes) */
  if (rem_bytes > 0 && current_word_idx >= 0)
    {
      uint64_t val = 0;
      /* build a partial word in big-endian order */
      for (i = 0; i < rem_bytes; i++)
    {
      val = (val << 8) | src[i];
    }
      dest[current_word_idx] = val;
    }
}

/*
 * numeric_words_to_bytes() - Convert a word-based buffer back to a NUMERIC byte buffer
 *   src(in)       : Source word array (MSB-first)
 *   src_words(in) : Number of words in source
 *   dest(out)     : Destination byte array (fixed size: DB_NUMERIC_BUF_SIZE)
 *
 * Note:
 *   - Packs the least significant 3 words into the 17-byte CUBRID NUMERIC format.
 *   - The format is fixed at 17 bytes, so at least 3 words (192 bits) are required.
 *   - Results are rounded to 40 digits, so 3 words are sufficient.
 */
static void
numeric_words_to_bytes (const uint64_t * src, int src_words, uint8_t * dest)
{
  /* pointer to the 3 least significant words (MSW → LSW within this range) */
  const uint64_t *lsb_ptr = src + (src_words - NUMERIC_AS_WORDS);

#if !defined (NDEBUG)
  /* ensure no overflow: higher words beyond the 3-word range must be zero */
  for (int i = 0; i < src_words - NUMERIC_AS_WORDS; i++)
    {
      assert (src[i] == 0);
    }
  /* ensure the MSW fits into 1 byte (17-byte layout constraint) */
  assert ((lsb_ptr[0] >> 8) == 0);
#endif

  /*
   * 17-byte NUMERIC layout (big-endian):
   *   dest[0]      : most significant byte (high part of mantissa, no sign)
   *   dest[1..8]   : middle 64 bits  (lsb_ptr[1])
   *   dest[9..16]  : least 64 bits   (lsb_ptr[2])
   */
  dest[0] = (uint8_t) (lsb_ptr[0] & 0xFF);
  uint64_t temp_word[2];
  temp_word[0] = NUMERIC_BSWAP64 (lsb_ptr[1]);
  temp_word[1] = NUMERIC_BSWAP64 (lsb_ptr[2]);
  memcpy (dest + 1, temp_word, sizeof (temp_word));
}

/*
 * numeric_db_value_coerce_to_num () -
 *   return: NO_ERROR, or ER_code
 *   src(in)     : ptr to a DB_VALUE of some numerical type
 *   dest(in/out): ptr to a DB_VALUE of type DB_TYPE_NUMERIC
 *   data_status(out): ptr to a DB_DATA_STATUS value
 *
 * Note: This routine converts a DB_VALUE of some numerical type into a
 * DB_VALUE of type DB_TYPE_NUMERIC.  The precision and scale fields of
 * are assumed to represent the desired values of the output.  If they are
 * set to DB_DEFAULT_PRECISION/SCALE, the default values are implied. If
 * they are set to 0, the precision and scale are set to be the maximum
 * amount necessary in order to preserve as much data as possible.
 */
int
numeric_db_value_coerce_to_num (DB_VALUE * src, DB_VALUE * dest, DB_DATA_STATUS * data_status)
{
  int ret = NO_ERROR;
  unsigned char num[DB_NUMERIC_BUF_SIZE];   /* copy of a DB_C_NUMERIC */
  int precision, scale;
  int desired_precision, desired_scale;
  bool num_is_negative = false;

  *data_status = DATA_STATUS_OK;
  desired_precision = DB_VALUE_PRECISION (dest);
  desired_scale = DB_VALUE_SCALE (dest);
  /* Check for a non NULL src and a dest whose type is DB_TYPE_NUMERIC */
  /* Switch on the src type */
  switch (DB_VALUE_TYPE (src))
    {
    case DB_TYPE_DOUBLE:
      {
    double adouble = db_get_double (src);
    ret = numeric_internal_double_to_num (adouble, desired_scale, num, &precision, &scale, &num_is_negative);
    break;
      }

    case DB_TYPE_FLOAT:
      {
    float adouble = (float) db_get_float (src);
    ret = numeric_internal_float_to_num (adouble, desired_scale, num, &precision, &scale, &num_is_negative);
    break;
      }

    case DB_TYPE_MONETARY:
      {
    double adouble = db_value_get_monetary_amount_as_double (src);
    ret = numeric_internal_double_to_num (adouble, desired_scale, num, &precision, &scale, &num_is_negative);
    break;
      }

    case DB_TYPE_INTEGER:
      {
    int anint = db_get_int (src);

    numeric_coerce_int_to_num (anint, num, &num_is_negative);
    precision = get_significant_digit (anint);
    scale = 0;
    break;
      }

    case DB_TYPE_SMALLINT:
      {
    int anint = (int) db_get_short (src);

    numeric_coerce_int_to_num (anint, num, &num_is_negative);
    precision = get_significant_digit (anint);
    scale = 0;
    break;
      }

    case DB_TYPE_BIGINT:
      {
    DB_BIGINT bigint = db_get_bigint (src);

    numeric_coerce_bigint_to_num (bigint, num, &num_is_negative);
    precision = get_significant_digit (bigint);
    desired_precision = MAX (desired_precision, precision);
    scale = 0;
    break;
      }

    case DB_TYPE_NUMERIC:
      {
    bool src_is_float_numeric = false;
    db_get_numeric_precision_and_scale (src, &precision, &scale, &src_is_float_numeric);

    if (!src_is_float_numeric && precision == (unsigned char) DB_HJOIN_NUMERIC_PRECISION_DEFERRED)
      {
        precision = numeric_get_precision_digits (db_locate_numeric (src));
      }

    numeric_copy (num, db_locate_numeric (src));
    num_is_negative = numeric_is_negative (src);
    break;
      }

    case DB_TYPE_ENUMERATION:
      {
    int anint = db_get_enum_short (src);
    numeric_coerce_int_to_num (anint, num, &num_is_negative);
    precision = 5;
    scale = 0;
    break;
      }

    default:
      ret = ER_FAILED;
      break;
    }

  /* Make the destination value */
  if (ret == NO_ERROR)
    {
      if (desired_precision == DB_DEFAULT_NUMERIC_PRECISION)
    {
      db_make_numeric (dest, num, precision, scale, DB_NUMERIC_BUF_SIZE, num_is_negative, true);
      return ret;
    }

      /* Make the intermediate value */
      bool dest_value_is_negative = num_is_negative;
      db_make_numeric (dest, num, precision, scale, DB_NUMERIC_BUF_SIZE, dest_value_is_negative, false);
      ret =
    numeric_coerce_num_to_num (dest, DB_VALUE_PRECISION (dest), DB_VALUE_SCALE (dest),
                   desired_precision, desired_scale, num, &dest_value_is_negative);
      if (ret != NO_ERROR)
    {
      goto exit_on_error;
    }

      db_make_numeric (dest, num, desired_precision, desired_scale, DB_NUMERIC_BUF_SIZE, dest_value_is_negative, false);
    }

  if (ret == ER_IT_DATA_OVERFLOW)
    {
      *data_status = DATA_STATUS_TRUNCATED;
    }

  return ret;

exit_on_error:

  if (ret == ER_IT_DATA_OVERFLOW)
    {
      *data_status = DATA_STATUS_TRUNCATED;
    }

  return (ret == NO_ERROR && (ret = er_errid ()) == NO_ERROR) ? ER_FAILED : ret;
}

/*
 * numeric_db_value_coerce_from_num () -
 *   return: NO_ERROR, or ER_code
 *   src(in)     : ptr to a DB_VALUE of type DB_TYPE_NUMERIC
 *   dest(out)   : ptr to a DB_VALUE of some numerical type
 *   data_status(out): ptr to a DB_DATA_STATUS value
 *
 * Note: This routine converts a DB_VALUE of type DB_TYPE_NUMERIC into some
 * numerical type.
 */
int
numeric_db_value_coerce_from_num (DB_VALUE * src, DB_VALUE * dest, DB_DATA_STATUS * data_status)
{
  int ret = NO_ERROR;
  int scale = db_get_numeric_scale (src, NULL);


  *data_status = DATA_STATUS_OK;
  /* Check for a DB_TYPE_NUMERIC src and a non NULL numerical dest */
  /* Switch on the dest type */
  switch (DB_VALUE_DOMAIN_TYPE (dest))
    {
    case DB_TYPE_DOUBLE:
      {
    double adouble;
    numeric_coerce_num_to_double (src, scale, &adouble);
    if (OR_CHECK_DOUBLE_OVERFLOW (adouble))
      {
        ret = ER_IT_DATA_OVERFLOW;
        goto exit_on_error;
      }
    db_make_double (dest, adouble);
    break;
      }

    case DB_TYPE_FLOAT:
      {
    double adouble;
    numeric_coerce_num_to_double (src, scale, &adouble);
    if (OR_CHECK_FLOAT_OVERFLOW (adouble))
      {
        ret = ER_IT_DATA_OVERFLOW;
        goto exit_on_error;
      }
    db_make_float (dest, (float) adouble);
    break;
      }

    case DB_TYPE_MONETARY:
      {
    double adouble;
    numeric_coerce_num_to_double (src, scale, &adouble);
    db_make_monetary (dest, DB_CURRENCY_DEFAULT, adouble);
    break;
      }

    case DB_TYPE_INTEGER:
      {
    double adouble;
    numeric_coerce_num_to_double (src, scale, &adouble);
    if (OR_CHECK_INT_OVERFLOW (adouble))
      {
        ret = ER_IT_DATA_OVERFLOW;
        goto exit_on_error;
      }
    db_make_int (dest, (int) ROUND (adouble));
    break;
      }

    case DB_TYPE_BIGINT:
      {
    DB_BIGINT bint;
    ret = numeric_coerce_num_to_bigint (db_locate_numeric (src), scale, &bint, numeric_is_negative (src));
    if (ret != NO_ERROR)
      {
        goto exit_on_error;
      }

    db_make_bigint (dest, bint);
    break;
      }

    case DB_TYPE_SMALLINT:
      {
    double adouble;
    numeric_coerce_num_to_double (src, scale, &adouble);
    if (OR_CHECK_SHORT_OVERFLOW (adouble))
      {
        ret = ER_IT_DATA_OVERFLOW;
        goto exit_on_error;
      }
    db_make_short (dest, (DB_C_SHORT) ROUND (adouble));
    break;
      }

    case DB_TYPE_NUMERIC:
      {
    ret = numeric_db_value_coerce_to_num (src, dest, data_status);
    break;
      }

    case DB_TYPE_CHAR:
    case DB_TYPE_VARCHAR:
      {
    char *return_string = NULL;
    char str_buf[NUMERIC_MAX_STRING_SIZE];
    int size = 0;
    DB_TYPE type;

    numeric_db_value_print (src, str_buf);
    size = strlen (str_buf);
    return_string = (char *) db_private_alloc (NULL, size + 1);
    if (return_string == NULL)
      {
        assert (er_errid () != NO_ERROR);
        return er_errid ();
      }

    strcpy (return_string, str_buf);
    type = DB_VALUE_DOMAIN_TYPE (dest);
    if (type == DB_TYPE_CHAR)
      {
        db_make_char (dest, size, return_string, size, LANG_SYS_CODESET, LANG_SYS_COLLATION);
      }
    else if (type == DB_TYPE_VARCHAR)
      {
        db_make_varchar (dest, size, return_string, size, LANG_SYS_CODESET, LANG_SYS_COLLATION);
      }
    dest->need_clear = true;
    break;
      }

    case DB_TYPE_TIME:
      {
    double adouble;
    DB_TIME v_time;
    int hour, minute, second;

    numeric_coerce_num_to_double (src, scale, &adouble);
    v_time = (int) (adouble + 0.5) % SECONDS_IN_A_DAY;
    db_time_decode (&v_time, &hour, &minute, &second);
    db_make_time (dest, hour, minute, second);
    break;
      }

    case DB_TYPE_DATE:
      {
    double adouble;
    DB_DATE v_date;
    int year, month, day;

    numeric_coerce_num_to_double (src, scale, &adouble);
    v_date = (DB_DATE) (adouble);
    db_date_decode (&v_date, &month, &day, &year);
    db_make_date (dest, month, day, year);
    break;
      }

    case DB_TYPE_TIMESTAMP:
      {
    double adouble;
    DB_TIMESTAMP v_timestamp;

    numeric_coerce_num_to_double (src, scale, &adouble);
    v_timestamp = (DB_TIMESTAMP) (adouble);
    db_make_timestamp (dest, v_timestamp);
    break;
      }

    case DB_TYPE_DATETIME:
      {
    DB_BIGINT bi, tmp_bi;
    DB_DATETIME v_datetime;

    ret = numeric_coerce_num_to_bigint (db_locate_numeric (src), scale, &bi, numeric_is_negative (src));
    if (ret == NO_ERROR)
      {
        /* make datetime value from interval value */
        tmp_bi = (DB_BIGINT) (bi / MILLISECONDS_OF_ONE_DAY);
        if (OR_CHECK_INT_OVERFLOW (tmp_bi))
          {
        ret = ER_IT_DATA_OVERFLOW;
          }
        else
          {
        v_datetime.date = (int) tmp_bi;
        v_datetime.time = (int) (bi % MILLISECONDS_OF_ONE_DAY);
        db_make_datetime (dest, &v_datetime);
          }
      }
    break;
      }

    default:
      ret = DOMAIN_INCOMPATIBLE;
      break;
    }

  return ret;

exit_on_error:

  return (ret == NO_ERROR && (ret = er_errid ()) == NO_ERROR) ? ER_FAILED : ret;
}

/*
 * numeric_db_value_coerce_from_num_strict () - coerce a numeric to the type
 *                      of dest
 * return : error code or NO_ERROR
 * src (in) : the numeric value
 * dest(in/out) : the value to coerce to
 */
int
numeric_db_value_coerce_from_num_strict (DB_VALUE * src, DB_VALUE * dest)
{
  int ret = NO_ERROR;
  int scale = db_get_numeric_scale (src, NULL);

  switch (DB_VALUE_DOMAIN_TYPE (dest))
    {
    case DB_TYPE_DOUBLE:
      {
    double adouble;
    numeric_coerce_num_to_double (src, scale, &adouble);
    if (OR_CHECK_DOUBLE_OVERFLOW (adouble))
      {
        return ER_FAILED;
      }
    db_make_double (dest, adouble);
    break;
      }

    case DB_TYPE_FLOAT:
      {
    double adouble;
    numeric_coerce_num_to_double (src, scale, &adouble);
    if (OR_CHECK_FLOAT_OVERFLOW (adouble))
      {
        return ER_FAILED;
      }
    db_make_float (dest, (float) adouble);
    break;
      }

    case DB_TYPE_MONETARY:
      {
    double adouble;
    numeric_coerce_num_to_double (src, scale, &adouble);
    if (OR_CHECK_FLOAT_OVERFLOW (adouble))
      {
        return ER_FAILED;
      }
    db_make_monetary (dest, DB_CURRENCY_DEFAULT, adouble);
    break;
      }

    case DB_TYPE_INTEGER:
      {
    double adouble;
    numeric_coerce_num_to_double (src, scale, &adouble);
    if (OR_CHECK_INT_OVERFLOW (adouble) || !numeric_is_fraction_part_zero (src, scale))
      {
        return ER_FAILED;
      }
    db_make_int (dest, (int) (adouble));
    break;
      }

    case DB_TYPE_BIGINT:
      {
    DB_BIGINT bint;

    ret = numeric_coerce_num_to_bigint (db_locate_numeric (src), scale, &bint, numeric_is_negative (src));
    if (ret != NO_ERROR || !numeric_is_fraction_part_zero (src, scale))
      {
        return ER_FAILED;
      }
    db_make_bigint (dest, bint);
    break;
      }

    case DB_TYPE_SMALLINT:
      {
    double adouble;
    numeric_coerce_num_to_double (src, scale, &adouble);
    if (OR_CHECK_SHORT_OVERFLOW (adouble) || !numeric_is_fraction_part_zero (src, scale))
      {
        return ER_FAILED;
      }
    db_make_short (dest, (DB_C_SHORT) ROUND (adouble));
    break;
      }

    case DB_TYPE_NUMERIC:
      {
    DB_DATA_STATUS data_status = DATA_STATUS_OK;
    ret = numeric_db_value_coerce_to_num (src, dest, &data_status);
    break;
      }

    default:
      ret = ER_FAILED;
      break;
    }

  return ER_FAILED;
}

/*
 * numeric_db_value_print () -
 *   return: a static character buffer that contains the numeric printed in an
 *           ASCII format.
 *   val(in)    : DB_VALUE of type numeric to print
 *
 * Note: returns the null-terminated string form of val
 */
char *
numeric_db_value_print (const DB_VALUE * val, char *buf)
{
  char temp[NUMERIC_MAX_STRING_SIZE];
  int nbuf;
  int temp_size;
  int i;
  bool found_first_non_zero = false;
  int scale = db_get_numeric_scale (val, NULL);

  /* it should not be static because the parameter could be changed without broker restart */
  bool oracle_compat_number = prm_get_bool_value (PRM_ID_ORACLE_COMPAT_NUMBER_BEHAVIOR);

  assert (val);
  assert (buf);

  if (DB_IS_NULL (val))
    {
      buf[0] = '\0';
      return buf;
    }

  /* Retrieve raw decimal string */
  numeric_coerce_num_to_dec_str (val, temp);

  /* Remove the extra padded zeroes and add the decimal point */
  nbuf = 0;
  temp_size = (int) strnlen (temp, sizeof (temp));
  for (i = 0; i < temp_size; i++)
    {
      /* Add the negative sign */
      if (temp[i] == '-')
    {
      buf[nbuf++] = '-';
    }

      /* Add decimal point */
      if (i == temp_size - scale)
    {
      int k = temp_size - 1;

      if (oracle_compat_number)
        {
          /* remove trailing zero */
          while (k > i && temp[k] == '0')
        {
          k--;
        }

          temp_size = k + 1;
          if (temp[k] == '0')
        {
          continue;
        }
          else if (k >= i)
        {
          buf[nbuf++] = '.';
        }
        }
      else
        {
          buf[nbuf++] = '.';
        }
    }

      /* Check to see if the first significant digit has been found */
      if (!found_first_non_zero && temp[i] >= '1' && temp[i] <= '9')
    {
      found_first_non_zero = true;
    }

      /* Remove leading zeroes */
      if (found_first_non_zero || i >= temp_size - scale - 1)
    {
      buf[nbuf++] = temp[i];
    }
    }

  /* Null terminate */
  buf[nbuf] = '\0';

  /* Handling negative scale: append zeros to the right of the decimal point */
  if (scale < 0)
    {
      int abs_scale = -scale;

      if (!found_first_non_zero)
    {
      // If no digit found: output only a single '0'
      buf[0] = '0';
      nbuf = 1;
    }
      else
    {
      // If digits exist: append '0' abs_scale times
      for (i = 0; i < abs_scale; i++)
        {
          buf[nbuf++] = '0';
        }
    }
      buf[nbuf] = '\0';
    }

  return buf;
}

/*
 * numeric_db_value_is_zero () -
 *   return: bool
 *   arg(in)    : DB_VALUE of type DB_NUMERIC
 *
 * Note: This routine checks if arg = 0.
 *       This function returns:
 *           true   if    arg1 = 0 and
 *           false  otherwise.
 *
 */
bool
numeric_db_value_is_zero (const DB_VALUE * arg)
{
  if (DB_IS_NULL (arg))     /* NULL values are not 0 */
    {
      return false;
    }
  else
    {
      return (numeric_is_zero (db_get_numeric (arg)));
    }
}

int
numeric_get_precision_digits (uint8_t * calc_buf)
{
  uint64_t word_buf[NUMERIC_AS_WORDS] = { 0 };
  numeric_bytes_to_words (calc_buf, DB_NUMERIC_BUF_SIZE, word_buf, NUMERIC_AS_WORDS, NUMERIC_AS_WORD_BYTES);
  return float_numeric_get_decimal_digit (word_buf, NUMERIC_AS_WORDS);
}