Merge 'Remainder fixes' from jachewz

This PR should close two related issues:
## 1. Remainder operand with lhs as text
Before:
```
limbo> SELECT 10 % '3';
┌──────────┐
│ 10 % '3' │
├──────────┤
│        3 │
└──────────┘
```
sqlite:
```
sqlite> SELECT 10 % '3';
1
```
After:
```
limbo> SELECT 10 % '3';
┌──────────┐
│ 10 % '3' │
├──────────┤
│        1 │
└──────────┘
```
## Overflow when min int64 % -1
Before:
```
limbo> SELECT -9223372036854775808 % -1;
thread 'main' panicked at core/vdbe/insn.rs:974:37:
attempt to calculate the remainder with overflow
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
sqlite:
```
sqlite> SELECT -9223372036854775808 % -1;
0
```
After:
```
limbo> SELECT -9223372036854775808 % -1;
┌─────────────────────────────┐
│ - 9223372036854775808 % - 1 │
├─────────────────────────────┤
│                           0 │
└─────────────────────────────┘
```
Tests for these cases are also added, and the `%` operator tests in
`math.test` were renamed to `remainder-` instead of `mod-` to
differentiate from tests for the `mod()` function.
Closes #1172

Closes #1267
This commit is contained in:
Pekka Enberg
2025-04-07 17:22:15 +03:00
2 changed files with 52 additions and 18 deletions

View File

@@ -971,7 +971,7 @@ pub fn exec_remainder(lhs: &OwnedValue, rhs: &OwnedValue) -> OwnedValue {
if rhs == &0 {
OwnedValue::Null
} else {
OwnedValue::Integer(lhs % rhs)
OwnedValue::Integer(lhs % rhs.abs())
}
}
(OwnedValue::Float(lhs), OwnedValue::Float(rhs)) => {
@@ -979,14 +979,14 @@ pub fn exec_remainder(lhs: &OwnedValue, rhs: &OwnedValue) -> OwnedValue {
if rhs_int == 0 {
OwnedValue::Null
} else {
OwnedValue::Float(((*lhs as i64) % rhs_int) as f64)
OwnedValue::Float(((*lhs as i64) % rhs_int.abs()) as f64)
}
}
(OwnedValue::Float(lhs), OwnedValue::Integer(rhs)) => {
if rhs == &0 {
OwnedValue::Null
} else {
OwnedValue::Float(((*lhs as i64) % rhs) as f64)
OwnedValue::Float(((*lhs as i64) % rhs.abs()) as f64)
}
}
(OwnedValue::Integer(lhs), OwnedValue::Float(rhs)) => {
@@ -994,16 +994,19 @@ pub fn exec_remainder(lhs: &OwnedValue, rhs: &OwnedValue) -> OwnedValue {
if rhs_int == 0 {
OwnedValue::Null
} else {
OwnedValue::Float((lhs % rhs_int) as f64)
OwnedValue::Float((lhs % rhs_int.abs()) as f64)
}
}
(OwnedValue::Text(lhs), OwnedValue::Text(rhs)) => exec_remainder(
&cast_text_to_numeric(lhs.as_str()),
&cast_text_to_numeric(rhs.as_str()),
),
(OwnedValue::Text(text), other) | (other, OwnedValue::Text(text)) => {
(OwnedValue::Text(text), other) => {
exec_remainder(&cast_text_to_numeric(text.as_str()), other)
}
(other, OwnedValue::Text(text)) => {
exec_remainder(other, &cast_text_to_numeric(text.as_str()))
}
other => todo!("remainder not implemented for: {:?} {:?}", lhs, other),
}
}
@@ -1685,10 +1688,15 @@ mod tests {
(OwnedValue::Float(12.0), OwnedValue::Float(0.0)),
(OwnedValue::Float(12.0), OwnedValue::Integer(0)),
(OwnedValue::Integer(12), OwnedValue::Float(0.0)),
(OwnedValue::Integer(i64::MIN), OwnedValue::Integer(-1)),
(OwnedValue::Integer(12), OwnedValue::Integer(3)),
(OwnedValue::Float(12.0), OwnedValue::Float(3.0)),
(OwnedValue::Float(12.0), OwnedValue::Integer(3)),
(OwnedValue::Integer(12), OwnedValue::Float(3.0)),
(OwnedValue::Integer(12), OwnedValue::Integer(-3)),
(OwnedValue::Float(12.0), OwnedValue::Float(-3.0)),
(OwnedValue::Float(12.0), OwnedValue::Integer(-3)),
(OwnedValue::Integer(12), OwnedValue::Float(-3.0)),
(
OwnedValue::Text(Text::from_str("12.0")),
OwnedValue::Text(Text::from_str("3.0")),
@@ -1699,7 +1707,7 @@ mod tests {
),
(
OwnedValue::Float(12.0),
OwnedValue::Text(Text::from_str("12.0")),
OwnedValue::Text(Text::from_str("3.0")),
),
];
let outputs = vec![
@@ -1713,6 +1721,11 @@ mod tests {
OwnedValue::Null,
OwnedValue::Null,
OwnedValue::Null,
OwnedValue::Float(0.0),
OwnedValue::Integer(0),
OwnedValue::Float(0.0),
OwnedValue::Float(0.0),
OwnedValue::Float(0.0),
OwnedValue::Integer(0),
OwnedValue::Float(0.0),
OwnedValue::Float(0.0),

View File

@@ -1309,54 +1309,75 @@ do_execsql_test log-int-null {
SELECT log(5, null)
} {}
do_execsql_test mod-int-null {
do_execsql_test remainder-int-null {
SELECT 183 % null
} {}
do_execsql_test mod-int-0 {
do_execsql_test remainder-int-0 {
SELECT 183 % 0
} {}
do_execsql_test mod-int-int {
do_execsql_test remainder-int-int {
SELECT 183 % 10
} { 3 }
do_execsql_test mod-int-float {
do_execsql_test remainder-int-float {
SELECT 38 % 10.35
} { 8.0 }
do_execsql_test mod-float-int {
do_execsql_test remainder-float-int {
SELECT 38.43 % 13
} { 12.0 }
do_execsql_test mod-0-float {
do_execsql_test remainder-0-float {
SELECT 0 % 12.0
} { 0.0 }
do_execsql_test mod-float-0 {
do_execsql_test remainder-float-0 {
SELECT 23.14 % 0
} {}
do_execsql_test mod-float-float {
do_execsql_test remainder-float-float {
SELECT 23.14 % 12.0
} { 11.0 }
do_execsql_test mod-float-agg {
do_execsql_test remainder-float-agg {
SELECT 23.14 % sum(id) from products
} { 23.0 }
do_execsql_test mod-int-agg {
do_execsql_test remainder-int-agg {
SELECT 17 % sum(id) from users
} { 17 }
do_execsql_test mod-agg-int {
do_execsql_test remainder-agg-int {
SELECT count(*) % 17 from users
} { 4 }
do_execsql_test mod-agg-float {
do_execsql_test remainder-agg-float {
SELECT count(*) % 2.43 from users
} { 0.0 }
foreach {testnum lhs rhs ans} {
1 'a' 'a' {}
2 'a' 10 0
3 10 'a' {}
4 'a' 11.0 0.0
5 11.0 'a' {}
7 '10' '3' 1
8 '10.0' '3' 1.0
9 '10.0' -3 1.0
} {
do_execsql_test remainder-text-$testnum "SELECT $lhs % $rhs" $::ans
}
foreach {testnum lhs rhs ans} {
1 '-9223372036854775808' '-1' 0
2 -9223372036854775808 -1 0
3 -9223372036854775809 -1 0.0
} {
do_execsql_test remainder-overflow-$testnum "SELECT $lhs % $rhs" $::ans
}
do_execsql_test comp-float-float {
SELECT 0.0 = 0.0
} { 1 }