Re: pgsql: Ensure that numeric.c compiles with other NBASE values.

2023-02-03 Thread Dean Rasheed
On Fri, 3 Feb 2023 at 01:18, Tom Lane  wrote:
>
> Dean Rasheed  writes:
>
> > 1). It doesn't work if log10val2 < 0, because then m < 0, and it
> > doesn't multiply by the remainder. And it then throws an overflow
> > error, because result.dscale comes out wrong when m < 0.
>
> I'm not quite sure that it's worth expending code space on the
> log10val2 < 0 case (compared to just "Assert(log10val2 >= 0").
> On the other hand, it's not much extra code, and committing it now
> might save somebody reinventing that logic in future.
>

Yeah, I thought about that, but it's hardly any code to support that
case. Also, this function is out there now (I found an example on
Stack Overflow of someone using it), so we have no control over how
people will use it in their own C code, and so I think it's worth
making it robust across the range of possible inputs.

Regards,
Dean




Re: pgsql: Ensure that numeric.c compiles with other NBASE values.

2023-02-02 Thread Tom Lane
Dean Rasheed  writes:
> However, looking more closely, I think this function is more or less
> completely broken:

> 1). It doesn't work if log10val2 < 0, because then m < 0, and it
> doesn't multiply by the remainder. And it then throws an overflow
> error, because result.dscale comes out wrong when m < 0.

> 2). The result.dscale calculation is wrong if log10val2 is a multiple
> of DEC_DIGITS, causing it to drop the last 4 digits.

> 3). If the scaled-up dividend doesn't fit in an int64, the numeric
> computation breaks if log10val2 >= 10 due to integer overflow.

Ugh.

I'm not quite sure that it's worth expending code space on the
log10val2 < 0 case (compared to just "Assert(log10val2 >= 0").
On the other hand, it's not much extra code, and committing it now
might save somebody reinventing that logic in future.

I've not actually tested the patch, but it looks reasonable
by eyeball.

regards, tom lane




Re: pgsql: Ensure that numeric.c compiles with other NBASE values.

2023-02-02 Thread Dean Rasheed
On Thu, 2 Feb 2023 at 15:15, Tom Lane  wrote:
>
> Looking at this diff made me wonder why the static pow10[] array
> isn't marked "const"?
>

Good point.

However, looking more closely, I think this function is more or less
completely broken:

1). It doesn't work if log10val2 < 0, because then m < 0, and it
doesn't multiply by the remainder. And it then throws an overflow
error, because result.dscale comes out wrong when m < 0.

2). The result.dscale calculation is wrong if log10val2 is a multiple
of DEC_DIGITS, causing it to drop the last 4 digits.

3). If the scaled-up dividend doesn't fit in an int64, the numeric
computation breaks if log10val2 >= 10 due to integer overflow.

So for example:

int64_div_fast_to_numeric(123456, -1) -> ERROR:  value overflows numeric format
int64_div_fast_to_numeric(123456, 0) -> ERROR:  value overflows numeric format
int64_div_fast_to_numeric(123456, 4) -> 12
int64_div_fast_to_numeric(123456, 8) -> 0.0012
int64_div_fast_to_numeric(100, 10) -> 709186959.9285992800

As it happens, none of the above represents a live bug, because we
currently only call it with log10val2 = 3 or 6, but it's definitely a
bug waiting to happen.

This was added by a2da77cdb4 ("Change return type of EXTRACT to
numeric"), so it only goes back as far as v14.

After hacking on it for a while, I ended up with the attached.

Regards,
Dean
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
new file mode 100644
index 08c8416..6bf6db6
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4235,7 +4235,7 @@ int64_to_numeric(int64 val)
 }
 
 /*
- * Convert val1/(10**val2) to numeric.  This is much faster than normal
+ * Convert val1/(10**log10val2) to numeric.  This is much faster than normal
  * numeric division.
  */
 Numeric
@@ -4243,59 +4243,78 @@ int64_div_fast_to_numeric(int64 val1, in
 {
 	Numeric		res;
 	NumericVar	result;
-	int64		saved_val1 = val1;
+	int			rscale;
 	int			w;
 	int			m;
 
+	init_var();
+
+	/* result scale */
+	rscale = log10val2 < 0 ? 0 : log10val2;
+
 	/* how much to decrease the weight by */
 	w = log10val2 / DEC_DIGITS;
-	/* how much is left */
+	/* how much is left to divide by */
 	m = log10val2 % DEC_DIGITS;
+	if (m < 0)
+	{
+		m += DEC_DIGITS;
+		w--;
+	}
 
 	/*
-	 * If there is anything left, multiply the dividend by what's left, then
-	 * shift the weight by one more.
+	 * If there is anything left to divide by (10^m with 0 < m < DEC_DIGITS),
+	 * multiply the dividend by 10^(DEC_DIGITS - m), and shift the weight by
+	 * one more.
 	 */
 	if (m > 0)
 	{
 #if DEC_DIGITS == 4
-		static int	pow10[] = {1, 10, 100, 1000};
+		static const int pow10[] = {1, 10, 100, 1000};
 #elif DEC_DIGITS == 2
-		static int	pow10[] = {1, 10};
+		static const int pow10[] = {1, 10};
 #elif DEC_DIGITS == 1
-		static int	pow10[] = {1};
+		static const int pow10[] = {1};
 #else
 #error unsupported NBASE
 #endif
+		int64		factor = pow10[DEC_DIGITS - m];
+		int64		new_val1;
 
 		StaticAssertDecl(lengthof(pow10) == DEC_DIGITS, "mismatch with DEC_DIGITS");
 
-		if (unlikely(pg_mul_s64_overflow(val1, pow10[DEC_DIGITS - m], )))
+		if (unlikely(pg_mul_s64_overflow(val1, factor, _val1)))
 		{
-			/*
-			 * If it doesn't fit, do the whole computation in numeric the slow
-			 * way.  Note that va1l may have been overwritten, so use
-			 * saved_val1 instead.
-			 */
-			int			val2 = 1;
+#ifdef HAVE_INT128
+			/* do the multiplication using 128-bit integers */
+			int128		tmp;
 
-			for (int i = 0; i < log10val2; i++)
-val2 *= 10;
-			res = numeric_div_opt_error(int64_to_numeric(saved_val1), int64_to_numeric(val2), NULL);
-			res = DatumGetNumeric(DirectFunctionCall2(numeric_round,
-	  NumericGetDatum(res),
-	  Int32GetDatum(log10val2)));
-			return res;
+			tmp = (int128) val1 * (int128) factor;
+
+			int128_to_numericvar(tmp, );
+#else
+			/* do the multiplication using numerics */
+			NumericVar	tmp;
+
+			init_var();
+
+			int64_to_numericvar(val1, );
+			int64_to_numericvar(factor, );
+			mul_var(, , , 0);
+
+			free_var();
+#endif
 		}
+		else
+			int64_to_numericvar(new_val1, );
+
 		w++;
 	}
-
-	init_var();
-
-	int64_to_numericvar(val1, );
+	else
+		int64_to_numericvar(val1, );
 
 	result.weight -= w;
-	result.dscale += w * DEC_DIGITS - (DEC_DIGITS - m);
+	result.dscale = rscale;
 
 	res = make_result();
 


Re: pgsql: Ensure that numeric.c compiles with other NBASE values.

2023-02-02 Thread Tom Lane
Dean Rasheed  writes:
> Ensure that numeric.c compiles with other NBASE values.

Looking at this diff made me wonder why the static pow10[] array
isn't marked "const"?

regards, tom lane




pgsql: Ensure that numeric.c compiles with other NBASE values.

2023-02-02 Thread Dean Rasheed
Ensure that numeric.c compiles with other NBASE values.

As noted in the comments, support for different NBASE values is really
only of historical interest, but as long as we're keeping it, we might
as well make sure that it compiles.

Joel Jacobson

Discussion: 
https://postgr.es/m/06712c29-98e9-43b3-98da-f234d81c6e49%40app.fastmail.com

Branch
--
master

Details
---
https://git.postgresql.org/pg/commitdiff/9a84f2947bf9345ad6b93ba37da63633649eaea8

Modified Files
--
src/backend/utils/adt/numeric.c | 8 
1 file changed, 8 insertions(+)