access numeric data in module
I'm the maintainer of the PL/Haskell language extension. (
https://github.com/ed-o-saurus/PLHaskell/)
I want to be able to have a function received and/or return numeric data.
However, I'm having trouble getting data from Datums and building Datums to
return. numeric.h does not contain the macros to do this. They are in
numeric.c.
Is there a way to access the values in the numeric structures? If not,
would a PR to move macros to numeric.h be welcome in the next commitfest?
-Ed
Ed Behn <ed@behn.us> writes:
I want to be able to have a function received and/or return numeric data.
However, I'm having trouble getting data from Datums and building Datums to
return. numeric.h does not contain the macros to do this. They are in
numeric.c.
Is there a way to access the values in the numeric structures? If not,
would a PR to move macros to numeric.h be welcome in the next commitfest?
It's intentional that that stuff is not exposed, so no.
What actual functionality do you need that numeric.h doesn't expose?
regards, tom lane
On Mon, Sep 9, 2024 at 10:14 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Ed Behn <ed@behn.us> writes:
I want to be able to have a function received and/or return numeric data.
However, I'm having trouble getting data from Datums and building Datums to
return. numeric.h does not contain the macros to do this. They are in
numeric.c.Is there a way to access the values in the numeric structures? If not,
would a PR to move macros to numeric.h be welcome in the next commitfest?It's intentional that that stuff is not exposed, so no.
What actual functionality do you need that numeric.h doesn't expose?
I don't agree with this reponse at all. It seems entirely reasonable
for third-party code to want to have a way to construct and interpret
numeric datums. Keeping the details private would MAYBE make sense if
the internal details were changing release to release, but that's
clearly not the case. Even if it were, an extension author is
completely entitled to say "hey, I'd rather have access to an unstable
API and update my code for new releases" and we should accommodate
that. If we don't, people don't give up on writing the code that they
want to write -- they just cut-and-paste private declarations/code
into their own source tree, which is WAY worse than if we just put the
stuff in a .h file.
--
Robert Haas
EDB: http://www.enterprisedb.com
Robert Haas <robertmhaas@gmail.com> writes:
On Mon, Sep 9, 2024 at 10:14 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
It's intentional that that stuff is not exposed, so no.
What actual functionality do you need that numeric.h doesn't expose?
I don't agree with this reponse at all. It seems entirely reasonable
for third-party code to want to have a way to construct and interpret
numeric datums. Keeping the details private would MAYBE make sense if
the internal details were changing release to release, but that's
clearly not the case.
We have changed numeric's internal representation in the past, and
I'd like to keep the freedom to do so again. There's been discussion
for example of reconsidering the choice of NBASE to make more sense
on 64-bit hardware. Yeah, maintaining on-disk compatibility limits
what we can do there, but not as much as if some external module
is in bed with the representation.
Even if it were, an extension author is
completely entitled to say "hey, I'd rather have access to an unstable
API and update my code for new releases" and we should accommodate
that. If we don't, people don't give up on writing the code that they
want to write -- they just cut-and-paste private declarations/code
into their own source tree, which is WAY worse than if we just put the
stuff in a .h file.
IMO it'd be a lot better if numeric.c exposed whatever functionality
Ed feels is missing, while keeping the contents of a numeric opaque.
regards, tom lane
On Mon, Sep 9, 2024 at 1:25 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
We have changed numeric's internal representation in the past, and
I'd like to keep the freedom to do so again. There's been discussion
for example of reconsidering the choice of NBASE to make more sense
on 64-bit hardware. Yeah, maintaining on-disk compatibility limits
what we can do there, but not as much as if some external module
is in bed with the representation.
I disagree with the idea that a contrib module looking at the details
of a Numeric value means we can't make these kinds of updates.
Even if it were, an extension author is
completely entitled to say "hey, I'd rather have access to an unstable
API and update my code for new releases" and we should accommodate
that. If we don't, people don't give up on writing the code that they
want to write -- they just cut-and-paste private declarations/code
into their own source tree, which is WAY worse than if we just put the
stuff in a .h file.IMO it'd be a lot better if numeric.c exposed whatever functionality
Ed feels is missing, while keeping the contents of a numeric opaque.
We could certainly expose a bunch of functions, but I think that would
actually be a bigger maintenance burden for us than just exposing some
of the details that are currently private to numeric.c. It would also
presumably be less performant, since it means somebody has to call a
function rather than just using a macro.
Also, this seems to me to be holding the numeric data type to a
different standard than other things. For numeric, we have
NumericData, NumericChoice, NumericShort, and NumericLong as structs
that define the on-disk representation. They're in numeric.c. But
ArrayType is in array.h. RangeType is in rangetypes.h. MultiRangeType
is in multirangetypes.h. PATH and POLYGON are in geo_decls.h. inet and
inet_data are in inet.h. int2vector and oidvector are in c.h (which
seems like questionable placement, but I digress). And there must be
tons of third-party code out there that knows how to interpret a text
or bytea varlena. So it's not like we have some principled
project-wide policy of hiding these implementation details. At first
look, numeric seems like an outlier.
--
Robert Haas
EDB: http://www.enterprisedb.com
Robert Haas <robertmhaas@gmail.com> writes:
On Mon, Sep 9, 2024 at 1:25 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
IMO it'd be a lot better if numeric.c exposed whatever functionality
Ed feels is missing, while keeping the contents of a numeric opaque.
We could certainly expose a bunch of functions, but I think that would
actually be a bigger maintenance burden for us than just exposing some
of the details that are currently private to numeric.c.
This whole argument is contingent on details that haven't been
provided, namely exactly what it is that Ed wants to do that he can't
do today. I think we should investigate that before deciding that
publishing previously-private detail is the best solution.
Also, this seems to me to be holding the numeric data type to a
different standard than other things.
By that argument, we should move every declaration in every .c file
into c.h and be done. I'd personally be happier if we had *not*
exposed the other data structure details you mention, but that
ship has sailed.
If we do do what you're advocating, I'd at least insist that the
declarations go into a new file numeric_internal.h, so that it's
clear to all concerned that they're playing with fire if they
depend on that stuff.
regards, tom lane
On 09/09/24 13:00, Robert Haas wrote:
I don't agree with this reponse at all. It seems entirely reasonable
for third-party code to want to have a way to construct and interpret
numeric datums. Keeping the details private would MAYBE make sense if
the internal details were changing release to release, but that's
clearly not the case. Even if it were, an extension author is
completely entitled to say "hey, I'd rather have access to an unstable
API and update my code for new releases" and we should accommodate
that. If we don't, people don't give up on writing the code that they
want to write -- they just cut-and-paste private declarations/code
into their own source tree, which is WAY worse than if we just put the
stuff in a .h file.
Amen.
The above API documentation was written when the PostgreSQL source
comments read "values of NBASE other than 10000 are considered of historical
interest only and are no longer supported in any sense".
I will have to generalize it a bit more if other NBASEs are now
to be considered again.
If Tom prefers the idea of keeping the datum layout strongly encapsulated
(pretty much uniquely among PG data types) and providing only a callable
C API for manipulating it, then I might propose something like the above-
linked Java API as one source of API ideas.
I think it's worth remembering that most PLs will have their own
libraries (sometimes multiple alternatives) for arbitrary-precision numbers,
and it's hard to generalize about /those/ libraries regarding what API
they will provide for most efficiently and faithfully converting a
foreign representation to or from their own. Conversion through a decimal
string (a) may not be most efficient, and (b) may not faithfully roundtrip
possible combinations of digits, displayScale, and weight.
From Java's perspective, there has historically been a significant JNI
overhead for calling from Java into a C API, so that it's advantageous
to know the memory layout and keep the processing in Java. There is
at last a batteries-included Java foreign-function interface that can
make it less costly to call into a C API, but that has only landed in
Java 22, which not everyone will be using right away.
Regards,
-Chap
On Mon, Sep 9, 2024 at 2:02 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
By that argument, we should move every declaration in every .c file
into c.h and be done. I'd personally be happier if we had *not*
exposed the other data structure details you mention, but that
ship has sailed.
Not every declaration in every .c file is of general interest, but the
ones that are probably should be moved into .h files. The on-disk
representation of a commonly-used data type certainly qualifies.
You can see from Chapman's reply that I'm not making this up: when we
don't expose things, it doesn't keep people from depending on them, it
just makes them copy our code into their own repository. That's not a
win. It makes those extensions more fragile, not less, and it makes
the PostgreSQL extension ecosystem worse. pg_hint_plan is another,
recently-discussed example of this phenomenon: refuse to give people
the keys, and they start hot-wiring stuff.
If we do do what you're advocating, I'd at least insist that the
declarations go into a new file numeric_internal.h, so that it's
clear to all concerned that they're playing with fire if they
depend on that stuff.
I think that's a bit pointless considering that we don't do it in any
of the other cases. I'd rather be consistent with our usual practice.
But if it ends up in a separate header file that's still better than
the status quo.
--
Robert Haas
EDB: http://www.enterprisedb.com
Sorry for taking so long to respond. I was at my day-job.
As I mentioned, I am the maintainer of the PL/Haskell language extension.
This extension allows users to write code in the Haskell language. In order
to use numeric types, I will need to create a Haskell type equivalent.
Something like
data Numeric = PosInfinity | NegInfinity | NaN | Number Integer Int16
where the Number constructor represents a numeric's mantissa and weight.
In order to get or return data, I would need to be able to access those
fields of the numeric type.
I'm not proposing giving access to the actual numeric structure. Rather,
the data should be accessed by function call or macro. This would allow
future changes to the inner workings without breaking compatibility as long
as the interface is maintained. It looks to me like all of the code to
access data exists, it should simply be made accessible. An additional
function should exist that allows an extension to create a numeric
structure by passing the needed data.
-Ed
On Mon, Sep 9, 2024 at 2:45 PM Robert Haas <robertmhaas@gmail.com> wrote:
Show quoted text
On Mon, Sep 9, 2024 at 2:02 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
By that argument, we should move every declaration in every .c file
into c.h and be done. I'd personally be happier if we had *not*
exposed the other data structure details you mention, but that
ship has sailed.Not every declaration in every .c file is of general interest, but the
ones that are probably should be moved into .h files. The on-disk
representation of a commonly-used data type certainly qualifies.You can see from Chapman's reply that I'm not making this up: when we
don't expose things, it doesn't keep people from depending on them, it
just makes them copy our code into their own repository. That's not a
win. It makes those extensions more fragile, not less, and it makes
the PostgreSQL extension ecosystem worse. pg_hint_plan is another,
recently-discussed example of this phenomenon: refuse to give people
the keys, and they start hot-wiring stuff.If we do do what you're advocating, I'd at least insist that the
declarations go into a new file numeric_internal.h, so that it's
clear to all concerned that they're playing with fire if they
depend on that stuff.I think that's a bit pointless considering that we don't do it in any
of the other cases. I'd rather be consistent with our usual practice.
But if it ends up in a separate header file that's still better than
the status quo.--
Robert Haas
EDB: http://www.enterprisedb.com
Good afternoon-
Was there a resolution of this? I'm wondering if it is worth it for me
to submit a PR for the next commitfest.
-Ed
On Mon, Sep 9, 2024 at 8:40 PM Ed Behn <ed@behn.us> wrote:
Show quoted text
Sorry for taking so long to respond. I was at my day-job.
As I mentioned, I am the maintainer of the PL/Haskell language extension.
This extension allows users to write code in the Haskell language. In order
to use numeric types, I will need to create a Haskell type equivalent.
Something likedata Numeric = PosInfinity | NegInfinity | NaN | Number Integer Int16
where the Number constructor represents a numeric's mantissa and weight.
In order to get or return data, I would need to be able to access those
fields of the numeric type.I'm not proposing giving access to the actual numeric structure. Rather,
the data should be accessed by function call or macro. This would allow
future changes to the inner workings without breaking compatibility as long
as the interface is maintained. It looks to me like all of the code to
access data exists, it should simply be made accessible. An additional
function should exist that allows an extension to create a numeric
structure by passing the needed data.-Ed
On Mon, Sep 9, 2024 at 2:45 PM Robert Haas <robertmhaas@gmail.com> wrote:
On Mon, Sep 9, 2024 at 2:02 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
By that argument, we should move every declaration in every .c file
into c.h and be done. I'd personally be happier if we had *not*
exposed the other data structure details you mention, but that
ship has sailed.Not every declaration in every .c file is of general interest, but the
ones that are probably should be moved into .h files. The on-disk
representation of a commonly-used data type certainly qualifies.You can see from Chapman's reply that I'm not making this up: when we
don't expose things, it doesn't keep people from depending on them, it
just makes them copy our code into their own repository. That's not a
win. It makes those extensions more fragile, not less, and it makes
the PostgreSQL extension ecosystem worse. pg_hint_plan is another,
recently-discussed example of this phenomenon: refuse to give people
the keys, and they start hot-wiring stuff.If we do do what you're advocating, I'd at least insist that the
declarations go into a new file numeric_internal.h, so that it's
clear to all concerned that they're playing with fire if they
depend on that stuff.I think that's a bit pointless considering that we don't do it in any
of the other cases. I'd rather be consistent with our usual practice.
But if it ends up in a separate header file that's still better than
the status quo.--
Robert Haas
EDB: http://www.enterprisedb.com
On Sat, Sep 14, 2024 at 2:10 PM Ed Behn <ed@behn.us> wrote:
Was there a resolution of this? I'm wondering if it is worth it for me to submit a PR for the next commitfest.
Well, it seems like what you want is different than what I want, and
what Tom wants is different from both of us. I'd like there to be a
way forward here but at the moment I'm not quite sure what it is.
--
Robert Haas
EDB: http://www.enterprisedb.com
I've created a patch (attached) to implement the changes discussed below.
This change moves the definition of the NumericVar structure to numeric.h.
Function definitions for functions used to work with NumericVar are also
moved to the header as are definitions of functions used to convert
NumericVar to Numeric. (Numeric is used to store numeric and decimal types.)
All of this is so that third-party libraries can access numeric and decimal
values without having to access the opaque Numeric structure.
There is actually no new code. Code is simply moved from numeric.c to
numeric.h.
This is a patch against branch master.
This successfully compiles and is tested with regression tests.
Also attached is a code sample that uses the change.
Please provide feedback. I'm planning to submit this for the March
commitfest.
-Ed
On Wed, Sep 18, 2024 at 9:50 AM Robert Haas <robertmhaas@gmail.com> wrote:
Show quoted text
On Sat, Sep 14, 2024 at 2:10 PM Ed Behn <ed@behn.us> wrote:
Was there a resolution of this? I'm wondering if it is worth it for
me to submit a PR for the next commitfest.
Well, it seems like what you want is different than what I want, and
what Tom wants is different from both of us. I'd like there to be a
way forward here but at the moment I'm not quite sure what it is.--
Robert Haas
EDB: http://www.enterprisedb.com
Attachments:
numericvar_access.patchtext/x-patch; charset=US-ASCII; name=numericvar_access.patchDownload
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 40dcbc7b67..126b7dc452 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -50,60 +50,6 @@
#define NUMERIC_DEBUG
*/
-
-/* ----------
- * Local data types
- *
- * Numeric values are represented in a base-NBASE floating point format.
- * Each "digit" ranges from 0 to NBASE-1. The type NumericDigit is signed
- * and wide enough to store a digit. We assume that NBASE*NBASE can fit in
- * an int. Although the purely calculational routines could handle any even
- * NBASE that's less than sqrt(INT_MAX), in practice we are only interested
- * in NBASE a power of ten, so that I/O conversions and decimal rounding
- * are easy. Also, it's actually more efficient if NBASE is rather less than
- * sqrt(INT_MAX), so that there is "headroom" for mul_var and div_var to
- * postpone processing carries.
- *
- * Values of NBASE other than 10000 are considered of historical interest only
- * and are no longer supported in any sense; no mechanism exists for the client
- * to discover the base, so every client supporting binary mode expects the
- * base-10000 format. If you plan to change this, also note the numeric
- * abbreviation code, which assumes NBASE=10000.
- * ----------
- */
-
-#if 0
-#define NBASE 10
-#define HALF_NBASE 5
-#define DEC_DIGITS 1 /* decimal digits per NBASE digit */
-#define MUL_GUARD_DIGITS 4 /* these are measured in NBASE digits */
-#define DIV_GUARD_DIGITS 8
-
-typedef signed char NumericDigit;
-#endif
-
-#if 0
-#define NBASE 100
-#define HALF_NBASE 50
-#define DEC_DIGITS 2 /* decimal digits per NBASE digit */
-#define MUL_GUARD_DIGITS 3 /* these are measured in NBASE digits */
-#define DIV_GUARD_DIGITS 6
-
-typedef signed char NumericDigit;
-#endif
-
-#if 1
-#define NBASE 10000
-#define HALF_NBASE 5000
-#define DEC_DIGITS 4 /* decimal digits per NBASE digit */
-#define MUL_GUARD_DIGITS 2 /* these are measured in NBASE digits */
-#define DIV_GUARD_DIGITS 4
-
-typedef int16 NumericDigit;
-#endif
-
-#define NBASE_SQR (NBASE * NBASE)
-
/*
* The Numeric type as stored on disk.
*
@@ -252,75 +198,6 @@ struct NumericData
| ((n)->choice.n_short.n_header & NUMERIC_SHORT_WEIGHT_MASK)) \
: ((n)->choice.n_long.n_weight))
-/*
- * Maximum weight of a stored Numeric value (based on the use of int16 for the
- * weight in NumericLong). Note that intermediate values held in NumericVar
- * and NumericSumAccum variables may have much larger weights.
- */
-#define NUMERIC_WEIGHT_MAX PG_INT16_MAX
-
-/* ----------
- * NumericVar is the format we use for arithmetic. The digit-array part
- * is the same as the NumericData storage format, but the header is more
- * complex.
- *
- * The value represented by a NumericVar is determined by the sign, weight,
- * ndigits, and digits[] array. If it is a "special" value (NaN or Inf)
- * then only the sign field matters; ndigits should be zero, and the weight
- * and dscale fields are ignored.
- *
- * Note: the first digit of a NumericVar's value is assumed to be multiplied
- * by NBASE ** weight. Another way to say it is that there are weight+1
- * digits before the decimal point. It is possible to have weight < 0.
- *
- * buf points at the physical start of the palloc'd digit buffer for the
- * NumericVar. digits points at the first digit in actual use (the one
- * with the specified weight). We normally leave an unused digit or two
- * (preset to zeroes) between buf and digits, so that there is room to store
- * a carry out of the top digit without reallocating space. We just need to
- * decrement digits (and increment weight) to make room for the carry digit.
- * (There is no such extra space in a numeric value stored in the database,
- * only in a NumericVar in memory.)
- *
- * If buf is NULL then the digit buffer isn't actually palloc'd and should
- * not be freed --- see the constants below for an example.
- *
- * dscale, or display scale, is the nominal precision expressed as number
- * of digits after the decimal point (it must always be >= 0 at present).
- * dscale may be more than the number of physically stored fractional digits,
- * implying that we have suppressed storage of significant trailing zeroes.
- * It should never be less than the number of stored digits, since that would
- * imply hiding digits that are present. NOTE that dscale is always expressed
- * in *decimal* digits, and so it may correspond to a fractional number of
- * base-NBASE digits --- divide by DEC_DIGITS to convert to NBASE digits.
- *
- * rscale, or result scale, is the target precision for a computation.
- * Like dscale it is expressed as number of *decimal* digits after the decimal
- * point, and is always >= 0 at present.
- * Note that rscale is not stored in variables --- it's figured on-the-fly
- * from the dscales of the inputs.
- *
- * While we consistently use "weight" to refer to the base-NBASE weight of
- * a numeric value, it is convenient in some scale-related calculations to
- * make use of the base-10 weight (ie, the approximate log10 of the value).
- * To avoid confusion, such a decimal-units weight is called a "dweight".
- *
- * NB: All the variable-level functions are written in a style that makes it
- * possible to give one and the same variable as argument and destination.
- * This is feasible because the digit buffer is separate from the variable.
- * ----------
- */
-typedef struct NumericVar
-{
- int ndigits; /* # of digits in digits[] - can be 0! */
- int weight; /* weight of first digit */
- int sign; /* NUMERIC_POS, _NEG, _NAN, _PINF, or _NINF */
- int dscale; /* display scale */
- NumericDigit *buf; /* start of palloc'd space for digits[] */
- NumericDigit *digits; /* base-NBASE digits */
-} NumericVar;
-
-
/* ----------
* Data for generate_series
* ----------
@@ -491,8 +368,6 @@ static void dump_var(const char *str, NumericVar *var);
pfree(buf); \
} while (0)
-#define init_var(v) memset(v, 0, sizeof(NumericVar))
-
#define NUMERIC_DIGITS(num) (NUMERIC_HEADER_IS_SHORT(num) ? \
(num)->choice.n_short.n_data : (num)->choice.n_long.n_data)
#define NUMERIC_NDIGITS(num) \
@@ -502,10 +377,6 @@ static void dump_var(const char *str, NumericVar *var);
(weight) <= NUMERIC_SHORT_WEIGHT_MAX && \
(weight) >= NUMERIC_SHORT_WEIGHT_MIN)
-static void alloc_var(NumericVar *var, int ndigits);
-static void free_var(NumericVar *var);
-static void zero_var(NumericVar *var);
-
static bool set_var_from_str(const char *str, const char *cp,
NumericVar *dest, const char **endptr,
Node *escontext);
@@ -514,9 +385,6 @@ static bool set_var_from_non_decimal_integer_str(const char *str,
int base, NumericVar *dest,
const char **endptr,
Node *escontext);
-static void set_var_from_num(Numeric num, NumericVar *dest);
-static void init_var_from_num(Numeric num, NumericVar *dest);
-static void set_var_from_var(const NumericVar *value, NumericVar *dest);
static char *get_str_from_var(const NumericVar *var);
static char *get_str_from_var_sci(const NumericVar *var, int rscale);
@@ -524,8 +392,6 @@ static void numericvar_serialize(StringInfo buf, const NumericVar *var);
static void numericvar_deserialize(StringInfo buf, NumericVar *var);
static Numeric duplicate_numeric(Numeric num);
-static Numeric make_result(const NumericVar *var);
-static Numeric make_result_opt_error(const NumericVar *var, bool *have_error);
static bool apply_typmod(NumericVar *var, int32 typmod, Node *escontext);
static bool apply_typmod_special(Numeric num, int32 typmod, Node *escontext);
@@ -7067,7 +6933,7 @@ dump_var(const char *str, NumericVar *var)
*
* Allocate a digit buffer of ndigits digits (plus a spare digit for rounding)
*/
-static void
+void
alloc_var(NumericVar *var, int ndigits)
{
digitbuf_free(var->buf);
@@ -7083,7 +6949,7 @@ alloc_var(NumericVar *var, int ndigits)
*
* Return the digit buffer of a variable to the free pool
*/
-static void
+void
free_var(NumericVar *var)
{
digitbuf_free(var->buf);
@@ -7099,7 +6965,7 @@ free_var(NumericVar *var)
* Set a variable to ZERO.
* Note: its dscale is not touched.
*/
-static void
+void
zero_var(NumericVar *var)
{
digitbuf_free(var->buf);
@@ -7534,7 +7400,7 @@ invalid_syntax:
*
* Convert the packed db format into a variable
*/
-static void
+void
set_var_from_num(Numeric num, NumericVar *dest)
{
int ndigits;
@@ -7565,7 +7431,7 @@ set_var_from_num(Numeric num, NumericVar *dest)
* propagate to the original Numeric! It's OK to use it as the destination
* argument of one of the calculational functions, though.
*/
-static void
+void
init_var_from_num(Numeric num, NumericVar *dest)
{
dest->ndigits = NUMERIC_NDIGITS(num);
@@ -7582,7 +7448,7 @@ init_var_from_num(Numeric num, NumericVar *dest)
*
* Copy one variable into another
*/
-static void
+void
set_var_from_var(const NumericVar *value, NumericVar *dest)
{
NumericDigit *newbuf;
@@ -7896,7 +7762,7 @@ duplicate_numeric(Numeric num)
* If "have_error" isn't NULL, on overflow *have_error is set to true and
* NULL is returned. This is helpful when caller needs to handle errors.
*/
-static Numeric
+Numeric
make_result_opt_error(const NumericVar *var, bool *have_error)
{
Numeric result;
@@ -8005,7 +7871,7 @@ make_result_opt_error(const NumericVar *var, bool *have_error)
*
* An interface to make_result_opt_error() without "have_error" argument.
*/
-static Numeric
+Numeric
make_result(const NumericVar *var)
{
return make_result_opt_error(var, NULL);
diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h
index 9e79fc376c..32e77d2d13 100644
--- a/src/include/utils/numeric.h
+++ b/src/include/utils/numeric.h
@@ -42,6 +42,59 @@
#define NUMERIC_MAX_RESULT_SCALE (NUMERIC_MAX_PRECISION * 2)
+/* ----------
+ * Local data types
+ *
+ * Numeric values are represented in a base-NBASE floating point format.
+ * Each "digit" ranges from 0 to NBASE-1. The type NumericDigit is signed
+ * and wide enough to store a digit. We assume that NBASE*NBASE can fit in
+ * an int. Although the purely calculational routines could handle any even
+ * NBASE that's less than sqrt(INT_MAX), in practice we are only interested
+ * in NBASE a power of ten, so that I/O conversions and decimal rounding
+ * are easy. Also, it's actually more efficient if NBASE is rather less than
+ * sqrt(INT_MAX), so that there is "headroom" for mul_var and div_var to
+ * postpone processing carries.
+ *
+ * Values of NBASE other than 10000 are considered of historical interest only
+ * and are no longer supported in any sense; no mechanism exists for the client
+ * to discover the base, so every client supporting binary mode expects the
+ * base-10000 format. If you plan to change this, also note the numeric
+ * abbreviation code, which assumes NBASE=10000.
+ * ----------
+ */
+
+#if 0
+#define NBASE 10
+#define HALF_NBASE 5
+#define DEC_DIGITS 1 /* decimal digits per NBASE digit */
+#define MUL_GUARD_DIGITS 4 /* these are measured in NBASE digits */
+#define DIV_GUARD_DIGITS 8
+
+typedef signed char NumericDigit;
+#endif
+
+#if 0
+#define NBASE 100
+#define HALF_NBASE 50
+#define DEC_DIGITS 2 /* decimal digits per NBASE digit */
+#define MUL_GUARD_DIGITS 3 /* these are measured in NBASE digits */
+#define DIV_GUARD_DIGITS 6
+
+typedef signed char NumericDigit;
+#endif
+
+#if 1
+#define NBASE 10000
+#define HALF_NBASE 5000
+#define DEC_DIGITS 4 /* decimal digits per NBASE digit */
+#define MUL_GUARD_DIGITS 2 /* these are measured in NBASE digits */
+#define DIV_GUARD_DIGITS 4
+
+typedef int16 NumericDigit;
+#endif
+
+#define NBASE_SQR (NBASE * NBASE)
+
/*
* For inherently inexact calculations such as division and square root,
* we try to get at least this many significant digits; the idea is to
@@ -49,8 +102,85 @@
*/
#define NUMERIC_MIN_SIG_DIGITS 16
+/*
+ * sign field of NumericVar
+ */
+
+#define NUMERIC_POS 0x0000
+#define NUMERIC_NEG 0x4000
+#define NUMERIC_NAN 0xC000
+#define NUMERIC_PINF 0xD000
+#define NUMERIC_NINF 0xF000
+
+/*
+ * Maximum weight of a stored Numeric value (based on the use of int16 for the
+ * weight in NumericLong). Note that intermediate values held in NumericVar
+ * and NumericSumAccum variables may have much larger weights.
+ */
+ #define NUMERIC_WEIGHT_MAX PG_INT16_MAX
+
+/* ----------
+ * NumericVar is the format we use for arithmetic. The digit-array part
+ * is the same as the NumericData storage format, but the header is more
+ * complex.
+ *
+ * The value represented by a NumericVar is determined by the sign, weight,
+ * ndigits, and digits[] array. If it is a "special" value (NaN or Inf)
+ * then only the sign field matters; ndigits should be zero, and the weight
+ * and dscale fields are ignored.
+ *
+ * Note: the first digit of a NumericVar's value is assumed to be multiplied
+ * by NBASE ** weight. Another way to say it is that there are weight+1
+ * digits before the decimal point. It is possible to have weight < 0.
+ *
+ * buf points at the physical start of the palloc'd digit buffer for the
+ * NumericVar. digits points at the first digit in actual use (the one
+ * with the specified weight). We normally leave an unused digit or two
+ * (preset to zeroes) between buf and digits, so that there is room to store
+ * a carry out of the top digit without reallocating space. We just need to
+ * decrement digits (and increment weight) to make room for the carry digit.
+ * (There is no such extra space in a numeric value stored in the database,
+ * only in a NumericVar in memory.)
+ *
+ * If buf is NULL then the digit buffer isn't actually palloc'd and should
+ * not be freed --- see the constants below for an example.
+ *
+ * dscale, or display scale, is the nominal precision expressed as number
+ * of digits after the decimal point (it must always be >= 0 at present).
+ * dscale may be more than the number of physically stored fractional digits,
+ * implying that we have suppressed storage of significant trailing zeroes.
+ * It should never be less than the number of stored digits, since that would
+ * imply hiding digits that are present. NOTE that dscale is always expressed
+ * in *decimal* digits, and so it may correspond to a fractional number of
+ * base-NBASE digits --- divide by DEC_DIGITS to convert to NBASE digits.
+ *
+ * rscale, or result scale, is the target precision for a computation.
+ * Like dscale it is expressed as number of *decimal* digits after the decimal
+ * point, and is always >= 0 at present.
+ * Note that rscale is not stored in variables --- it's figured on-the-fly
+ * from the dscales of the inputs.
+ *
+ * While we consistently use "weight" to refer to the base-NBASE weight of
+ * a numeric value, it is convenient in some scale-related calculations to
+ * make use of the base-10 weight (ie, the approximate log10 of the value).
+ * To avoid confusion, such a decimal-units weight is called a "dweight".
+ *
+ * NB: All the variable-level functions are written in a style that makes it
+ * possible to give one and the same variable as argument and destination.
+ * This is feasible because the digit buffer is separate from the variable.
+ * ----------
+ */
+typedef struct NumericVar
+{
+ int ndigits; /* # of digits in digits[] - can be 0! */
+ int weight; /* weight of first digit */
+ int sign; /* NUMERIC_POS, _NEG, _NAN, _PINF, or _NINF */
+ int dscale; /* display scale */
+ NumericDigit *buf; /* start of palloc'd space for digits[] */
+ NumericDigit *digits; /* base-NBASE digits */
+} NumericVar;
+
/* The actual contents of Numeric are private to numeric.c */
-struct NumericData;
typedef struct NumericData *Numeric;
/*
@@ -79,9 +209,23 @@ NumericGetDatum(Numeric X)
#define PG_GETARG_NUMERIC_COPY(n) DatumGetNumericCopy(PG_GETARG_DATUM(n))
#define PG_RETURN_NUMERIC(x) return NumericGetDatum(x)
+#define init_var(v) memset(v, 0, sizeof(NumericVar))
+
/*
* Utility functions in numeric.c
*/
+
+extern void alloc_var(NumericVar *var, int ndigits);
+extern void free_var(NumericVar *var);
+extern void zero_var(NumericVar *var);
+
+extern void set_var_from_num(Numeric num, NumericVar *dest);
+extern void init_var_from_num(Numeric num, NumericVar *dest);
+extern void set_var_from_var(const NumericVar *value, NumericVar *dest);
+
+extern Numeric make_result(const NumericVar *var);
+extern Numeric make_result_opt_error(const NumericVar *var, bool *have_error);
+
extern bool numeric_is_nan(Numeric num);
extern bool numeric_is_inf(Numeric num);
extern int32 numeric_maximum_size(int32 typmod);
Upon further review, I've updated the patch. This avoids possible name
conflicts with other functions. See attached.
On Sat, Feb 8, 2025 at 3:24 PM Ed Behn <ed@behn.us> wrote:
Show quoted text
I've created a patch (attached) to implement the changes discussed below.
This change moves the definition of the NumericVar structure to numeric.h.
Function definitions for functions used to work with NumericVar are also
moved to the header as are definitions of functions used to convert
NumericVar to Numeric. (Numeric is used to store numeric and decimal types.)All of this is so that third-party libraries can access numeric and
decimal values without having to access the opaque Numeric structure.There is actually no new code. Code is simply moved from numeric.c to
numeric.h.This is a patch against branch master.
This successfully compiles and is tested with regression tests.
Also attached is a code sample that uses the change.
Please provide feedback. I'm planning to submit this for the March
commitfest.-Ed
On Wed, Sep 18, 2024 at 9:50 AM Robert Haas <robertmhaas@gmail.com> wrote:
On Sat, Sep 14, 2024 at 2:10 PM Ed Behn <ed@behn.us> wrote:
Was there a resolution of this? I'm wondering if it is worth it for
me to submit a PR for the next commitfest.
Well, it seems like what you want is different than what I want, and
what Tom wants is different from both of us. I'd like there to be a
way forward here but at the moment I'm not quite sure what it is.--
Robert Haas
EDB: http://www.enterprisedb.com
Attachments:
numericvar_access.patchtext/x-patch; charset=US-ASCII; name=numericvar_access.patchDownload
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 40dcbc7b671..ec5e4b5ee32 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -45,65 +45,11 @@
/* ----------
* Uncomment the following to enable compilation of dump_numeric()
- * and dump_var() and to get a dump of any result produced by make_result().
+ * and dump_var() and to get a dump of any result produced by numeric_make_result().
* ----------
#define NUMERIC_DEBUG
*/
-
-/* ----------
- * Local data types
- *
- * Numeric values are represented in a base-NBASE floating point format.
- * Each "digit" ranges from 0 to NBASE-1. The type NumericDigit is signed
- * and wide enough to store a digit. We assume that NBASE*NBASE can fit in
- * an int. Although the purely calculational routines could handle any even
- * NBASE that's less than sqrt(INT_MAX), in practice we are only interested
- * in NBASE a power of ten, so that I/O conversions and decimal rounding
- * are easy. Also, it's actually more efficient if NBASE is rather less than
- * sqrt(INT_MAX), so that there is "headroom" for mul_var and div_var to
- * postpone processing carries.
- *
- * Values of NBASE other than 10000 are considered of historical interest only
- * and are no longer supported in any sense; no mechanism exists for the client
- * to discover the base, so every client supporting binary mode expects the
- * base-10000 format. If you plan to change this, also note the numeric
- * abbreviation code, which assumes NBASE=10000.
- * ----------
- */
-
-#if 0
-#define NBASE 10
-#define HALF_NBASE 5
-#define DEC_DIGITS 1 /* decimal digits per NBASE digit */
-#define MUL_GUARD_DIGITS 4 /* these are measured in NBASE digits */
-#define DIV_GUARD_DIGITS 8
-
-typedef signed char NumericDigit;
-#endif
-
-#if 0
-#define NBASE 100
-#define HALF_NBASE 50
-#define DEC_DIGITS 2 /* decimal digits per NBASE digit */
-#define MUL_GUARD_DIGITS 3 /* these are measured in NBASE digits */
-#define DIV_GUARD_DIGITS 6
-
-typedef signed char NumericDigit;
-#endif
-
-#if 1
-#define NBASE 10000
-#define HALF_NBASE 5000
-#define DEC_DIGITS 4 /* decimal digits per NBASE digit */
-#define MUL_GUARD_DIGITS 2 /* these are measured in NBASE digits */
-#define DIV_GUARD_DIGITS 4
-
-typedef int16 NumericDigit;
-#endif
-
-#define NBASE_SQR (NBASE * NBASE)
-
/*
* The Numeric type as stored on disk.
*
@@ -252,75 +198,6 @@ struct NumericData
| ((n)->choice.n_short.n_header & NUMERIC_SHORT_WEIGHT_MASK)) \
: ((n)->choice.n_long.n_weight))
-/*
- * Maximum weight of a stored Numeric value (based on the use of int16 for the
- * weight in NumericLong). Note that intermediate values held in NumericVar
- * and NumericSumAccum variables may have much larger weights.
- */
-#define NUMERIC_WEIGHT_MAX PG_INT16_MAX
-
-/* ----------
- * NumericVar is the format we use for arithmetic. The digit-array part
- * is the same as the NumericData storage format, but the header is more
- * complex.
- *
- * The value represented by a NumericVar is determined by the sign, weight,
- * ndigits, and digits[] array. If it is a "special" value (NaN or Inf)
- * then only the sign field matters; ndigits should be zero, and the weight
- * and dscale fields are ignored.
- *
- * Note: the first digit of a NumericVar's value is assumed to be multiplied
- * by NBASE ** weight. Another way to say it is that there are weight+1
- * digits before the decimal point. It is possible to have weight < 0.
- *
- * buf points at the physical start of the palloc'd digit buffer for the
- * NumericVar. digits points at the first digit in actual use (the one
- * with the specified weight). We normally leave an unused digit or two
- * (preset to zeroes) between buf and digits, so that there is room to store
- * a carry out of the top digit without reallocating space. We just need to
- * decrement digits (and increment weight) to make room for the carry digit.
- * (There is no such extra space in a numeric value stored in the database,
- * only in a NumericVar in memory.)
- *
- * If buf is NULL then the digit buffer isn't actually palloc'd and should
- * not be freed --- see the constants below for an example.
- *
- * dscale, or display scale, is the nominal precision expressed as number
- * of digits after the decimal point (it must always be >= 0 at present).
- * dscale may be more than the number of physically stored fractional digits,
- * implying that we have suppressed storage of significant trailing zeroes.
- * It should never be less than the number of stored digits, since that would
- * imply hiding digits that are present. NOTE that dscale is always expressed
- * in *decimal* digits, and so it may correspond to a fractional number of
- * base-NBASE digits --- divide by DEC_DIGITS to convert to NBASE digits.
- *
- * rscale, or result scale, is the target precision for a computation.
- * Like dscale it is expressed as number of *decimal* digits after the decimal
- * point, and is always >= 0 at present.
- * Note that rscale is not stored in variables --- it's figured on-the-fly
- * from the dscales of the inputs.
- *
- * While we consistently use "weight" to refer to the base-NBASE weight of
- * a numeric value, it is convenient in some scale-related calculations to
- * make use of the base-10 weight (ie, the approximate log10 of the value).
- * To avoid confusion, such a decimal-units weight is called a "dweight".
- *
- * NB: All the variable-level functions are written in a style that makes it
- * possible to give one and the same variable as argument and destination.
- * This is feasible because the digit buffer is separate from the variable.
- * ----------
- */
-typedef struct NumericVar
-{
- int ndigits; /* # of digits in digits[] - can be 0! */
- int weight; /* weight of first digit */
- int sign; /* NUMERIC_POS, _NEG, _NAN, _PINF, or _NINF */
- int dscale; /* display scale */
- NumericDigit *buf; /* start of palloc'd space for digits[] */
- NumericDigit *digits; /* base-NBASE digits */
-} NumericVar;
-
-
/* ----------
* Data for generate_series
* ----------
@@ -491,8 +368,6 @@ static void dump_var(const char *str, NumericVar *var);
pfree(buf); \
} while (0)
-#define init_var(v) memset(v, 0, sizeof(NumericVar))
-
#define NUMERIC_DIGITS(num) (NUMERIC_HEADER_IS_SHORT(num) ? \
(num)->choice.n_short.n_data : (num)->choice.n_long.n_data)
#define NUMERIC_NDIGITS(num) \
@@ -502,10 +377,6 @@ static void dump_var(const char *str, NumericVar *var);
(weight) <= NUMERIC_SHORT_WEIGHT_MAX && \
(weight) >= NUMERIC_SHORT_WEIGHT_MIN)
-static void alloc_var(NumericVar *var, int ndigits);
-static void free_var(NumericVar *var);
-static void zero_var(NumericVar *var);
-
static bool set_var_from_str(const char *str, const char *cp,
NumericVar *dest, const char **endptr,
Node *escontext);
@@ -514,9 +385,6 @@ static bool set_var_from_non_decimal_integer_str(const char *str,
int base, NumericVar *dest,
const char **endptr,
Node *escontext);
-static void set_var_from_num(Numeric num, NumericVar *dest);
-static void init_var_from_num(Numeric num, NumericVar *dest);
-static void set_var_from_var(const NumericVar *value, NumericVar *dest);
static char *get_str_from_var(const NumericVar *var);
static char *get_str_from_var_sci(const NumericVar *var, int rscale);
@@ -524,8 +392,6 @@ static void numericvar_serialize(StringInfo buf, const NumericVar *var);
static void numericvar_deserialize(StringInfo buf, NumericVar *var);
static Numeric duplicate_numeric(Numeric num);
-static Numeric make_result(const NumericVar *var);
-static Numeric make_result_opt_error(const NumericVar *var, bool *have_error);
static bool apply_typmod(NumericVar *var, int32 typmod, Node *escontext);
static bool apply_typmod_special(Numeric num, int32 typmod, Node *escontext);
@@ -687,17 +553,17 @@ numeric_in(PG_FUNCTION_ARGS)
*/
if (pg_strncasecmp(numstart, "NaN", 3) == 0)
{
- res = make_result(&const_nan);
+ res = numeric_make_result(&const_nan);
cp = numstart + 3;
}
else if (pg_strncasecmp(cp, "Infinity", 8) == 0)
{
- res = make_result(sign == NUMERIC_POS ? &const_pinf : &const_ninf);
+ res = numeric_make_result(sign == NUMERIC_POS ? &const_pinf : &const_ninf);
cp += 8;
}
else if (pg_strncasecmp(cp, "inf", 3) == 0)
{
- res = make_result(sign == NUMERIC_POS ? &const_pinf : &const_ninf);
+ res = numeric_make_result(sign == NUMERIC_POS ? &const_pinf : &const_ninf);
cp += 3;
}
else
@@ -787,7 +653,7 @@ numeric_in(PG_FUNCTION_ARGS)
if (!apply_typmod(&value, typmod, escontext))
PG_RETURN_NULL();
- res = make_result_opt_error(&value, &have_error);
+ res = numeric_make_result_opt_error(&value, &have_error);
if (have_error)
ereturn(escontext, (Datum) 0,
@@ -1130,7 +996,7 @@ numeric_recv(PG_FUNCTION_ARGS)
* extra code (about as much as trunc_var involves), and it might cause
* client compatibility issues. Be careful not to apply trunc_var to
* special values, as it could do the wrong thing; we don't need it
- * anyway, since make_result will ignore all but the sign field.
+ * anyway, since make_result_numericvar will ignore all but the sign field.
*
* After doing that, be sure to check the typmod restriction.
*/
@@ -1141,12 +1007,12 @@ numeric_recv(PG_FUNCTION_ARGS)
(void) apply_typmod(&value, typmod, NULL);
- res = make_result(&value);
+ res = numeric_make_result(&value);
}
else
{
/* apply_typmod_special wants us to make the Numeric first */
- res = make_result(&value);
+ res = numeric_make_result(&value);
(void) apply_typmod_special(res, typmod, NULL);
}
@@ -1313,7 +1179,7 @@ numeric (PG_FUNCTION_ARGS)
set_var_from_num(num, &var);
(void) apply_typmod(&var, typmod, NULL);
- new = make_result(&var);
+ new = numeric_make_result(&var);
free_var(&var);
@@ -1515,16 +1381,16 @@ numeric_sign(PG_FUNCTION_ARGS)
* Handle NaN (infinities can be handled normally)
*/
if (NUMERIC_IS_NAN(num))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
switch (numeric_sign_internal(num))
{
case 0:
- PG_RETURN_NUMERIC(make_result(&const_zero));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_zero));
case 1:
- PG_RETURN_NUMERIC(make_result(&const_one));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_one));
case -1:
- PG_RETURN_NUMERIC(make_result(&const_minus_one));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_minus_one));
}
Assert(false);
@@ -1579,7 +1445,7 @@ numeric_round(PG_FUNCTION_ARGS)
/*
* Return the rounded result
*/
- res = make_result(&arg);
+ res = numeric_make_result(&arg);
free_var(&arg);
PG_RETURN_NUMERIC(res);
@@ -1631,7 +1497,7 @@ numeric_trunc(PG_FUNCTION_ARGS)
/*
* Return the truncated result
*/
- res = make_result(&arg);
+ res = numeric_make_result(&arg);
free_var(&arg);
PG_RETURN_NUMERIC(res);
@@ -1659,7 +1525,7 @@ numeric_ceil(PG_FUNCTION_ARGS)
init_var_from_num(num, &result);
ceil_var(&result, &result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
@@ -1687,7 +1553,7 @@ numeric_floor(PG_FUNCTION_ARGS)
init_var_from_num(num, &result);
floor_var(&result, &result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
@@ -1811,7 +1677,7 @@ generate_series_step_numeric(PG_FUNCTION_ARGS)
(fctx->step.sign == NUMERIC_NEG &&
cmp_var(&fctx->current, &fctx->stop) >= 0))
{
- Numeric result = make_result(&fctx->current);
+ Numeric result = numeric_make_result(&fctx->current);
/* switch to memory context appropriate for iteration calculation */
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
@@ -2995,26 +2861,26 @@ numeric_add_opt_error(Numeric num1, Numeric num2, bool *have_error)
if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
{
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- return make_result(&const_nan);
+ return numeric_make_result(&const_nan);
if (NUMERIC_IS_PINF(num1))
{
if (NUMERIC_IS_NINF(num2))
- return make_result(&const_nan); /* Inf + -Inf */
+ return numeric_make_result(&const_nan); /* Inf + -Inf */
else
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
}
if (NUMERIC_IS_NINF(num1))
{
if (NUMERIC_IS_PINF(num2))
- return make_result(&const_nan); /* -Inf + Inf */
+ return numeric_make_result(&const_nan); /* -Inf + Inf */
else
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
}
/* by here, num1 must be finite, so num2 is not */
if (NUMERIC_IS_PINF(num2))
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
Assert(NUMERIC_IS_NINF(num2));
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
}
/*
@@ -3026,7 +2892,7 @@ numeric_add_opt_error(Numeric num1, Numeric num2, bool *have_error)
init_var(&result);
add_var(&arg1, &arg2, &result);
- res = make_result_opt_error(&result, have_error);
+ res = numeric_make_result_opt_error(&result, have_error);
free_var(&result);
@@ -3073,26 +2939,26 @@ numeric_sub_opt_error(Numeric num1, Numeric num2, bool *have_error)
if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
{
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- return make_result(&const_nan);
+ return numeric_make_result(&const_nan);
if (NUMERIC_IS_PINF(num1))
{
if (NUMERIC_IS_PINF(num2))
- return make_result(&const_nan); /* Inf - Inf */
+ return numeric_make_result(&const_nan); /* Inf - Inf */
else
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
}
if (NUMERIC_IS_NINF(num1))
{
if (NUMERIC_IS_NINF(num2))
- return make_result(&const_nan); /* -Inf - -Inf */
+ return numeric_make_result(&const_nan); /* -Inf - -Inf */
else
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
}
/* by here, num1 must be finite, so num2 is not */
if (NUMERIC_IS_PINF(num2))
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
Assert(NUMERIC_IS_NINF(num2));
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
}
/*
@@ -3104,7 +2970,7 @@ numeric_sub_opt_error(Numeric num1, Numeric num2, bool *have_error)
init_var(&result);
sub_var(&arg1, &arg2, &result);
- res = make_result_opt_error(&result, have_error);
+ res = numeric_make_result_opt_error(&result, have_error);
free_var(&result);
@@ -3151,17 +3017,17 @@ numeric_mul_opt_error(Numeric num1, Numeric num2, bool *have_error)
if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
{
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- return make_result(&const_nan);
+ return numeric_make_result(&const_nan);
if (NUMERIC_IS_PINF(num1))
{
switch (numeric_sign_internal(num2))
{
case 0:
- return make_result(&const_nan); /* Inf * 0 */
+ return numeric_make_result(&const_nan); /* Inf * 0 */
case 1:
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
case -1:
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
}
Assert(false);
}
@@ -3170,11 +3036,11 @@ numeric_mul_opt_error(Numeric num1, Numeric num2, bool *have_error)
switch (numeric_sign_internal(num2))
{
case 0:
- return make_result(&const_nan); /* -Inf * 0 */
+ return numeric_make_result(&const_nan); /* -Inf * 0 */
case 1:
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
case -1:
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
}
Assert(false);
}
@@ -3184,11 +3050,11 @@ numeric_mul_opt_error(Numeric num1, Numeric num2, bool *have_error)
switch (numeric_sign_internal(num1))
{
case 0:
- return make_result(&const_nan); /* 0 * Inf */
+ return numeric_make_result(&const_nan); /* 0 * Inf */
case 1:
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
case -1:
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
}
Assert(false);
}
@@ -3196,11 +3062,11 @@ numeric_mul_opt_error(Numeric num1, Numeric num2, bool *have_error)
switch (numeric_sign_internal(num1))
{
case 0:
- return make_result(&const_nan); /* 0 * -Inf */
+ return numeric_make_result(&const_nan); /* 0 * -Inf */
case 1:
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
case -1:
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
}
Assert(false);
}
@@ -3225,7 +3091,7 @@ numeric_mul_opt_error(Numeric num1, Numeric num2, bool *have_error)
if (result.dscale > NUMERIC_DSCALE_MAX)
round_var(&result, NUMERIC_DSCALE_MAX);
- res = make_result_opt_error(&result, have_error);
+ res = numeric_make_result_opt_error(&result, have_error);
free_var(&result);
@@ -3276,11 +3142,11 @@ numeric_div_opt_error(Numeric num1, Numeric num2, bool *have_error)
if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
{
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- return make_result(&const_nan);
+ return numeric_make_result(&const_nan);
if (NUMERIC_IS_PINF(num1))
{
if (NUMERIC_IS_SPECIAL(num2))
- return make_result(&const_nan); /* Inf / [-]Inf */
+ return numeric_make_result(&const_nan); /* Inf / [-]Inf */
switch (numeric_sign_internal(num2))
{
case 0:
@@ -3294,16 +3160,16 @@ numeric_div_opt_error(Numeric num1, Numeric num2, bool *have_error)
errmsg("division by zero")));
break;
case 1:
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
case -1:
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
}
Assert(false);
}
if (NUMERIC_IS_NINF(num1))
{
if (NUMERIC_IS_SPECIAL(num2))
- return make_result(&const_nan); /* -Inf / [-]Inf */
+ return numeric_make_result(&const_nan); /* -Inf / [-]Inf */
switch (numeric_sign_internal(num2))
{
case 0:
@@ -3317,9 +3183,9 @@ numeric_div_opt_error(Numeric num1, Numeric num2, bool *have_error)
errmsg("division by zero")));
break;
case 1:
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
case -1:
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
}
Assert(false);
}
@@ -3330,7 +3196,7 @@ numeric_div_opt_error(Numeric num1, Numeric num2, bool *have_error)
* otherwise throw an underflow error. But the numeric type doesn't
* really do underflow, so let's just return zero.
*/
- return make_result(&const_zero);
+ return numeric_make_result(&const_zero);
}
/*
@@ -3360,7 +3226,7 @@ numeric_div_opt_error(Numeric num1, Numeric num2, bool *have_error)
*/
div_var(&arg1, &arg2, &result, rscale, true, true);
- res = make_result_opt_error(&result, have_error);
+ res = numeric_make_result_opt_error(&result, have_error);
free_var(&result);
@@ -3389,11 +3255,11 @@ numeric_div_trunc(PG_FUNCTION_ARGS)
if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
{
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
if (NUMERIC_IS_PINF(num1))
{
if (NUMERIC_IS_SPECIAL(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan)); /* Inf / [-]Inf */
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan)); /* Inf / [-]Inf */
switch (numeric_sign_internal(num2))
{
case 0:
@@ -3402,16 +3268,16 @@ numeric_div_trunc(PG_FUNCTION_ARGS)
errmsg("division by zero")));
break;
case 1:
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
case -1:
- PG_RETURN_NUMERIC(make_result(&const_ninf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_ninf));
}
Assert(false);
}
if (NUMERIC_IS_NINF(num1))
{
if (NUMERIC_IS_SPECIAL(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan)); /* -Inf / [-]Inf */
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan)); /* -Inf / [-]Inf */
switch (numeric_sign_internal(num2))
{
case 0:
@@ -3420,9 +3286,9 @@ numeric_div_trunc(PG_FUNCTION_ARGS)
errmsg("division by zero")));
break;
case 1:
- PG_RETURN_NUMERIC(make_result(&const_ninf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_ninf));
case -1:
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
}
Assert(false);
}
@@ -3433,7 +3299,7 @@ numeric_div_trunc(PG_FUNCTION_ARGS)
* otherwise throw an underflow error. But the numeric type doesn't
* really do underflow, so let's just return zero.
*/
- PG_RETURN_NUMERIC(make_result(&const_zero));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_zero));
}
/*
@@ -3449,7 +3315,7 @@ numeric_div_trunc(PG_FUNCTION_ARGS)
*/
div_var(&arg1, &arg2, &result, 0, false, true);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -3501,7 +3367,7 @@ numeric_mod_opt_error(Numeric num1, Numeric num2, bool *have_error)
if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
{
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- return make_result(&const_nan);
+ return numeric_make_result(&const_nan);
if (NUMERIC_IS_INF(num1))
{
if (numeric_sign_internal(num2) == 0)
@@ -3516,7 +3382,7 @@ numeric_mod_opt_error(Numeric num1, Numeric num2, bool *have_error)
errmsg("division by zero")));
}
/* Inf % any nonzero = NaN */
- return make_result(&const_nan);
+ return numeric_make_result(&const_nan);
}
/* num2 must be [-]Inf; result is num1 regardless of sign of num2 */
return duplicate_numeric(num1);
@@ -3538,7 +3404,7 @@ numeric_mod_opt_error(Numeric num1, Numeric num2, bool *have_error)
mod_var(&arg1, &arg2, &result);
- res = make_result_opt_error(&result, NULL);
+ res = numeric_make_result_opt_error(&result, NULL);
free_var(&result);
@@ -3571,7 +3437,7 @@ numeric_inc(PG_FUNCTION_ARGS)
add_var(&arg, &const_one, &arg);
- res = make_result(&arg);
+ res = numeric_make_result(&arg);
free_var(&arg);
@@ -3650,7 +3516,7 @@ numeric_gcd(PG_FUNCTION_ARGS)
* cases.
*/
if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
/*
* Unpack the arguments
@@ -3665,7 +3531,7 @@ numeric_gcd(PG_FUNCTION_ARGS)
*/
gcd_var(&arg1, &arg2, &result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -3693,7 +3559,7 @@ numeric_lcm(PG_FUNCTION_ARGS)
* cases.
*/
if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
/*
* Unpack the arguments
@@ -3725,7 +3591,7 @@ numeric_lcm(PG_FUNCTION_ARGS)
result.dscale = Max(arg1.dscale, arg2.dscale);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -3752,7 +3618,7 @@ numeric_fac(PG_FUNCTION_ARGS)
errmsg("factorial of a negative number is undefined")));
if (num <= 1)
{
- res = make_result(&const_one);
+ res = numeric_make_result(&const_one);
PG_RETURN_NUMERIC(res);
}
/* Fail immediately if the result would overflow */
@@ -3776,7 +3642,7 @@ numeric_fac(PG_FUNCTION_ARGS)
mul_var(&result, &fact, &result, 0);
}
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&fact);
free_var(&result);
@@ -3849,7 +3715,7 @@ numeric_sqrt(PG_FUNCTION_ARGS)
*/
sqrt_var(&arg, &result, rscale);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -3879,7 +3745,7 @@ numeric_exp(PG_FUNCTION_ARGS)
{
/* Per POSIX, exp(-Inf) is zero */
if (NUMERIC_IS_NINF(num))
- PG_RETURN_NUMERIC(make_result(&const_zero));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_zero));
/* For NAN or PINF, just duplicate the input */
PG_RETURN_NUMERIC(duplicate_numeric(num));
}
@@ -3916,7 +3782,7 @@ numeric_exp(PG_FUNCTION_ARGS)
*/
exp_var(&arg, &result, rscale);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -3965,7 +3831,7 @@ numeric_ln(PG_FUNCTION_ARGS)
ln_var(&arg, &result, rscale);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -3997,7 +3863,7 @@ numeric_log(PG_FUNCTION_ARGS)
sign2;
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
/* fail on negative inputs including -Inf, as log_var would */
sign1 = numeric_sign_internal(num1);
sign2 = numeric_sign_internal(num2);
@@ -4014,13 +3880,13 @@ numeric_log(PG_FUNCTION_ARGS)
{
/* log(Inf, Inf) reduces to Inf/Inf, so it's NaN */
if (NUMERIC_IS_PINF(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
/* log(Inf, finite-positive) is zero (we don't throw underflow) */
- PG_RETURN_NUMERIC(make_result(&const_zero));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_zero));
}
Assert(NUMERIC_IS_PINF(num2));
/* log(finite-positive, Inf) is Inf */
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
}
/*
@@ -4036,7 +3902,7 @@ numeric_log(PG_FUNCTION_ARGS)
*/
log_var(&arg1, &arg2, &result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -4077,9 +3943,9 @@ numeric_power(PG_FUNCTION_ARGS)
{
init_var_from_num(num2, &arg2);
if (cmp_var(&arg2, &const_zero) == 0)
- PG_RETURN_NUMERIC(make_result(&const_one));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_one));
}
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
}
if (NUMERIC_IS_NAN(num2))
{
@@ -4087,9 +3953,9 @@ numeric_power(PG_FUNCTION_ARGS)
{
init_var_from_num(num1, &arg1);
if (cmp_var(&arg1, &const_one) == 0)
- PG_RETURN_NUMERIC(make_result(&const_one));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_one));
}
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
}
/* At least one input is infinite, but error rules still apply */
sign1 = numeric_sign_internal(num1);
@@ -4112,14 +3978,14 @@ numeric_power(PG_FUNCTION_ARGS)
{
init_var_from_num(num1, &arg1);
if (cmp_var(&arg1, &const_one) == 0)
- PG_RETURN_NUMERIC(make_result(&const_one));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_one));
}
/*
* For any value of x, if y is [-]0, 1.0 shall be returned.
*/
if (sign2 == 0)
- PG_RETURN_NUMERIC(make_result(&const_one));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_one));
/*
* For any odd integer value of y > 0, if x is [-]0, [-]0 shall be
@@ -4128,7 +3994,7 @@ numeric_power(PG_FUNCTION_ARGS)
* distinguish these two cases.)
*/
if (sign1 == 0 && sign2 > 0)
- PG_RETURN_NUMERIC(make_result(&const_zero));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_zero));
/*
* If x is -1, and y is [-]Inf, 1.0 shall be returned.
@@ -4151,14 +4017,14 @@ numeric_power(PG_FUNCTION_ARGS)
{
init_var_from_num(num1, &arg1);
if (cmp_var(&arg1, &const_minus_one) == 0)
- PG_RETURN_NUMERIC(make_result(&const_one));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_one));
arg1.sign = NUMERIC_POS; /* now arg1 = abs(x) */
abs_x_gt_one = (cmp_var(&arg1, &const_one) > 0);
}
if (abs_x_gt_one == (sign2 > 0))
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
else
- PG_RETURN_NUMERIC(make_result(&const_zero));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_zero));
}
/*
@@ -4169,9 +4035,9 @@ numeric_power(PG_FUNCTION_ARGS)
if (NUMERIC_IS_PINF(num1))
{
if (sign2 > 0)
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
else
- PG_RETURN_NUMERIC(make_result(&const_zero));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_zero));
}
Assert(NUMERIC_IS_NINF(num1));
@@ -4182,7 +4048,7 @@ numeric_power(PG_FUNCTION_ARGS)
* (Again, we need not distinguish these two cases.)
*/
if (sign2 < 0)
- PG_RETURN_NUMERIC(make_result(&const_zero));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_zero));
/*
* For y an odd integer > 0, if x is -Inf, -Inf shall be returned. For
@@ -4191,9 +4057,9 @@ numeric_power(PG_FUNCTION_ARGS)
init_var_from_num(num2, &arg2);
if (arg2.ndigits > 0 && arg2.ndigits == arg2.weight + 1 &&
(arg2.digits[arg2.ndigits - 1] & 1))
- PG_RETURN_NUMERIC(make_result(&const_ninf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_ninf));
else
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
}
/*
@@ -4224,7 +4090,7 @@ numeric_power(PG_FUNCTION_ARGS)
*/
power_var(&arg1, &arg2, &result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -4333,7 +4199,7 @@ numeric_trim_scale(PG_FUNCTION_ARGS)
init_var_from_num(num, &result);
result.dscale = get_min_scale(&result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
@@ -4382,7 +4248,7 @@ random_numeric(pg_prng_state *state, Numeric rmin, Numeric rmax)
random_var(state, &rmin_var, &rmax_var, &result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -4407,7 +4273,7 @@ int64_to_numeric(int64 val)
int64_to_numericvar(val, &result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -4496,7 +4362,7 @@ int64_div_fast_to_numeric(int64 val1, int log10val2)
result.weight -= w;
result.dscale = rscale;
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -4717,14 +4583,14 @@ float8_numeric(PG_FUNCTION_ARGS)
const char *endptr;
if (isnan(val))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
if (isinf(val))
{
if (val < 0)
- PG_RETURN_NUMERIC(make_result(&const_ninf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_ninf));
else
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
}
snprintf(buf, sizeof(buf), "%.*g", DBL_DIG, val);
@@ -4734,7 +4600,7 @@ float8_numeric(PG_FUNCTION_ARGS)
/* Assume we need not worry about leading/trailing spaces */
(void) set_var_from_str(buf, buf, &result, &endptr, NULL);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -4811,14 +4677,14 @@ float4_numeric(PG_FUNCTION_ARGS)
const char *endptr;
if (isnan(val))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
if (isinf(val))
{
if (val < 0)
- PG_RETURN_NUMERIC(make_result(&const_ninf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_ninf));
else
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
}
snprintf(buf, sizeof(buf), "%.*g", FLT_DIG, val);
@@ -4828,7 +4694,7 @@ float4_numeric(PG_FUNCTION_ARGS)
/* Assume we need not worry about leading/trailing spaces */
(void) set_var_from_str(buf, buf, &result, &endptr, NULL);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -6202,7 +6068,7 @@ numeric_poly_sum(PG_FUNCTION_ARGS)
int128_to_numericvar(state->sumX, &result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -6232,7 +6098,7 @@ numeric_poly_avg(PG_FUNCTION_ARGS)
int128_to_numericvar(state->sumX, &result);
countd = NumericGetDatum(int64_to_numeric(state->N));
- sumd = NumericGetDatum(make_result(&result));
+ sumd = NumericGetDatum(numeric_make_result(&result));
free_var(&result);
@@ -6257,21 +6123,21 @@ numeric_avg(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (state->NaNcount > 0) /* there was at least one NaN input */
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
/* adding plus and minus infinities gives NaN */
if (state->pInfcount > 0 && state->nInfcount > 0)
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
if (state->pInfcount > 0)
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
if (state->nInfcount > 0)
- PG_RETURN_NUMERIC(make_result(&const_ninf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_ninf));
N_datum = NumericGetDatum(int64_to_numeric(state->N));
init_var(&sumX_var);
accum_sum_final(&state->sumX, &sumX_var);
- sumX_datum = NumericGetDatum(make_result(&sumX_var));
+ sumX_datum = NumericGetDatum(numeric_make_result(&sumX_var));
free_var(&sumX_var);
PG_RETURN_DATUM(DirectFunctionCall2(numeric_div, sumX_datum, N_datum));
@@ -6291,19 +6157,19 @@ numeric_sum(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (state->NaNcount > 0) /* there was at least one NaN input */
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
/* adding plus and minus infinities gives NaN */
if (state->pInfcount > 0 && state->nInfcount > 0)
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
if (state->pInfcount > 0)
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
if (state->nInfcount > 0)
- PG_RETURN_NUMERIC(make_result(&const_ninf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_ninf));
init_var(&sumX_var);
accum_sum_final(&state->sumX, &sumX_var);
- result = make_result(&sumX_var);
+ result = numeric_make_result(&sumX_var);
free_var(&sumX_var);
PG_RETURN_NUMERIC(result);
@@ -6357,7 +6223,7 @@ numeric_stddev_internal(NumericAggState *state,
* float8 functions, any infinity input produces NaN output.
*/
if (state->NaNcount > 0 || state->pInfcount > 0 || state->nInfcount > 0)
- return make_result(&const_nan);
+ return numeric_make_result(&const_nan);
/* OK, normal calculation applies */
init_var(&vN);
@@ -6381,7 +6247,7 @@ numeric_stddev_internal(NumericAggState *state,
if (cmp_var(&vsumX2, &const_zero) <= 0)
{
/* Watch out for roundoff error producing a negative numerator */
- res = make_result(&const_zero);
+ res = numeric_make_result(&const_zero);
}
else
{
@@ -6394,7 +6260,7 @@ numeric_stddev_internal(NumericAggState *state,
if (!variance)
sqrt_var(&vsumX, &vsumX, rscale); /* stddev */
- res = make_result(&vsumX);
+ res = numeric_make_result(&vsumX);
}
free_var(&vNminus1);
@@ -7067,7 +6933,7 @@ dump_var(const char *str, NumericVar *var)
*
* Allocate a digit buffer of ndigits digits (plus a spare digit for rounding)
*/
-static void
+void
alloc_var(NumericVar *var, int ndigits)
{
digitbuf_free(var->buf);
@@ -7083,7 +6949,7 @@ alloc_var(NumericVar *var, int ndigits)
*
* Return the digit buffer of a variable to the free pool
*/
-static void
+void
free_var(NumericVar *var)
{
digitbuf_free(var->buf);
@@ -7099,7 +6965,7 @@ free_var(NumericVar *var)
* Set a variable to ZERO.
* Note: its dscale is not touched.
*/
-static void
+void
zero_var(NumericVar *var)
{
digitbuf_free(var->buf);
@@ -7221,8 +7087,8 @@ set_var_from_str(const char *str, const char *cp,
* INT_MAX/2 due to the MaxAllocSize limit on string length, so
* constraining the exponent similarly should be enough to prevent
* integer overflow in this function. If the value is too large to
- * fit in storage format, make_result() will complain about it later;
- * for consistency use the same ereport errcode/text as make_result().
+ * fit in storage format, numeric_make_result() will complain about it later;
+ * for consistency use the same ereport errcode/text as numeric_make_result().
*/
/* exponent sign */
@@ -7534,7 +7400,7 @@ invalid_syntax:
*
* Convert the packed db format into a variable
*/
-static void
+void
set_var_from_num(Numeric num, NumericVar *dest)
{
int ndigits;
@@ -7565,7 +7431,7 @@ set_var_from_num(Numeric num, NumericVar *dest)
* propagate to the original Numeric! It's OK to use it as the destination
* argument of one of the calculational functions, though.
*/
-static void
+void
init_var_from_num(Numeric num, NumericVar *dest)
{
dest->ndigits = NUMERIC_NDIGITS(num);
@@ -7582,7 +7448,7 @@ init_var_from_num(Numeric num, NumericVar *dest)
*
* Copy one variable into another
*/
-static void
+void
set_var_from_var(const NumericVar *value, NumericVar *dest)
{
NumericDigit *newbuf;
@@ -7888,7 +7754,7 @@ duplicate_numeric(Numeric num)
}
/*
- * make_result_opt_error() -
+ * numeric_make_result_opt_error() -
*
* Create the packed db numeric format in palloc()'d memory from
* a variable. This will handle NaN and Infinity cases.
@@ -7896,8 +7762,8 @@ duplicate_numeric(Numeric num)
* If "have_error" isn't NULL, on overflow *have_error is set to true and
* NULL is returned. This is helpful when caller needs to handle errors.
*/
-static Numeric
-make_result_opt_error(const NumericVar *var, bool *have_error)
+Numeric
+numeric_make_result_opt_error(const NumericVar *var, bool *have_error)
{
Numeric result;
NumericDigit *digits = var->digits;
@@ -7927,7 +7793,7 @@ make_result_opt_error(const NumericVar *var, bool *have_error)
result->choice.n_header = sign;
/* the header word is all we need */
- dump_numeric("make_result()", result);
+ dump_numeric("numeric_make_result()", result);
return result;
}
@@ -7995,20 +7861,20 @@ make_result_opt_error(const NumericVar *var, bool *have_error)
}
}
- dump_numeric("make_result()", result);
+ dump_numeric("numeric_make_result()", result);
return result;
}
/*
- * make_result() -
+ * numeric_make_result() -
*
- * An interface to make_result_opt_error() without "have_error" argument.
+ * An interface to numeric_make_result_opt_error() without "have_error" argument.
*/
-static Numeric
-make_result(const NumericVar *var)
+Numeric
+numeric_make_result(const NumericVar *var)
{
- return make_result_opt_error(var, NULL);
+ return numeric_make_result_opt_error(var, NULL);
}
diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h
index 9e79fc376cb..4a290577abd 100644
--- a/src/include/utils/numeric.h
+++ b/src/include/utils/numeric.h
@@ -42,6 +42,59 @@
#define NUMERIC_MAX_RESULT_SCALE (NUMERIC_MAX_PRECISION * 2)
+/* ----------
+ * Local data types
+ *
+ * Numeric values are represented in a base-NBASE floating point format.
+ * Each "digit" ranges from 0 to NBASE-1. The type NumericDigit is signed
+ * and wide enough to store a digit. We assume that NBASE*NBASE can fit in
+ * an int. Although the purely calculational routines could handle any even
+ * NBASE that's less than sqrt(INT_MAX), in practice we are only interested
+ * in NBASE a power of ten, so that I/O conversions and decimal rounding
+ * are easy. Also, it's actually more efficient if NBASE is rather less than
+ * sqrt(INT_MAX), so that there is "headroom" for mul_var and div_var to
+ * postpone processing carries.
+ *
+ * Values of NBASE other than 10000 are considered of historical interest only
+ * and are no longer supported in any sense; no mechanism exists for the client
+ * to discover the base, so every client supporting binary mode expects the
+ * base-10000 format. If you plan to change this, also note the numeric
+ * abbreviation code, which assumes NBASE=10000.
+ * ----------
+ */
+
+#if 0
+#define NBASE 10
+#define HALF_NBASE 5
+#define DEC_DIGITS 1 /* decimal digits per NBASE digit */
+#define MUL_GUARD_DIGITS 4 /* these are measured in NBASE digits */
+#define DIV_GUARD_DIGITS 8
+
+typedef signed char NumericDigit;
+#endif
+
+#if 0
+#define NBASE 100
+#define HALF_NBASE 50
+#define DEC_DIGITS 2 /* decimal digits per NBASE digit */
+#define MUL_GUARD_DIGITS 3 /* these are measured in NBASE digits */
+#define DIV_GUARD_DIGITS 6
+
+typedef signed char NumericDigit;
+#endif
+
+#if 1
+#define NBASE 10000
+#define HALF_NBASE 5000
+#define DEC_DIGITS 4 /* decimal digits per NBASE digit */
+#define MUL_GUARD_DIGITS 2 /* these are measured in NBASE digits */
+#define DIV_GUARD_DIGITS 4
+
+typedef int16 NumericDigit;
+#endif
+
+#define NBASE_SQR (NBASE * NBASE)
+
/*
* For inherently inexact calculations such as division and square root,
* we try to get at least this many significant digits; the idea is to
@@ -49,6 +102,84 @@
*/
#define NUMERIC_MIN_SIG_DIGITS 16
+/*
+ * sign field of NumericVar
+ */
+
+#define NUMERIC_POS 0x0000
+#define NUMERIC_NEG 0x4000
+#define NUMERIC_NAN 0xC000
+#define NUMERIC_PINF 0xD000
+#define NUMERIC_NINF 0xF000
+
+/*
+ * Maximum weight of a stored Numeric value (based on the use of int16 for the
+ * weight in NumericLong). Note that intermediate values held in NumericVar
+ * and NumericSumAccum variables may have much larger weights.
+ */
+ #define NUMERIC_WEIGHT_MAX PG_INT16_MAX
+
+/* ----------
+ * NumericVar is the format we use for arithmetic. The digit-array part
+ * is the same as the NumericData storage format, but the header is more
+ * complex.
+ *
+ * The value represented by a NumericVar is determined by the sign, weight,
+ * ndigits, and digits[] array. If it is a "special" value (NaN or Inf)
+ * then only the sign field matters; ndigits should be zero, and the weight
+ * and dscale fields are ignored.
+ *
+ * Note: the first digit of a NumericVar's value is assumed to be multiplied
+ * by NBASE ** weight. Another way to say it is that there are weight+1
+ * digits before the decimal point. It is possible to have weight < 0.
+ *
+ * buf points at the physical start of the palloc'd digit buffer for the
+ * NumericVar. digits points at the first digit in actual use (the one
+ * with the specified weight). We normally leave an unused digit or two
+ * (preset to zeroes) between buf and digits, so that there is room to store
+ * a carry out of the top digit without reallocating space. We just need to
+ * decrement digits (and increment weight) to make room for the carry digit.
+ * (There is no such extra space in a numeric value stored in the database,
+ * only in a NumericVar in memory.)
+ *
+ * If buf is NULL then the digit buffer isn't actually palloc'd and should
+ * not be freed --- see the constants below for an example.
+ *
+ * dscale, or display scale, is the nominal precision expressed as number
+ * of digits after the decimal point (it must always be >= 0 at present).
+ * dscale may be more than the number of physically stored fractional digits,
+ * implying that we have suppressed storage of significant trailing zeroes.
+ * It should never be less than the number of stored digits, since that would
+ * imply hiding digits that are present. NOTE that dscale is always expressed
+ * in *decimal* digits, and so it may correspond to a fractional number of
+ * base-NBASE digits --- divide by DEC_DIGITS to convert to NBASE digits.
+ *
+ * rscale, or result scale, is the target precision for a computation.
+ * Like dscale it is expressed as number of *decimal* digits after the decimal
+ * point, and is always >= 0 at present.
+ * Note that rscale is not stored in variables --- it's figured on-the-fly
+ * from the dscales of the inputs.
+ *
+ * While we consistently use "weight" to refer to the base-NBASE weight of
+ * a numeric value, it is convenient in some scale-related calculations to
+ * make use of the base-10 weight (ie, the approximate log10 of the value).
+ * To avoid confusion, such a decimal-units weight is called a "dweight".
+ *
+ * NB: All the variable-level functions are written in a style that makes it
+ * possible to give one and the same variable as argument and destination.
+ * This is feasible because the digit buffer is separate from the variable.
+ * ----------
+ */
+typedef struct NumericVar
+{
+ int ndigits; /* # of digits in digits[] - can be 0! */
+ int weight; /* weight of first digit */
+ int sign; /* NUMERIC_POS, _NEG, _NAN, _PINF, or _NINF */
+ int dscale; /* display scale */
+ NumericDigit *buf; /* start of palloc'd space for digits[] */
+ NumericDigit *digits; /* base-NBASE digits */
+} NumericVar;
+
/* The actual contents of Numeric are private to numeric.c */
struct NumericData;
typedef struct NumericData *Numeric;
@@ -79,9 +210,22 @@ NumericGetDatum(Numeric X)
#define PG_GETARG_NUMERIC_COPY(n) DatumGetNumericCopy(PG_GETARG_DATUM(n))
#define PG_RETURN_NUMERIC(x) return NumericGetDatum(x)
+#define init_var(v) memset(v, 0, sizeof(NumericVar))
+
/*
* Utility functions in numeric.c
*/
+extern void alloc_var(NumericVar *var, int ndigits);
+extern void free_var(NumericVar *var);
+extern void zero_var(NumericVar *var);
+
+extern void set_var_from_num(Numeric num, NumericVar *dest);
+extern void init_var_from_num(Numeric num, NumericVar *dest);
+extern void set_var_from_var(const NumericVar *value, NumericVar *dest);
+
+extern Numeric numeric_make_result(const NumericVar *var);
+extern Numeric numeric_make_result_opt_error(const NumericVar *var, bool *have_error);
+
extern bool numeric_is_nan(Numeric num);
extern bool numeric_is_inf(Numeric num);
extern int32 numeric_maximum_size(int32 typmod);
Ed Behn <ed@behn.us> writes:
There is actually no new code. Code is simply moved from numeric.c to
numeric.h.
I will absolutely not hold still for that. It would mean that any
time we want to think about messing with the contents of numerics,
we need to examine more or less the whole Postgres code base to see
what else is poking into those structures.
If we must do something like this, then a separate header
"numeric_internal.h" or something like that would reduce the blast
radius for changes. But IMO you still haven't made an acceptable case
for deciding that these data structures aren't private to numeric.c.
What behaviors do you actually need that aren't accessible via the
existing exported functons?
regards, tom lane
Tom-
I think that I can allay your concerns. Please give me a day or so to
put together my more complete thoughts on the matter. I'll be in touch.
-Ed
On Sat, Mar 1, 2025 at 11:33 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Show quoted text
Ed Behn <ed@behn.us> writes:
There is actually no new code. Code is simply moved from numeric.c to
numeric.h.I will absolutely not hold still for that. It would mean that any
time we want to think about messing with the contents of numerics,
we need to examine more or less the whole Postgres code base to see
what else is poking into those structures.If we must do something like this, then a separate header
"numeric_internal.h" or something like that would reduce the blast
radius for changes. But IMO you still haven't made an acceptable case
for deciding that these data structures aren't private to numeric.c.
What behaviors do you actually need that aren't accessible via the
existing exported functons?regards, tom lane
On Sat, 1 Mar 2025 at 17:33, Tom Lane <tgl@sss.pgh.pa.us> wrote:
But IMO you still haven't made an acceptable case
for deciding that these data structures aren't private to numeric.c.
What behaviors do you actually need that aren't accessible via the
existing exported functons?
FWIW in pg_duckdb we would definitely have liked to have access to
some of the currently unexposed numeric internals. We vendored in some
of the definitions that we required ourselves[1]https://github.com/duckdb/pg_duckdb/blob/main/include/pgduckdb/vendor/pg_numeric_c.hpp, but it would be very
nice if we didn't have to. I cannot speak for Ed's reasoning, but for
pg_duckdb the reason we cannot use the many of the already exposed
functions are not thread-safe. So we create our own thread-safe way of
constructing these functions. Another reason is so we can construct
numeric types from int128/uint128 types efficiently. I would be very
happy if we'd have these definitions available, even if they would
change in a future PG version (that's what you sign up for as an
extension author). And indeed as Robert already said, numeric seems to
be the only type that has this kind of restriction on viewing the
internal representation.
[1]: https://github.com/duckdb/pg_duckdb/blob/main/include/pgduckdb/vendor/pg_numeric_c.hpp
Tom-
I understand that you are concerned about future maintenance costs vs
benefits of this change. I hope that I can address those concerns. An
important thing to note is that there are two different structures that
represent numeric values:
* NumericData is an opaque structure that is defined in numeric.c. It
is this struct that is used to store values. The patch I submitted has this
structure remain opaque and in numeric.c. Its internals are messy and
subject to future changes. I agree that third parties should not have
access to this. Of note is that the type Numeric is a typedef of
NumericData*.
* NumericVar is a user-friendly structure that already exists. It is
this structure that I propose moving to numeric.h. There are functions that
exist to convert it to and from NumericData. It is these functions that I
propose giving access to.
What the patch boils down to is the movement of NumericVar to numeric.h
along with function declarations for the basic function to work with it and
a few pre-processor declarations.
I agree that there is the potential for future maintenance costs here.
However, any future changes to NumericData would necessitate updating the
code to convert to and from NumericVar regardless of the proposed changes.
I think that this small increase in costs is outweighed by the benefits of
allowing third parties to access this powerful datatype.
As for the reason that I would like to make this change: I am the
maintainer of the PL/Haskell extension. (It allows the use of Haskell code
as a procedural language. https://github.com/ed-o-saurus/PLHaskell) In the
extension, users can currently pass several Postgres types and also have
the function return them. This is accomplished by using the functions and
macros that convert between Datums and C data types. (For example
DatumGetFloat8 and Float8GetDatum to handle double-precision floating point
values) I would like to add support for the use of the numeric type to the
extension. To this end, I would need to create a Haskell type that mirrors
the Postgres numeric type. Passed Haskell values would be instantiated by
reading the data from Postgres. Conversely, returned values would be
converted to the Postgres type. Internally, users would be able to perform
mathematical operations with the Haskell values like any other type.
Currently, there is no way for a third-party extension to get needed
information about numeric values or build new numeric values. The proposed
changes would remedy this.
An alternative approach would be to make available calls to read and
create numeric data. However, as the NumericVar struct already exists, I
feel that utilizing it is the more natural approach.
What do you think?
-Ed
On Sat, Mar 1, 2025 at 3:32 PM Ed Behn <ed@behn.us> wrote:
Show quoted text
Tom-
I think that I can allay your concerns. Please give me a day or so to
put together my more complete thoughts on the matter. I'll be in touch.-Ed
On Sat, Mar 1, 2025 at 11:33 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Ed Behn <ed@behn.us> writes:
There is actually no new code. Code is simply moved from numeric.c to
numeric.h.I will absolutely not hold still for that. It would mean that any
time we want to think about messing with the contents of numerics,
we need to examine more or less the whole Postgres code base to see
what else is poking into those structures.If we must do something like this, then a separate header
"numeric_internal.h" or something like that would reduce the blast
radius for changes. But IMO you still haven't made an acceptable case
for deciding that these data structures aren't private to numeric.c.
What behaviors do you actually need that aren't accessible via the
existing exported functons?regards, tom lane
Good morning-
It looks like the proposed change associated with this thread has
languished (https://commitfest.postgresql.org/patch/5623/). Is there
anything I can do to get it rolling again?
-Ed
On Sat, Mar 1, 2025 at 5:25 PM Ed Behn <ed@behn.us> wrote:
Show quoted text
Tom-
I understand that you are concerned about future maintenance costs vs
benefits of this change. I hope that I can address those concerns. An
important thing to note is that there are two different structures that
represent numeric values:
* NumericData is an opaque structure that is defined in numeric.c. It
is this struct that is used to store values. The patch I submitted has this
structure remain opaque and in numeric.c. Its internals are messy and
subject to future changes. I agree that third parties should not have
access to this. Of note is that the type Numeric is a typedef of
NumericData*.
* NumericVar is a user-friendly structure that already exists. It is
this structure that I propose moving to numeric.h. There are functions that
exist to convert it to and from NumericData. It is these functions that I
propose giving access to.What the patch boils down to is the movement of NumericVar to
numeric.h along with function declarations for the basic function to work
with it and a few pre-processor declarations.I agree that there is the potential for future maintenance costs here.
However, any future changes to NumericData would necessitate updating the
code to convert to and from NumericVar regardless of the proposed changes.
I think that this small increase in costs is outweighed by the benefits of
allowing third parties to access this powerful datatype.As for the reason that I would like to make this change: I am the
maintainer of the PL/Haskell extension. (It allows the use of Haskell code
as a procedural language. https://github.com/ed-o-saurus/PLHaskell) In
the extension, users can currently pass several Postgres types and also
have the function return them. This is accomplished by using the functions
and macros that convert between Datums and C data types. (For example
DatumGetFloat8 and Float8GetDatum to handle double-precision floating point
values) I would like to add support for the use of the numeric type to the
extension. To this end, I would need to create a Haskell type that mirrors
the Postgres numeric type. Passed Haskell values would be instantiated by
reading the data from Postgres. Conversely, returned values would be
converted to the Postgres type. Internally, users would be able to perform
mathematical operations with the Haskell values like any other type.
Currently, there is no way for a third-party extension to get needed
information about numeric values or build new numeric values. The proposed
changes would remedy this.An alternative approach would be to make available calls to read and
create numeric data. However, as the NumericVar struct already exists, I
feel that utilizing it is the more natural approach.What do you think?
-EdOn Sat, Mar 1, 2025 at 3:32 PM Ed Behn <ed@behn.us> wrote:
Tom-
I think that I can allay your concerns. Please give me a day or so to
put together my more complete thoughts on the matter. I'll be in touch.-Ed
On Sat, Mar 1, 2025 at 11:33 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Ed Behn <ed@behn.us> writes:
There is actually no new code. Code is simply moved from numeric.c to
numeric.h.I will absolutely not hold still for that. It would mean that any
time we want to think about messing with the contents of numerics,
we need to examine more or less the whole Postgres code base to see
what else is poking into those structures.If we must do something like this, then a separate header
"numeric_internal.h" or something like that would reduce the blast
radius for changes. But IMO you still haven't made an acceptable case
for deciding that these data structures aren't private to numeric.c.
What behaviors do you actually need that aren't accessible via the
existing exported functons?regards, tom lane
Please find attached an updated patch against the current master branch.
-Ed
On Sat, Sep 20, 2025 at 8:11 AM Ed Behn <ed@behn.us> wrote:
Show quoted text
Good morning-
It looks like the proposed change associated with this thread has
languished (https://commitfest.postgresql.org/patch/5623/). Is there
anything I can do to get it rolling again?-Ed
On Sat, Mar 1, 2025 at 5:25 PM Ed Behn <ed@behn.us> wrote:
Tom-
I understand that you are concerned about future maintenance costs vs
benefits of this change. I hope that I can address those concerns. An
important thing to note is that there are two different structures that
represent numeric values:
* NumericData is an opaque structure that is defined in numeric.c. It
is this struct that is used to store values. The patch I submitted has this
structure remain opaque and in numeric.c. Its internals are messy and
subject to future changes. I agree that third parties should not have
access to this. Of note is that the type Numeric is a typedef of
NumericData*.
* NumericVar is a user-friendly structure that already exists. It is
this structure that I propose moving to numeric.h. There are functions that
exist to convert it to and from NumericData. It is these functions that I
propose giving access to.What the patch boils down to is the movement of NumericVar to
numeric.h along with function declarations for the basic function to work
with it and a few pre-processor declarations.I agree that there is the potential for future maintenance costs
here. However, any future changes to NumericData would necessitate updating
the code to convert to and from NumericVar regardless of the proposed
changes. I think that this small increase in costs is outweighed by the
benefits of allowing third parties to access this powerful datatype.As for the reason that I would like to make this change: I am the
maintainer of the PL/Haskell extension. (It allows the use of Haskell code
as a procedural language. https://github.com/ed-o-saurus/PLHaskell) In
the extension, users can currently pass several Postgres types and also
have the function return them. This is accomplished by using the functions
and macros that convert between Datums and C data types. (For example
DatumGetFloat8 and Float8GetDatum to handle double-precision floating point
values) I would like to add support for the use of the numeric type to the
extension. To this end, I would need to create a Haskell type that mirrors
the Postgres numeric type. Passed Haskell values would be instantiated by
reading the data from Postgres. Conversely, returned values would be
converted to the Postgres type. Internally, users would be able to perform
mathematical operations with the Haskell values like any other type.
Currently, there is no way for a third-party extension to get needed
information about numeric values or build new numeric values. The proposed
changes would remedy this.An alternative approach would be to make available calls to read and
create numeric data. However, as the NumericVar struct already exists, I
feel that utilizing it is the more natural approach.What do you think?
-EdOn Sat, Mar 1, 2025 at 3:32 PM Ed Behn <ed@behn.us> wrote:
Tom-
I think that I can allay your concerns. Please give me a day or so
to put together my more complete thoughts on the matter. I'll be in touch.-Ed
On Sat, Mar 1, 2025 at 11:33 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Ed Behn <ed@behn.us> writes:
There is actually no new code. Code is simply moved from numeric.c to
numeric.h.I will absolutely not hold still for that. It would mean that any
time we want to think about messing with the contents of numerics,
we need to examine more or less the whole Postgres code base to see
what else is poking into those structures.If we must do something like this, then a separate header
"numeric_internal.h" or something like that would reduce the blast
radius for changes. But IMO you still haven't made an acceptable case
for deciding that these data structures aren't private to numeric.c.
What behaviors do you actually need that aren't accessible via the
existing exported functons?regards, tom lane
Attachments:
numeric.patchtext/x-patch; charset=US-ASCII; name=numeric.patchDownload
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 76269918593..a700d3ee690 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -46,65 +46,11 @@
/* ----------
* Uncomment the following to enable compilation of dump_numeric()
- * and dump_var() and to get a dump of any result produced by make_result().
+ * and dump_var() and to get a dump of any result produced by numeric_make_result().
* ----------
#define NUMERIC_DEBUG
*/
-
-/* ----------
- * Local data types
- *
- * Numeric values are represented in a base-NBASE floating point format.
- * Each "digit" ranges from 0 to NBASE-1. The type NumericDigit is signed
- * and wide enough to store a digit. We assume that NBASE*NBASE can fit in
- * an int. Although the purely calculational routines could handle any even
- * NBASE that's less than sqrt(INT_MAX), in practice we are only interested
- * in NBASE a power of ten, so that I/O conversions and decimal rounding
- * are easy. Also, it's actually more efficient if NBASE is rather less than
- * sqrt(INT_MAX), so that there is "headroom" for mul_var and div_var to
- * postpone processing carries.
- *
- * Values of NBASE other than 10000 are considered of historical interest only
- * and are no longer supported in any sense; no mechanism exists for the client
- * to discover the base, so every client supporting binary mode expects the
- * base-10000 format. If you plan to change this, also note the numeric
- * abbreviation code, which assumes NBASE=10000.
- * ----------
- */
-
-#if 0
-#define NBASE 10
-#define HALF_NBASE 5
-#define DEC_DIGITS 1 /* decimal digits per NBASE digit */
-#define MUL_GUARD_DIGITS 4 /* these are measured in NBASE digits */
-#define DIV_GUARD_DIGITS 8
-
-typedef signed char NumericDigit;
-#endif
-
-#if 0
-#define NBASE 100
-#define HALF_NBASE 50
-#define DEC_DIGITS 2 /* decimal digits per NBASE digit */
-#define MUL_GUARD_DIGITS 3 /* these are measured in NBASE digits */
-#define DIV_GUARD_DIGITS 6
-
-typedef signed char NumericDigit;
-#endif
-
-#if 1
-#define NBASE 10000
-#define HALF_NBASE 5000
-#define DEC_DIGITS 4 /* decimal digits per NBASE digit */
-#define MUL_GUARD_DIGITS 2 /* these are measured in NBASE digits */
-#define DIV_GUARD_DIGITS 4
-
-typedef int16 NumericDigit;
-#endif
-
-#define NBASE_SQR (NBASE * NBASE)
-
/*
* The Numeric type as stored on disk.
*
@@ -166,8 +112,6 @@ struct NumericData
*/
#define NUMERIC_SIGN_MASK 0xC000
-#define NUMERIC_POS 0x0000
-#define NUMERIC_NEG 0x4000
#define NUMERIC_SHORT 0x8000
#define NUMERIC_SPECIAL 0xC000
@@ -198,9 +142,6 @@ struct NumericData
* currently those bits must be zeroes, so masking would just add cycles.
*/
#define NUMERIC_EXT_SIGN_MASK 0xF000 /* high bits plus NaN/Inf flag bits */
-#define NUMERIC_NAN 0xC000
-#define NUMERIC_PINF 0xD000
-#define NUMERIC_NINF 0xF000
#define NUMERIC_INF_SIGN_MASK 0x2000
#define NUMERIC_EXT_FLAGBITS(n) ((n)->choice.n_header & NUMERIC_EXT_SIGN_MASK)
@@ -253,75 +194,6 @@ struct NumericData
| ((n)->choice.n_short.n_header & NUMERIC_SHORT_WEIGHT_MASK)) \
: ((n)->choice.n_long.n_weight))
-/*
- * Maximum weight of a stored Numeric value (based on the use of int16 for the
- * weight in NumericLong). Note that intermediate values held in NumericVar
- * and NumericSumAccum variables may have much larger weights.
- */
-#define NUMERIC_WEIGHT_MAX PG_INT16_MAX
-
-/* ----------
- * NumericVar is the format we use for arithmetic. The digit-array part
- * is the same as the NumericData storage format, but the header is more
- * complex.
- *
- * The value represented by a NumericVar is determined by the sign, weight,
- * ndigits, and digits[] array. If it is a "special" value (NaN or Inf)
- * then only the sign field matters; ndigits should be zero, and the weight
- * and dscale fields are ignored.
- *
- * Note: the first digit of a NumericVar's value is assumed to be multiplied
- * by NBASE ** weight. Another way to say it is that there are weight+1
- * digits before the decimal point. It is possible to have weight < 0.
- *
- * buf points at the physical start of the palloc'd digit buffer for the
- * NumericVar. digits points at the first digit in actual use (the one
- * with the specified weight). We normally leave an unused digit or two
- * (preset to zeroes) between buf and digits, so that there is room to store
- * a carry out of the top digit without reallocating space. We just need to
- * decrement digits (and increment weight) to make room for the carry digit.
- * (There is no such extra space in a numeric value stored in the database,
- * only in a NumericVar in memory.)
- *
- * If buf is NULL then the digit buffer isn't actually palloc'd and should
- * not be freed --- see the constants below for an example.
- *
- * dscale, or display scale, is the nominal precision expressed as number
- * of digits after the decimal point (it must always be >= 0 at present).
- * dscale may be more than the number of physically stored fractional digits,
- * implying that we have suppressed storage of significant trailing zeroes.
- * It should never be less than the number of stored digits, since that would
- * imply hiding digits that are present. NOTE that dscale is always expressed
- * in *decimal* digits, and so it may correspond to a fractional number of
- * base-NBASE digits --- divide by DEC_DIGITS to convert to NBASE digits.
- *
- * rscale, or result scale, is the target precision for a computation.
- * Like dscale it is expressed as number of *decimal* digits after the decimal
- * point, and is always >= 0 at present.
- * Note that rscale is not stored in variables --- it's figured on-the-fly
- * from the dscales of the inputs.
- *
- * While we consistently use "weight" to refer to the base-NBASE weight of
- * a numeric value, it is convenient in some scale-related calculations to
- * make use of the base-10 weight (ie, the approximate log10 of the value).
- * To avoid confusion, such a decimal-units weight is called a "dweight".
- *
- * NB: All the variable-level functions are written in a style that makes it
- * possible to give one and the same variable as argument and destination.
- * This is feasible because the digit buffer is separate from the variable.
- * ----------
- */
-typedef struct NumericVar
-{
- int ndigits; /* # of digits in digits[] - can be 0! */
- int weight; /* weight of first digit */
- int sign; /* NUMERIC_POS, _NEG, _NAN, _PINF, or _NINF */
- int dscale; /* display scale */
- NumericDigit *buf; /* start of palloc'd space for digits[] */
- NumericDigit *digits; /* base-NBASE digits */
-} NumericVar;
-
-
/* ----------
* Data for generate_series
* ----------
@@ -483,8 +355,6 @@ static void dump_var(const char *str, NumericVar *var);
pfree(buf); \
} while (0)
-#define init_var(v) memset(v, 0, sizeof(NumericVar))
-
#define NUMERIC_DIGITS(num) (NUMERIC_HEADER_IS_SHORT(num) ? \
(num)->choice.n_short.n_data : (num)->choice.n_long.n_data)
#define NUMERIC_NDIGITS(num) \
@@ -494,10 +364,6 @@ static void dump_var(const char *str, NumericVar *var);
(weight) <= NUMERIC_SHORT_WEIGHT_MAX && \
(weight) >= NUMERIC_SHORT_WEIGHT_MIN)
-static void alloc_var(NumericVar *var, int ndigits);
-static void free_var(NumericVar *var);
-static void zero_var(NumericVar *var);
-
static bool set_var_from_str(const char *str, const char *cp,
NumericVar *dest, const char **endptr,
Node *escontext);
@@ -506,9 +372,6 @@ static bool set_var_from_non_decimal_integer_str(const char *str,
int base, NumericVar *dest,
const char **endptr,
Node *escontext);
-static void set_var_from_num(Numeric num, NumericVar *dest);
-static void init_var_from_num(Numeric num, NumericVar *dest);
-static void set_var_from_var(const NumericVar *value, NumericVar *dest);
static char *get_str_from_var(const NumericVar *var);
static char *get_str_from_var_sci(const NumericVar *var, int rscale);
@@ -516,8 +379,6 @@ static void numericvar_serialize(StringInfo buf, const NumericVar *var);
static void numericvar_deserialize(StringInfo buf, NumericVar *var);
static Numeric duplicate_numeric(Numeric num);
-static Numeric make_result(const NumericVar *var);
-static Numeric make_result_safe(const NumericVar *var, Node *escontext);
static bool apply_typmod(NumericVar *var, int32 typmod, Node *escontext);
static bool apply_typmod_special(Numeric num, int32 typmod, Node *escontext);
@@ -676,17 +537,17 @@ numeric_in(PG_FUNCTION_ARGS)
*/
if (pg_strncasecmp(numstart, "NaN", 3) == 0)
{
- res = make_result(&const_nan);
+ res = numeric_make_result(&const_nan);
cp = numstart + 3;
}
else if (pg_strncasecmp(cp, "Infinity", 8) == 0)
{
- res = make_result(sign == NUMERIC_POS ? &const_pinf : &const_ninf);
+ res = numeric_make_result(sign == NUMERIC_POS ? &const_pinf : &const_ninf);
cp += 8;
}
else if (pg_strncasecmp(cp, "inf", 3) == 0)
{
- res = make_result(sign == NUMERIC_POS ? &const_pinf : &const_ninf);
+ res = numeric_make_result(sign == NUMERIC_POS ? &const_pinf : &const_ninf);
cp += 3;
}
else
@@ -775,7 +636,7 @@ numeric_in(PG_FUNCTION_ARGS)
if (!apply_typmod(&value, typmod, escontext))
PG_RETURN_NULL();
- res = make_result_safe(&value, escontext);
+ res = numeric_make_result_safe(&value, escontext);
free_var(&value);
}
@@ -1113,7 +974,7 @@ numeric_recv(PG_FUNCTION_ARGS)
* extra code (about as much as trunc_var involves), and it might cause
* client compatibility issues. Be careful not to apply trunc_var to
* special values, as it could do the wrong thing; we don't need it
- * anyway, since make_result will ignore all but the sign field.
+ * anyway, since numeric_make_result will ignore all but the sign field.
*
* After doing that, be sure to check the typmod restriction.
*/
@@ -1124,12 +985,12 @@ numeric_recv(PG_FUNCTION_ARGS)
(void) apply_typmod(&value, typmod, NULL);
- res = make_result(&value);
+ res = numeric_make_result(&value);
}
else
{
/* apply_typmod_special wants us to make the Numeric first */
- res = make_result(&value);
+ res = numeric_make_result(&value);
(void) apply_typmod_special(res, typmod, NULL);
}
@@ -1296,7 +1157,7 @@ numeric (PG_FUNCTION_ARGS)
set_var_from_num(num, &var);
(void) apply_typmod(&var, typmod, NULL);
- new = make_result(&var);
+ new = numeric_make_result(&var);
free_var(&var);
@@ -1498,16 +1359,16 @@ numeric_sign(PG_FUNCTION_ARGS)
* Handle NaN (infinities can be handled normally)
*/
if (NUMERIC_IS_NAN(num))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
switch (numeric_sign_internal(num))
{
case 0:
- PG_RETURN_NUMERIC(make_result(&const_zero));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_zero));
case 1:
- PG_RETURN_NUMERIC(make_result(&const_one));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_one));
case -1:
- PG_RETURN_NUMERIC(make_result(&const_minus_one));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_minus_one));
}
Assert(false);
@@ -1562,7 +1423,7 @@ numeric_round(PG_FUNCTION_ARGS)
/*
* Return the rounded result
*/
- res = make_result(&arg);
+ res = numeric_make_result(&arg);
free_var(&arg);
PG_RETURN_NUMERIC(res);
@@ -1614,7 +1475,7 @@ numeric_trunc(PG_FUNCTION_ARGS)
/*
* Return the truncated result
*/
- res = make_result(&arg);
+ res = numeric_make_result(&arg);
free_var(&arg);
PG_RETURN_NUMERIC(res);
@@ -1642,7 +1503,7 @@ numeric_ceil(PG_FUNCTION_ARGS)
init_var_from_num(num, &result);
ceil_var(&result, &result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
@@ -1670,7 +1531,7 @@ numeric_floor(PG_FUNCTION_ARGS)
init_var_from_num(num, &result);
floor_var(&result, &result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
@@ -1794,7 +1655,7 @@ generate_series_step_numeric(PG_FUNCTION_ARGS)
(fctx->step.sign == NUMERIC_NEG &&
cmp_var(&fctx->current, &fctx->stop) >= 0))
{
- Numeric result = make_result(&fctx->current);
+ Numeric result = numeric_make_result(&fctx->current);
/* switch to memory context appropriate for iteration calculation */
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
@@ -2892,26 +2753,26 @@ numeric_add_safe(Numeric num1, Numeric num2, Node *escontext)
if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
{
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- return make_result(&const_nan);
+ return numeric_make_result(&const_nan);
if (NUMERIC_IS_PINF(num1))
{
if (NUMERIC_IS_NINF(num2))
- return make_result(&const_nan); /* Inf + -Inf */
+ return numeric_make_result(&const_nan); /* Inf + -Inf */
else
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
}
if (NUMERIC_IS_NINF(num1))
{
if (NUMERIC_IS_PINF(num2))
- return make_result(&const_nan); /* -Inf + Inf */
+ return numeric_make_result(&const_nan); /* -Inf + Inf */
else
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
}
/* by here, num1 must be finite, so num2 is not */
if (NUMERIC_IS_PINF(num2))
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
Assert(NUMERIC_IS_NINF(num2));
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
}
/*
@@ -2923,7 +2784,7 @@ numeric_add_safe(Numeric num1, Numeric num2, Node *escontext)
init_var(&result);
add_var(&arg1, &arg2, &result);
- res = make_result_safe(&result, escontext);
+ res = numeric_make_result_safe(&result, escontext);
free_var(&result);
@@ -2968,26 +2829,26 @@ numeric_sub_safe(Numeric num1, Numeric num2, Node *escontext)
if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
{
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- return make_result(&const_nan);
+ return numeric_make_result(&const_nan);
if (NUMERIC_IS_PINF(num1))
{
if (NUMERIC_IS_PINF(num2))
- return make_result(&const_nan); /* Inf - Inf */
+ return numeric_make_result(&const_nan); /* Inf - Inf */
else
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
}
if (NUMERIC_IS_NINF(num1))
{
if (NUMERIC_IS_NINF(num2))
- return make_result(&const_nan); /* -Inf - -Inf */
+ return numeric_make_result(&const_nan); /* -Inf - -Inf */
else
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
}
/* by here, num1 must be finite, so num2 is not */
if (NUMERIC_IS_PINF(num2))
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
Assert(NUMERIC_IS_NINF(num2));
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
}
/*
@@ -2999,7 +2860,7 @@ numeric_sub_safe(Numeric num1, Numeric num2, Node *escontext)
init_var(&result);
sub_var(&arg1, &arg2, &result);
- res = make_result_safe(&result, escontext);
+ res = numeric_make_result_safe(&result, escontext);
free_var(&result);
@@ -3044,17 +2905,17 @@ numeric_mul_safe(Numeric num1, Numeric num2, Node *escontext)
if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
{
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- return make_result(&const_nan);
+ return numeric_make_result(&const_nan);
if (NUMERIC_IS_PINF(num1))
{
switch (numeric_sign_internal(num2))
{
case 0:
- return make_result(&const_nan); /* Inf * 0 */
+ return numeric_make_result(&const_nan); /* Inf * 0 */
case 1:
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
case -1:
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
}
Assert(false);
}
@@ -3063,11 +2924,11 @@ numeric_mul_safe(Numeric num1, Numeric num2, Node *escontext)
switch (numeric_sign_internal(num2))
{
case 0:
- return make_result(&const_nan); /* -Inf * 0 */
+ return numeric_make_result(&const_nan); /* -Inf * 0 */
case 1:
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
case -1:
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
}
Assert(false);
}
@@ -3077,11 +2938,11 @@ numeric_mul_safe(Numeric num1, Numeric num2, Node *escontext)
switch (numeric_sign_internal(num1))
{
case 0:
- return make_result(&const_nan); /* 0 * Inf */
+ return numeric_make_result(&const_nan); /* 0 * Inf */
case 1:
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
case -1:
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
}
Assert(false);
}
@@ -3089,11 +2950,11 @@ numeric_mul_safe(Numeric num1, Numeric num2, Node *escontext)
switch (numeric_sign_internal(num1))
{
case 0:
- return make_result(&const_nan); /* 0 * -Inf */
+ return numeric_make_result(&const_nan); /* 0 * -Inf */
case 1:
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
case -1:
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
}
Assert(false);
}
@@ -3118,7 +2979,7 @@ numeric_mul_safe(Numeric num1, Numeric num2, Node *escontext)
if (result.dscale > NUMERIC_DSCALE_MAX)
round_var(&result, NUMERIC_DSCALE_MAX);
- res = make_result_safe(&result, escontext);
+ res = numeric_make_result_safe(&result, escontext);
free_var(&result);
@@ -3164,34 +3025,34 @@ numeric_div_safe(Numeric num1, Numeric num2, Node *escontext)
if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
{
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- return make_result(&const_nan);
+ return numeric_make_result(&const_nan);
if (NUMERIC_IS_PINF(num1))
{
if (NUMERIC_IS_SPECIAL(num2))
- return make_result(&const_nan); /* Inf / [-]Inf */
+ return numeric_make_result(&const_nan); /* Inf / [-]Inf */
switch (numeric_sign_internal(num2))
{
case 0:
goto division_by_zero;
case 1:
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
case -1:
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
}
Assert(false);
}
if (NUMERIC_IS_NINF(num1))
{
if (NUMERIC_IS_SPECIAL(num2))
- return make_result(&const_nan); /* -Inf / [-]Inf */
+ return numeric_make_result(&const_nan); /* -Inf / [-]Inf */
switch (numeric_sign_internal(num2))
{
case 0:
goto division_by_zero;
case 1:
- return make_result(&const_ninf);
+ return numeric_make_result(&const_ninf);
case -1:
- return make_result(&const_pinf);
+ return numeric_make_result(&const_pinf);
}
Assert(false);
}
@@ -3202,7 +3063,7 @@ numeric_div_safe(Numeric num1, Numeric num2, Node *escontext)
* otherwise throw an underflow error. But the numeric type doesn't
* really do underflow, so let's just return zero.
*/
- return make_result(&const_zero);
+ return numeric_make_result(&const_zero);
}
/*
@@ -3227,7 +3088,7 @@ numeric_div_safe(Numeric num1, Numeric num2, Node *escontext)
*/
div_var(&arg1, &arg2, &result, rscale, true, true);
- res = make_result_safe(&result, escontext);
+ res = numeric_make_result_safe(&result, escontext);
free_var(&result);
@@ -3261,11 +3122,11 @@ numeric_div_trunc(PG_FUNCTION_ARGS)
if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
{
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
if (NUMERIC_IS_PINF(num1))
{
if (NUMERIC_IS_SPECIAL(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan)); /* Inf / [-]Inf */
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan)); /* Inf / [-]Inf */
switch (numeric_sign_internal(num2))
{
case 0:
@@ -3274,16 +3135,16 @@ numeric_div_trunc(PG_FUNCTION_ARGS)
errmsg("division by zero")));
break;
case 1:
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
case -1:
- PG_RETURN_NUMERIC(make_result(&const_ninf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_ninf));
}
Assert(false);
}
if (NUMERIC_IS_NINF(num1))
{
if (NUMERIC_IS_SPECIAL(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan)); /* -Inf / [-]Inf */
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan)); /* -Inf / [-]Inf */
switch (numeric_sign_internal(num2))
{
case 0:
@@ -3292,9 +3153,9 @@ numeric_div_trunc(PG_FUNCTION_ARGS)
errmsg("division by zero")));
break;
case 1:
- PG_RETURN_NUMERIC(make_result(&const_ninf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_ninf));
case -1:
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
}
Assert(false);
}
@@ -3305,7 +3166,7 @@ numeric_div_trunc(PG_FUNCTION_ARGS)
* otherwise throw an underflow error. But the numeric type doesn't
* really do underflow, so let's just return zero.
*/
- PG_RETURN_NUMERIC(make_result(&const_zero));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_zero));
}
/*
@@ -3321,7 +3182,7 @@ numeric_div_trunc(PG_FUNCTION_ARGS)
*/
div_var(&arg1, &arg2, &result, 0, false, true);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -3368,14 +3229,14 @@ numeric_mod_safe(Numeric num1, Numeric num2, Node *escontext)
if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
{
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- return make_result(&const_nan);
+ return numeric_make_result(&const_nan);
if (NUMERIC_IS_INF(num1))
{
if (numeric_sign_internal(num2) == 0)
goto division_by_zero;
/* Inf % any nonzero = NaN */
- return make_result(&const_nan);
+ return numeric_make_result(&const_nan);
}
/* num2 must be [-]Inf; result is num1 regardless of sign of num2 */
return duplicate_numeric(num1);
@@ -3392,7 +3253,7 @@ numeric_mod_safe(Numeric num1, Numeric num2, Node *escontext)
mod_var(&arg1, &arg2, &result);
- res = make_result_safe(&result, escontext);
+ res = numeric_make_result_safe(&result, escontext);
free_var(&result);
@@ -3430,7 +3291,7 @@ numeric_inc(PG_FUNCTION_ARGS)
add_var(&arg, &const_one, &arg);
- res = make_result(&arg);
+ res = numeric_make_result(&arg);
free_var(&arg);
@@ -3509,7 +3370,7 @@ numeric_gcd(PG_FUNCTION_ARGS)
* cases.
*/
if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
/*
* Unpack the arguments
@@ -3524,7 +3385,7 @@ numeric_gcd(PG_FUNCTION_ARGS)
*/
gcd_var(&arg1, &arg2, &result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -3552,7 +3413,7 @@ numeric_lcm(PG_FUNCTION_ARGS)
* cases.
*/
if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
/*
* Unpack the arguments
@@ -3584,7 +3445,7 @@ numeric_lcm(PG_FUNCTION_ARGS)
result.dscale = Max(arg1.dscale, arg2.dscale);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -3611,7 +3472,7 @@ numeric_fac(PG_FUNCTION_ARGS)
errmsg("factorial of a negative number is undefined")));
if (num <= 1)
{
- res = make_result(&const_one);
+ res = numeric_make_result(&const_one);
PG_RETURN_NUMERIC(res);
}
/* Fail immediately if the result would overflow */
@@ -3635,7 +3496,7 @@ numeric_fac(PG_FUNCTION_ARGS)
mul_var(&result, &fact, &result, 0);
}
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&fact);
free_var(&result);
@@ -3708,7 +3569,7 @@ numeric_sqrt(PG_FUNCTION_ARGS)
*/
sqrt_var(&arg, &result, rscale);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -3738,7 +3599,7 @@ numeric_exp(PG_FUNCTION_ARGS)
{
/* Per POSIX, exp(-Inf) is zero */
if (NUMERIC_IS_NINF(num))
- PG_RETURN_NUMERIC(make_result(&const_zero));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_zero));
/* For NAN or PINF, just duplicate the input */
PG_RETURN_NUMERIC(duplicate_numeric(num));
}
@@ -3775,7 +3636,7 @@ numeric_exp(PG_FUNCTION_ARGS)
*/
exp_var(&arg, &result, rscale);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -3824,7 +3685,7 @@ numeric_ln(PG_FUNCTION_ARGS)
ln_var(&arg, &result, rscale);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -3856,7 +3717,7 @@ numeric_log(PG_FUNCTION_ARGS)
sign2;
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
/* fail on negative inputs including -Inf, as log_var would */
sign1 = numeric_sign_internal(num1);
sign2 = numeric_sign_internal(num2);
@@ -3873,13 +3734,13 @@ numeric_log(PG_FUNCTION_ARGS)
{
/* log(Inf, Inf) reduces to Inf/Inf, so it's NaN */
if (NUMERIC_IS_PINF(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
/* log(Inf, finite-positive) is zero (we don't throw underflow) */
- PG_RETURN_NUMERIC(make_result(&const_zero));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_zero));
}
Assert(NUMERIC_IS_PINF(num2));
/* log(finite-positive, Inf) is Inf */
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
}
/*
@@ -3895,7 +3756,7 @@ numeric_log(PG_FUNCTION_ARGS)
*/
log_var(&arg1, &arg2, &result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -3936,9 +3797,9 @@ numeric_power(PG_FUNCTION_ARGS)
{
init_var_from_num(num2, &arg2);
if (cmp_var(&arg2, &const_zero) == 0)
- PG_RETURN_NUMERIC(make_result(&const_one));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_one));
}
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
}
if (NUMERIC_IS_NAN(num2))
{
@@ -3946,9 +3807,9 @@ numeric_power(PG_FUNCTION_ARGS)
{
init_var_from_num(num1, &arg1);
if (cmp_var(&arg1, &const_one) == 0)
- PG_RETURN_NUMERIC(make_result(&const_one));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_one));
}
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
}
/* At least one input is infinite, but error rules still apply */
sign1 = numeric_sign_internal(num1);
@@ -3971,14 +3832,14 @@ numeric_power(PG_FUNCTION_ARGS)
{
init_var_from_num(num1, &arg1);
if (cmp_var(&arg1, &const_one) == 0)
- PG_RETURN_NUMERIC(make_result(&const_one));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_one));
}
/*
* For any value of x, if y is [-]0, 1.0 shall be returned.
*/
if (sign2 == 0)
- PG_RETURN_NUMERIC(make_result(&const_one));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_one));
/*
* For any odd integer value of y > 0, if x is [-]0, [-]0 shall be
@@ -3987,7 +3848,7 @@ numeric_power(PG_FUNCTION_ARGS)
* distinguish these two cases.)
*/
if (sign1 == 0 && sign2 > 0)
- PG_RETURN_NUMERIC(make_result(&const_zero));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_zero));
/*
* If x is -1, and y is [-]Inf, 1.0 shall be returned.
@@ -4010,14 +3871,14 @@ numeric_power(PG_FUNCTION_ARGS)
{
init_var_from_num(num1, &arg1);
if (cmp_var(&arg1, &const_minus_one) == 0)
- PG_RETURN_NUMERIC(make_result(&const_one));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_one));
arg1.sign = NUMERIC_POS; /* now arg1 = abs(x) */
abs_x_gt_one = (cmp_var(&arg1, &const_one) > 0);
}
if (abs_x_gt_one == (sign2 > 0))
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
else
- PG_RETURN_NUMERIC(make_result(&const_zero));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_zero));
}
/*
@@ -4028,9 +3889,9 @@ numeric_power(PG_FUNCTION_ARGS)
if (NUMERIC_IS_PINF(num1))
{
if (sign2 > 0)
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
else
- PG_RETURN_NUMERIC(make_result(&const_zero));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_zero));
}
Assert(NUMERIC_IS_NINF(num1));
@@ -4041,7 +3902,7 @@ numeric_power(PG_FUNCTION_ARGS)
* (Again, we need not distinguish these two cases.)
*/
if (sign2 < 0)
- PG_RETURN_NUMERIC(make_result(&const_zero));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_zero));
/*
* For y an odd integer > 0, if x is -Inf, -Inf shall be returned. For
@@ -4050,9 +3911,9 @@ numeric_power(PG_FUNCTION_ARGS)
init_var_from_num(num2, &arg2);
if (arg2.ndigits > 0 && arg2.ndigits == arg2.weight + 1 &&
(arg2.digits[arg2.ndigits - 1] & 1))
- PG_RETURN_NUMERIC(make_result(&const_ninf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_ninf));
else
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
}
/*
@@ -4083,7 +3944,7 @@ numeric_power(PG_FUNCTION_ARGS)
*/
power_var(&arg1, &arg2, &result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -4192,7 +4053,7 @@ numeric_trim_scale(PG_FUNCTION_ARGS)
init_var_from_num(num, &result);
result.dscale = get_min_scale(&result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
@@ -4241,7 +4102,7 @@ random_numeric(pg_prng_state *state, Numeric rmin, Numeric rmax)
random_var(state, &rmin_var, &rmax_var, &result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -4266,7 +4127,7 @@ int64_to_numeric(int64 val)
int64_to_numericvar(val, &result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -4343,7 +4204,7 @@ int64_div_fast_to_numeric(int64 val1, int log10val2)
result.weight -= w;
result.dscale = rscale;
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -4528,14 +4389,14 @@ float8_numeric(PG_FUNCTION_ARGS)
const char *endptr;
if (isnan(val))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
if (isinf(val))
{
if (val < 0)
- PG_RETURN_NUMERIC(make_result(&const_ninf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_ninf));
else
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
}
snprintf(buf, sizeof(buf), "%.*g", DBL_DIG, val);
@@ -4545,7 +4406,7 @@ float8_numeric(PG_FUNCTION_ARGS)
/* Assume we need not worry about leading/trailing spaces */
(void) set_var_from_str(buf, buf, &result, &endptr, NULL);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -4622,14 +4483,14 @@ float4_numeric(PG_FUNCTION_ARGS)
const char *endptr;
if (isnan(val))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
if (isinf(val))
{
if (val < 0)
- PG_RETURN_NUMERIC(make_result(&const_ninf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_ninf));
else
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
}
snprintf(buf, sizeof(buf), "%.*g", FLT_DIG, val);
@@ -4639,7 +4500,7 @@ float4_numeric(PG_FUNCTION_ARGS)
/* Assume we need not worry about leading/trailing spaces */
(void) set_var_from_str(buf, buf, &result, &endptr, NULL);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -5882,7 +5743,7 @@ numeric_poly_sum(PG_FUNCTION_ARGS)
int128_to_numericvar(state->sumX, &result);
- res = make_result(&result);
+ res = numeric_make_result(&result);
free_var(&result);
@@ -5908,7 +5769,7 @@ numeric_poly_avg(PG_FUNCTION_ARGS)
int128_to_numericvar(state->sumX, &result);
countd = NumericGetDatum(int64_to_numeric(state->N));
- sumd = NumericGetDatum(make_result(&result));
+ sumd = NumericGetDatum(numeric_make_result(&result));
free_var(&result);
@@ -5930,21 +5791,21 @@ numeric_avg(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (state->NaNcount > 0) /* there was at least one NaN input */
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
/* adding plus and minus infinities gives NaN */
if (state->pInfcount > 0 && state->nInfcount > 0)
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
if (state->pInfcount > 0)
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
if (state->nInfcount > 0)
- PG_RETURN_NUMERIC(make_result(&const_ninf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_ninf));
N_datum = NumericGetDatum(int64_to_numeric(state->N));
init_var(&sumX_var);
accum_sum_final(&state->sumX, &sumX_var);
- sumX_datum = NumericGetDatum(make_result(&sumX_var));
+ sumX_datum = NumericGetDatum(numeric_make_result(&sumX_var));
free_var(&sumX_var);
PG_RETURN_DATUM(DirectFunctionCall2(numeric_div, sumX_datum, N_datum));
@@ -5964,19 +5825,19 @@ numeric_sum(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (state->NaNcount > 0) /* there was at least one NaN input */
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
/* adding plus and minus infinities gives NaN */
if (state->pInfcount > 0 && state->nInfcount > 0)
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_nan));
if (state->pInfcount > 0)
- PG_RETURN_NUMERIC(make_result(&const_pinf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_pinf));
if (state->nInfcount > 0)
- PG_RETURN_NUMERIC(make_result(&const_ninf));
+ PG_RETURN_NUMERIC(numeric_make_result(&const_ninf));
init_var(&sumX_var);
accum_sum_final(&state->sumX, &sumX_var);
- result = make_result(&sumX_var);
+ result = numeric_make_result(&sumX_var);
free_var(&sumX_var);
PG_RETURN_NUMERIC(result);
@@ -6030,7 +5891,7 @@ numeric_stddev_internal(NumericAggState *state,
* float8 functions, any infinity input produces NaN output.
*/
if (state->NaNcount > 0 || state->pInfcount > 0 || state->nInfcount > 0)
- return make_result(&const_nan);
+ return numeric_make_result(&const_nan);
/* OK, normal calculation applies */
init_var(&vN);
@@ -6054,7 +5915,7 @@ numeric_stddev_internal(NumericAggState *state,
if (cmp_var(&vsumX2, &const_zero) <= 0)
{
/* Watch out for roundoff error producing a negative numerator */
- res = make_result(&const_zero);
+ res = numeric_make_result(&const_zero);
}
else
{
@@ -6067,7 +5928,7 @@ numeric_stddev_internal(NumericAggState *state,
if (!variance)
sqrt_var(&vsumX, &vsumX, rscale); /* stddev */
- res = make_result(&vsumX);
+ res = numeric_make_result(&vsumX);
}
free_var(&vNminus1);
@@ -6680,7 +6541,7 @@ dump_var(const char *str, NumericVar *var)
*
* Allocate a digit buffer of ndigits digits (plus a spare digit for rounding)
*/
-static void
+void
alloc_var(NumericVar *var, int ndigits)
{
digitbuf_free(var->buf);
@@ -6696,7 +6557,7 @@ alloc_var(NumericVar *var, int ndigits)
*
* Return the digit buffer of a variable to the free pool
*/
-static void
+void
free_var(NumericVar *var)
{
digitbuf_free(var->buf);
@@ -6712,7 +6573,7 @@ free_var(NumericVar *var)
* Set a variable to ZERO.
* Note: its dscale is not touched.
*/
-static void
+void
zero_var(NumericVar *var)
{
digitbuf_free(var->buf);
@@ -6834,8 +6695,8 @@ set_var_from_str(const char *str, const char *cp,
* INT_MAX/2 due to the MaxAllocSize limit on string length, so
* constraining the exponent similarly should be enough to prevent
* integer overflow in this function. If the value is too large to
- * fit in storage format, make_result() will complain about it later;
- * for consistency use the same ereport errcode/text as make_result().
+ * fit in storage format, numeric_make_result() will complain about it later;
+ * for consistency use the same ereport errcode/text as numeric_make_result().
*/
/* exponent sign */
@@ -7147,7 +7008,7 @@ invalid_syntax:
*
* Convert the packed db format into a variable
*/
-static void
+void
set_var_from_num(Numeric num, NumericVar *dest)
{
int ndigits;
@@ -7178,7 +7039,7 @@ set_var_from_num(Numeric num, NumericVar *dest)
* propagate to the original Numeric! It's OK to use it as the destination
* argument of one of the calculational functions, though.
*/
-static void
+void
init_var_from_num(Numeric num, NumericVar *dest)
{
dest->ndigits = NUMERIC_NDIGITS(num);
@@ -7195,7 +7056,7 @@ init_var_from_num(Numeric num, NumericVar *dest)
*
* Copy one variable into another
*/
-static void
+void
set_var_from_var(const NumericVar *value, NumericVar *dest)
{
NumericDigit *newbuf;
@@ -7501,13 +7362,13 @@ duplicate_numeric(Numeric num)
}
/*
- * make_result_safe() -
+ * numeric_make_result_safe() -
*
* Create the packed db numeric format in palloc()'d memory from
* a variable. This will handle NaN and Infinity cases.
*/
-static Numeric
-make_result_safe(const NumericVar *var, Node *escontext)
+Numeric
+numeric_make_result_safe(const NumericVar *var, Node *escontext)
{
Numeric result;
NumericDigit *digits = var->digits;
@@ -7534,7 +7395,7 @@ make_result_safe(const NumericVar *var, Node *escontext)
result->choice.n_header = sign;
/* the header word is all we need */
- dump_numeric("make_result()", result);
+ dump_numeric("numeric_make_result()", result);
return result;
}
@@ -7592,20 +7453,20 @@ make_result_safe(const NumericVar *var, Node *escontext)
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value overflows numeric format")));
- dump_numeric("make_result()", result);
+ dump_numeric("numeric_make_result()", result);
return result;
}
/*
- * make_result() -
+ * numeric_make_result() -
*
- * An interface to make_result_safe() without "escontext" argument.
+ * An interface to numeric_make_result_safe() without "escontext" argument.
*/
-static Numeric
-make_result(const NumericVar *var)
+Numeric
+numeric_make_result(const NumericVar *var)
{
- return make_result_safe(var, NULL);
+ return numeric_make_result_safe(var, NULL);
}
diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h
index 215f1ea4f53..5caa8438cdb 100644
--- a/src/include/utils/numeric.h
+++ b/src/include/utils/numeric.h
@@ -45,6 +45,59 @@ typedef struct Node Node;
#define NUMERIC_MAX_RESULT_SCALE (NUMERIC_MAX_PRECISION * 2)
+/* ----------
+ * Local data types
+ *
+ * Numeric values are represented in a base-NBASE floating point format.
+ * Each "digit" ranges from 0 to NBASE-1. The type NumericDigit is signed
+ * and wide enough to store a digit. We assume that NBASE*NBASE can fit in
+ * an int. Although the purely calculational routines could handle any even
+ * NBASE that's less than sqrt(INT_MAX), in practice we are only interested
+ * in NBASE a power of ten, so that I/O conversions and decimal rounding
+ * are easy. Also, it's actually more efficient if NBASE is rather less than
+ * sqrt(INT_MAX), so that there is "headroom" for mul_var and div_var to
+ * postpone processing carries.
+ *
+ * Values of NBASE other than 10000 are considered of historical interest only
+ * and are no longer supported in any sense; no mechanism exists for the client
+ * to discover the base, so every client supporting binary mode expects the
+ * base-10000 format. If you plan to change this, also note the numeric
+ * abbreviation code, which assumes NBASE=10000.
+ * ----------
+ */
+
+#if 0
+#define NBASE 10
+#define HALF_NBASE 5
+#define DEC_DIGITS 1 /* decimal digits per NBASE digit */
+#define MUL_GUARD_DIGITS 4 /* these are measured in NBASE digits */
+#define DIV_GUARD_DIGITS 8
+
+typedef signed char NumericDigit;
+#endif
+
+#if 0
+#define NBASE 100
+#define HALF_NBASE 50
+#define DEC_DIGITS 2 /* decimal digits per NBASE digit */
+#define MUL_GUARD_DIGITS 3 /* these are measured in NBASE digits */
+#define DIV_GUARD_DIGITS 6
+
+typedef signed char NumericDigit;
+#endif
+
+#if 1
+#define NBASE 10000
+#define HALF_NBASE 5000
+#define DEC_DIGITS 4 /* decimal digits per NBASE digit */
+#define MUL_GUARD_DIGITS 2 /* these are measured in NBASE digits */
+#define DIV_GUARD_DIGITS 4
+
+typedef int16 NumericDigit;
+#endif
+
+#define NBASE_SQR (NBASE * NBASE)
+
/*
* For inherently inexact calculations such as division and square root,
* we try to get at least this many significant digits; the idea is to
@@ -52,6 +105,84 @@ typedef struct Node Node;
*/
#define NUMERIC_MIN_SIG_DIGITS 16
+/*
+ * sign field of NumericVar
+ */
+
+#define NUMERIC_POS 0x0000
+#define NUMERIC_NEG 0x4000
+#define NUMERIC_NAN 0xC000
+#define NUMERIC_PINF 0xD000
+#define NUMERIC_NINF 0xF000
+
+/*
+ * Maximum weight of a stored Numeric value (based on the use of int16 for the
+ * weight in NumericLong). Note that intermediate values held in NumericVar
+ * and NumericSumAccum variables may have much larger weights.
+ */
+#define NUMERIC_WEIGHT_MAX PG_INT16_MAX
+
+/* ----------
+ * NumericVar is the format we use for arithmetic. The digit-array part
+ * is the same as the NumericData storage format, but the header is more
+ * complex.
+ *
+ * The value represented by a NumericVar is determined by the sign, weight,
+ * ndigits, and digits[] array. If it is a "special" value (NaN or Inf)
+ * then only the sign field matters; ndigits should be zero, and the weight
+ * and dscale fields are ignored.
+ *
+ * Note: the first digit of a NumericVar's value is assumed to be multiplied
+ * by NBASE ** weight. Another way to say it is that there are weight+1
+ * digits before the decimal point. It is possible to have weight < 0.
+ *
+ * buf points at the physical start of the palloc'd digit buffer for the
+ * NumericVar. digits points at the first digit in actual use (the one
+ * with the specified weight). We normally leave an unused digit or two
+ * (preset to zeroes) between buf and digits, so that there is room to store
+ * a carry out of the top digit without reallocating space. We just need to
+ * decrement digits (and increment weight) to make room for the carry digit.
+ * (There is no such extra space in a numeric value stored in the database,
+ * only in a NumericVar in memory.)
+ *
+ * If buf is NULL then the digit buffer isn't actually palloc'd and should
+ * not be freed --- see the constants below for an example.
+ *
+ * dscale, or display scale, is the nominal precision expressed as number
+ * of digits after the decimal point (it must always be >= 0 at present).
+ * dscale may be more than the number of physically stored fractional digits,
+ * implying that we have suppressed storage of significant trailing zeroes.
+ * It should never be less than the number of stored digits, since that would
+ * imply hiding digits that are present. NOTE that dscale is always expressed
+ * in *decimal* digits, and so it may correspond to a fractional number of
+ * base-NBASE digits --- divide by DEC_DIGITS to convert to NBASE digits.
+ *
+ * rscale, or result scale, is the target precision for a computation.
+ * Like dscale it is expressed as number of *decimal* digits after the decimal
+ * point, and is always >= 0 at present.
+ * Note that rscale is not stored in variables --- it's figured on-the-fly
+ * from the dscales of the inputs.
+ *
+ * While we consistently use "weight" to refer to the base-NBASE weight of
+ * a numeric value, it is convenient in some scale-related calculations to
+ * make use of the base-10 weight (ie, the approximate log10 of the value).
+ * To avoid confusion, such a decimal-units weight is called a "dweight".
+ *
+ * NB: All the variable-level functions are written in a style that makes it
+ * possible to give one and the same variable as argument and destination.
+ * This is feasible because the digit buffer is separate from the variable.
+ * ----------
+ */
+typedef struct NumericVar
+{
+ int ndigits; /* # of digits in digits[] - can be 0! */
+ int weight; /* weight of first digit */
+ int sign; /* NUMERIC_POS, _NEG, _NAN, _PINF, or _NINF */
+ int dscale; /* display scale */
+ NumericDigit *buf; /* start of palloc'd space for digits[] */
+ NumericDigit *digits; /* base-NBASE digits */
+} NumericVar;
+
/* The actual contents of Numeric are private to numeric.c */
struct NumericData;
typedef struct NumericData *Numeric;
@@ -82,9 +213,22 @@ NumericGetDatum(Numeric X)
#define PG_GETARG_NUMERIC_COPY(n) DatumGetNumericCopy(PG_GETARG_DATUM(n))
#define PG_RETURN_NUMERIC(x) return NumericGetDatum(x)
+#define init_var(v) memset(v, 0, sizeof(NumericVar))
+
/*
* Utility functions in numeric.c
*/
+extern void alloc_var(NumericVar *var, int ndigits);
+extern void free_var(NumericVar *var);
+extern void zero_var(NumericVar *var);
+
+extern void set_var_from_num(Numeric num, NumericVar *dest);
+extern void init_var_from_num(Numeric num, NumericVar *dest);
+extern void set_var_from_var(const NumericVar *value, NumericVar *dest);
+
+extern Numeric numeric_make_result(const NumericVar *var);
+extern Numeric numeric_make_result_safe(const NumericVar *var, Node *escontext);
+
extern bool numeric_is_nan(Numeric num);
extern bool numeric_is_inf(Numeric num);
extern int32 numeric_maximum_size(int32 typmod);
Good morning-
I'm wondering what the status of this patch is. (
https://commitfest.postgresql.org/patch/5623/)
I am, of course, eager to be able to move forward with work on my side
that requires it and would love to see it approved. If there are still any
concerns, please let me know and I can attempt to address them.
Thanks
-Ed
On Fri, Sep 26, 2025 at 5:03 PM Ed Behn <ed@behn.us> wrote:
Show quoted text
Please find attached an updated patch against the current master branch.
-Ed
On Sat, Sep 20, 2025 at 8:11 AM Ed Behn <ed@behn.us> wrote:
Good morning-
It looks like the proposed change associated with this thread has
languished (https://commitfest.postgresql.org/patch/5623/). Is there
anything I can do to get it rolling again?-Ed
On Sat, Mar 1, 2025 at 5:25 PM Ed Behn <ed@behn.us> wrote:
Tom-
I understand that you are concerned about future maintenance costs
vs benefits of this change. I hope that I can address those concerns. An
important thing to note is that there are two different structures that
represent numeric values:
* NumericData is an opaque structure that is defined in numeric.c.
It is this struct that is used to store values. The patch I submitted has
this structure remain opaque and in numeric.c. Its internals are messy and
subject to future changes. I agree that third parties should not have
access to this. Of note is that the type Numeric is a typedef of
NumericData*.
* NumericVar is a user-friendly structure that already exists. It is
this structure that I propose moving to numeric.h. There are functions that
exist to convert it to and from NumericData. It is these functions that I
propose giving access to.What the patch boils down to is the movement of NumericVar to
numeric.h along with function declarations for the basic function to work
with it and a few pre-processor declarations.I agree that there is the potential for future maintenance costs
here. However, any future changes to NumericData would necessitate updating
the code to convert to and from NumericVar regardless of the proposed
changes. I think that this small increase in costs is outweighed by the
benefits of allowing third parties to access this powerful datatype.As for the reason that I would like to make this change: I am the
maintainer of the PL/Haskell extension. (It allows the use of Haskell code
as a procedural language. https://github.com/ed-o-saurus/PLHaskell) In
the extension, users can currently pass several Postgres types and also
have the function return them. This is accomplished by using the functions
and macros that convert between Datums and C data types. (For example
DatumGetFloat8 and Float8GetDatum to handle double-precision floating point
values) I would like to add support for the use of the numeric type to the
extension. To this end, I would need to create a Haskell type that mirrors
the Postgres numeric type. Passed Haskell values would be instantiated by
reading the data from Postgres. Conversely, returned values would be
converted to the Postgres type. Internally, users would be able to perform
mathematical operations with the Haskell values like any other type.
Currently, there is no way for a third-party extension to get needed
information about numeric values or build new numeric values. The proposed
changes would remedy this.An alternative approach would be to make available calls to read and
create numeric data. However, as the NumericVar struct already exists, I
feel that utilizing it is the more natural approach.What do you think?
-EdOn Sat, Mar 1, 2025 at 3:32 PM Ed Behn <ed@behn.us> wrote:
Tom-
I think that I can allay your concerns. Please give me a day or so
to put together my more complete thoughts on the matter. I'll be in touch.-Ed
On Sat, Mar 1, 2025 at 11:33 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Ed Behn <ed@behn.us> writes:
There is actually no new code. Code is simply moved from numeric.c
to
numeric.h.
I will absolutely not hold still for that. It would mean that any
time we want to think about messing with the contents of numerics,
we need to examine more or less the whole Postgres code base to see
what else is poking into those structures.If we must do something like this, then a separate header
"numeric_internal.h" or something like that would reduce the blast
radius for changes. But IMO you still haven't made an acceptable case
for deciding that these data structures aren't private to numeric.c.
What behaviors do you actually need that aren't accessible via the
existing exported functons?regards, tom lane
Hello Hackers,
On Dec 14, 2025, at 09:10, Ed Behn <ed@behn.us> wrote:
I'm wondering what the status of this patch is. (https://commitfest.postgresql.org/patch/5623/)
I am, of course, eager to be able to move forward with work on my side that requires it and would love to see it approved. If there are still any concerns, please let me know and I can attempt to address them.
The patch looks quite straightforward to me; I see Jelte signed on to review it, but I get the impression that it’s probably completely fine as a patch. The question is whether the committers actually want it, and there’s clearly some disagreement there. I find your explanation of how NumericVar is already an abstraction, therefore limiting the challenges of changing the internal representation of NumericData, but I also see that its structure is pretty specific to the Postgres type:
```c
typedef struct NumericVar
{
int ndigits; /* # of digits in digits[] - can be 0! */
int weight; /* weight of first digit */
int sign; /* NUMERIC_POS, _NEG, _NAN, _PINF, or _NINF */
int dscale; /* display scale */
NumericDigit *buf; /* start of palloc'd space for digits[] */
NumericDigit *digits; /* base-NBASE digits */
} NumericVar;
```
That all feels somewhat internal-y to me, but maybe that’s just because I don’t understand it, and would have to study quite a lot to get there.
The problem I’d like to see solved is related. I maintain pg_clickhouse, and wrote code to convert between the ClickHouse Decimal structure and the Postgres Numeric structure. I ended up adding stringification to the ClickHouse C++ library Decimal type[1]https://github.com/ClickHouse/clickhouse-cpp/pull/451 and passing it to numeric_in.
This feels inefficient, and may or may not be, but it would be much easier for this use case --- and, I suspect, other extensions that need to convert between data types, surely a lot of FDWs and PLs --- if there were more constructors for the Numeric type than just parsing the string representation.
Would it make sense to add constructors for some common use cases? I’m thinking something similar to the various timestamp constructors, like time_t_to_timestamptz()[2]https://github.com/postgres/postgres/blob/094b61ce3ebbb1258675cb9b4eca9198628e2177/src/backend/utils/adt/timestamp.c#L1808-L1829. Something like this would help my use case:
Numeric int64_scale_to_numeric(int64 num, size_t scale);
Numeric int128_scale_to_numeric(int64 high_num, int64 low_num, size_t scale);
These would assume that the scale defines where the decimal point goes into the resulting Numeric, e.g.,
int64_scale_to_numeric(2342342334, 4)
Would define the Number 234234.2334.
I don’t know how common this pattern is, however; maybe not so common? If so, then having NumericVar made public, as your patch proposes, would at least allow me to learn how it works and build my own conversion functions.
IOW, I would very much like to see some ways to make it easier to covert to and from Numeric values, or at least to expose the data structures to simplify implementation of such conversions in extensions.
Best,
David
[1]: https://github.com/ClickHouse/clickhouse-cpp/pull/451
[2]: https://github.com/postgres/postgres/blob/094b61ce3ebbb1258675cb9b4eca9198628e2177/src/backend/utils/adt/timestamp.c#L1808-L1829