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]

Reply via email to