dwsmith1983 opened a new pull request, #60056:
URL: https://github.com/apache/spark/pull/60056
### What changes were proposed in this pull request?
`Decimal.roundToInt`, `roundToShort` and `roundToByte` checked the range of
a BigDecimal-backed value by converting it to a `Double`. This PR makes them
truncate exactly through `BigInteger`, as `roundToLong` already does, and
range-check the truncated integer. Long-backed values keep the existing exact
path.
### Why are the changes needed?
A value that truncates to the bound but is close to the next integer rounds
up past the bound as a double, so the check rejected it even though the
truncated result fits. With ANSI mode on:
```sql
SELECT CAST(CAST('2147483647.99999999' AS DECIMAL(20,8)) AS INT);
-- before: [CAST_OVERFLOW] The value 2147483647.99999999BD of the type
"DECIMAL(20,8)" cannot be cast to "INT" due to an overflow.
-- after: 2147483647
```
`try_cast` returned `NULL` for the same input. The same happens at
`Int.MinValue` and at the `SMALLINT` and `TINYINT` bounds.
Values just below an integer near the bound did not fail but came back one
too high, because the cast converted the rounded double:
```sql
SELECT CAST(CAST('2147483646.99999999' AS DECIMAL(20,8)) AS INT);
-- before: 2147483647
-- after: 2147483646 (same as the non-ANSI cast)
```
A `Decimal` that holds the same number as an unscaled long was already
truncated exactly, so the result depended on how the value happened to be
represented internally.
### Does this PR introduce _any_ user-facing change?
Yes, a bug fix. Under ANSI mode, casting a `DECIMAL` to `INT`, `SMALLINT` or
`TINYINT` no longer fails with `CAST_OVERFLOW` (and `try_cast` no longer
returns `NULL`) when the value truncates to exactly the target type's maximum
or minimum. It returns the truncated value instead. Values just below an
integer near the bound, which used to be rounded up to that integer, are now
truncated toward zero. Values that are out of range still overflow. Non-ANSI
casts are unchanged. The SQL migration guide has a line for it under 4.3 to 4.4.
### How was this patch tested?
New tests, both of which fail without the change:
- `DecimalSuite`: `roundToInt`/`roundToShort`/`roundToByte` on
BigDecimal-backed values just inside each bound return the bound, and values
just below an integer near the bound truncate instead of rounding up, matching
the long-backed representation of the same value; values just outside each
bound, and a 38-digit value, still throw `CAST_OVERFLOW`.
- `CastWithAnsiOnSuite` (also run by `TryCastSuite`): casts of `decimal(20,
s)` literals at the `INT`/`SMALLINT`/`TINYINT` bounds return the bound, and one
unit in the last place beyond each bound fails with `CAST_OVERFLOW` (`NULL`
under `try_cast`), interpreted and codegen.
`DecimalSuite`, `CastWithAnsiOnSuite`, `CastWithAnsiOffSuite`,
`TryCastSuite` and `DecimalExpressionSuite` pass.
### Was this patch authored or co-authored using generative AI tooling?
No
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]