On Tue, 2005-11-01 at 17:55 -0500, Tom Lane wrote:
> "Jim C. Nasby" <[EMAIL PROTECTED]> writes:
> > FWIW, most databases I've used limit NUMERIC to 38 digits, presumably to
> > fit length info into 1 or 2 bytes. So there's something to be said for a
> > small numeric type that has less overhead and a large numeric (what we
> > have today).
> 
> I don't think it'd be worth having 2 types.  Remember that the weight is
> measured in base-10k digits.  Suppose for instance
>       sign            1 bit
>       weight          7 bits (-64 .. +63)
>       dscale          8 bits (0..255)
> This gives us a dynamic range of 1e-256 to 1e255 as well as the ability
> to represent up to 255 displayable fraction digits.  Does anyone know
> any real database applications where that's not enough?
> 
> (I'm neglecting NaN here in supposing we need only 1 bit for sign,
> but we could have a special encoding for NaN.  Perhaps disallow the
> weight = -64 case and use that to signal NaN.)

I've coded a short patch to do this, which is the result of two
alternate patches and some thinking, but maybe not enough yet.

The patch given here is different on two counts from above:

This sets...
#define NUMERIC_MAX_PRECISION           64

since

#define NUMERIC_MAX_RESULT_SCALE        (NUMERIC_MAX_PRECISION * 2)

We don't seem to be able to use all of the bits actually available to us
in the format. Perhaps we need to decouple these now? Previously, we had
room for 14 bits, which gave a maximum of 16384. We were using
NUMERIC_MAX of 1000, so doubling it didn't give problems.

The above on-disk format showed sign & weight together, whereas the
current code has sign and dscale together. Trying to put sign and weight
together is somewhat difficult, since weight is itself a signed value. 

I coded it up that way around, which is reasonably straightforward but
harder than the patch enclosed here. But AFAICS - which isn't that far
normally I grant you, doing things that way around would require some
twos-complement work to get things correct when weight is negative. That
worries me.

IMHO we should accept the step down in maximum numeric precision (down
to "only" 64 digits) rather than put extra processing into every
manipulation of a NUMERIC datatype. With luck, I've misunderstood and we
can have both performance and precision. 

If not, I commend 64 digits to you as sufficient for every imaginable
purpose - saving 2 bytes off every numeric column. (And still 28 decimal
places more accurate than Oracle).

Best Regards, Simon Riggs
Index: src/include/utils/numeric.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/utils/numeric.h,v
retrieving revision 1.20
diff -c -c -r1.20 numeric.h
*** src/include/utils/numeric.h	1 Jan 2005 05:43:09 -0000	1.20
--- src/include/utils/numeric.h	2 Nov 2005 18:06:03 -0000
***************
*** 15,24 ****
  #define _PG_NUMERIC_H_
  
  /*
!  * Hardcoded precision limit - arbitrary, but must be small enough that
!  * dscale values will fit in 14 bits.
   */
! #define NUMERIC_MAX_PRECISION		1000
  
  /*
   * Internal limits on the scales chosen for calculation results
--- 15,24 ----
  #define _PG_NUMERIC_H_
  
  /*
!  * Hardcoded precision limit - must be small enough that
!  * dscale values will fit into the number of bits available in NumericData
   */
! #define NUMERIC_MAX_PRECISION		64
  
  /*
   * Internal limits on the scales chosen for calculation results
***************
*** 39,49 ****
  /*
   * Sign values and macros to deal with packing/unpacking n_sign_dscale
   */
! #define NUMERIC_SIGN_MASK	0xC000
! #define NUMERIC_POS			0x0000
! #define NUMERIC_NEG			0x4000
! #define NUMERIC_NAN			0xC000
! #define NUMERIC_DSCALE_MASK 0x3FFF
  #define NUMERIC_SIGN(n)		((n)->n_sign_dscale & NUMERIC_SIGN_MASK)
  #define NUMERIC_DSCALE(n)	((n)->n_sign_dscale & NUMERIC_DSCALE_MASK)
  #define NUMERIC_IS_NAN(n)	(NUMERIC_SIGN(n) != NUMERIC_POS &&	\
--- 39,49 ----
  /*
   * Sign values and macros to deal with packing/unpacking n_sign_dscale
   */
! #define NUMERIC_SIGN_MASK	0xC0
! #define NUMERIC_POS			0x00
! #define NUMERIC_NEG			0x40
! #define NUMERIC_NAN			0xC0
! #define NUMERIC_DSCALE_MASK 0x3F
  #define NUMERIC_SIGN(n)		((n)->n_sign_dscale & NUMERIC_SIGN_MASK)
  #define NUMERIC_DSCALE(n)	((n)->n_sign_dscale & NUMERIC_DSCALE_MASK)
  #define NUMERIC_IS_NAN(n)	(NUMERIC_SIGN(n) != NUMERIC_POS &&	\
***************
*** 61,74 ****
  typedef struct NumericData
  {
  	int32		varlen;			/* Variable size (std varlena header) */
! 	int16		n_weight;		/* Weight of 1st digit	*/
! 	uint16		n_sign_dscale;	/* Sign + display scale */
  	char		n_data[1];		/* Digits (really array of NumericDigit) */
  } NumericData;
  
  typedef NumericData *Numeric;
  
! #define NUMERIC_HDRSZ	(sizeof(int32) + sizeof(int16) + sizeof(uint16))
  
  
  /*
--- 61,74 ----
  typedef struct NumericData
  {
  	int32		varlen;			/* Variable size (std varlena header) */
! 	int8		n_weight;		/* Weight of 1st digit	*/
! 	uint8		n_sign_dscale;	/* Sign + display scale */
  	char		n_data[1];		/* Digits (really array of NumericDigit) */
  } NumericData;
  
  typedef NumericData *Numeric;
  
! #define NUMERIC_HDRSZ	(sizeof(int32) + sizeof(int8) + sizeof(uint8))
  
  
  /*
Index: src/test/regress/expected/numeric.out
===================================================================
RCS file: /projects/cvsroot/pgsql/src/test/regress/expected/numeric.out,v
retrieving revision 1.17
diff -c -c -r1.17 numeric.out
*** src/test/regress/expected/numeric.out	26 Jun 2005 03:04:18 -0000	1.17
--- src/test/regress/expected/numeric.out	2 Nov 2005 18:06:04 -0000
***************
*** 1,16 ****
  --
  -- NUMERIC
  --
! CREATE TABLE num_data (id int4, val numeric(210,10));
! CREATE TABLE num_exp_add (id1 int4, id2 int4, expected numeric(210,10));
! CREATE TABLE num_exp_sub (id1 int4, id2 int4, expected numeric(210,10));
! CREATE TABLE num_exp_div (id1 int4, id2 int4, expected numeric(210,10));
! CREATE TABLE num_exp_mul (id1 int4, id2 int4, expected numeric(210,10));
! CREATE TABLE num_exp_sqrt (id int4, expected numeric(210,10));
! CREATE TABLE num_exp_ln (id int4, expected numeric(210,10));
! CREATE TABLE num_exp_log10 (id int4, expected numeric(210,10));
! CREATE TABLE num_exp_power_10_ln (id int4, expected numeric(210,10));
! CREATE TABLE num_result (id1 int4, id2 int4, result numeric(210,10));
  -- ******************************
  -- * The following EXPECTED results are computed by bc(1)
  -- * with a scale of 200
--- 1,16 ----
  --
  -- NUMERIC
  --
! CREATE TABLE num_data (id int4, val numeric(64,10));
! CREATE TABLE num_exp_add (id1 int4, id2 int4, expected numeric(64,10));
! CREATE TABLE num_exp_sub (id1 int4, id2 int4, expected numeric(64,10));
! CREATE TABLE num_exp_div (id1 int4, id2 int4, expected numeric(64,10));
! CREATE TABLE num_exp_mul (id1 int4, id2 int4, expected numeric(64,10));
! CREATE TABLE num_exp_sqrt (id int4, expected numeric(64,10));
! CREATE TABLE num_exp_ln (id int4, expected numeric(64,10));
! CREATE TABLE num_exp_log10 (id int4, expected numeric(64,10));
! CREATE TABLE num_exp_power_10_ln (id int4, expected numeric(64,10));
! CREATE TABLE num_result (id1 int4, id2 int4, result numeric(64,10));
  -- ******************************
  -- * The following EXPECTED results are computed by bc(1)
  -- * with a scale of 200
***************
*** 590,602 ****
  (0 rows)
  
  DELETE FROM num_result;
! INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val / t2.val, 80)
      FROM num_data t1, num_data t2
      WHERE t2.val != '0.0';
! SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 80) as expected
      FROM num_result t1, num_exp_div t2
      WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2
!     AND t1.result != round(t2.expected, 80);
   id1 | id2 | result | expected 
  -----+-----+--------+----------
  (0 rows)
--- 590,602 ----
  (0 rows)
  
  DELETE FROM num_result;
! INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val / t2.val, 60)
      FROM num_data t1, num_data t2
      WHERE t2.val != '0.0';
! SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 60) as expected
      FROM num_result t1, num_exp_div t2
      WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2
!     AND t1.result != round(t2.expected, 60);
   id1 | id2 | result | expected 
  -----+-----+--------+----------
  (0 rows)
***************
*** 649,655 ****
  -- * POWER(10, LN(value)) check
  -- ******************************
  DELETE FROM num_result;
! INSERT INTO num_result SELECT id, 0, POWER(numeric '10', LN(ABS(round(val,200))))
      FROM num_data
      WHERE val != '0.0';
  SELECT t1.id1, t1.result, t2.expected
--- 649,655 ----
  -- * POWER(10, LN(value)) check
  -- ******************************
  DELETE FROM num_result;
! INSERT INTO num_result SELECT id, 0, POWER(numeric '10', LN(ABS(round(val,60))))
      FROM num_data
      WHERE val != '0.0';
  SELECT t1.id1, t1.result, t2.expected
Index: src/test/regress/sql/numeric.sql
===================================================================
RCS file: /projects/cvsroot/pgsql/src/test/regress/sql/numeric.sql,v
retrieving revision 1.12
diff -c -c -r1.12 numeric.sql
*** src/test/regress/sql/numeric.sql	26 Jun 2005 03:04:37 -0000	1.12
--- src/test/regress/sql/numeric.sql	2 Nov 2005 18:06:04 -0000
***************
*** 2,18 ****
  -- NUMERIC
  --
  
! CREATE TABLE num_data (id int4, val numeric(210,10));
! CREATE TABLE num_exp_add (id1 int4, id2 int4, expected numeric(210,10));
! CREATE TABLE num_exp_sub (id1 int4, id2 int4, expected numeric(210,10));
! CREATE TABLE num_exp_div (id1 int4, id2 int4, expected numeric(210,10));
! CREATE TABLE num_exp_mul (id1 int4, id2 int4, expected numeric(210,10));
! CREATE TABLE num_exp_sqrt (id int4, expected numeric(210,10));
! CREATE TABLE num_exp_ln (id int4, expected numeric(210,10));
! CREATE TABLE num_exp_log10 (id int4, expected numeric(210,10));
! CREATE TABLE num_exp_power_10_ln (id int4, expected numeric(210,10));
  
! CREATE TABLE num_result (id1 int4, id2 int4, result numeric(210,10));
  
  
  -- ******************************
--- 2,18 ----
  -- NUMERIC
  --
  
! CREATE TABLE num_data (id int4, val numeric(64,10));
! CREATE TABLE num_exp_add (id1 int4, id2 int4, expected numeric(64,10));
! CREATE TABLE num_exp_sub (id1 int4, id2 int4, expected numeric(64,10));
! CREATE TABLE num_exp_div (id1 int4, id2 int4, expected numeric(64,10));
! CREATE TABLE num_exp_mul (id1 int4, id2 int4, expected numeric(64,10));
! CREATE TABLE num_exp_sqrt (id int4, expected numeric(64,10));
! CREATE TABLE num_exp_ln (id int4, expected numeric(64,10));
! CREATE TABLE num_exp_log10 (id int4, expected numeric(64,10));
! CREATE TABLE num_exp_power_10_ln (id int4, expected numeric(64,10));
  
! CREATE TABLE num_result (id1 int4, id2 int4, result numeric(64,10));
  
  
  -- ******************************
***************
*** 579,591 ****
      AND t1.result != t2.expected;
  
  DELETE FROM num_result;
! INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val / t2.val, 80)
      FROM num_data t1, num_data t2
      WHERE t2.val != '0.0';
! SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 80) as expected
      FROM num_result t1, num_exp_div t2
      WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2
!     AND t1.result != round(t2.expected, 80);
  
  -- ******************************
  -- * Square root check
--- 579,591 ----
      AND t1.result != t2.expected;
  
  DELETE FROM num_result;
! INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val / t2.val, 60)
      FROM num_data t1, num_data t2
      WHERE t2.val != '0.0';
! SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 60) as expected
      FROM num_result t1, num_exp_div t2
      WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2
!     AND t1.result != round(t2.expected, 60);
  
  -- ******************************
  -- * Square root check
***************
*** 626,632 ****
  -- * POWER(10, LN(value)) check
  -- ******************************
  DELETE FROM num_result;
! INSERT INTO num_result SELECT id, 0, POWER(numeric '10', LN(ABS(round(val,200))))
      FROM num_data
      WHERE val != '0.0';
  SELECT t1.id1, t1.result, t2.expected
--- 626,632 ----
  -- * POWER(10, LN(value)) check
  -- ******************************
  DELETE FROM num_result;
! INSERT INTO num_result SELECT id, 0, POWER(numeric '10', LN(ABS(round(val,60))))
      FROM num_data
      WHERE val != '0.0';
  SELECT t1.id1, t1.result, t2.expected
---------------------------(end of broadcast)---------------------------
TIP 2: Don't 'kill -9' the postmaster

Reply via email to