From e69ee80fac5eaab156559a5c38464b2606df74ed Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Sun, 15 Dec 2024 22:30:04 +0200 Subject: [PATCH] Support `log(X)` and `log(B,X)` math functions --- COMPAT.md | 4 +-- core/translate/expr.rs | 35 ++++++++++++++++++++++- core/vdbe/mod.rs | 40 +++++++++++++++++++++++++- testing/math.test | 65 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 4 deletions(-) diff --git a/COMPAT.md b/COMPAT.md index 25dae3614..f02c25b66 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -179,8 +179,8 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html). | exp(X) | Yes | | | floor(X) | Yes | | | ln(X) | Yes | | -| log(B,X) | No | | -| log(X) | No | | +| log(B,X) | Yes | | +| log(X) | Yes | | | log10(X) | Yes | | | log2(X) | Yes | | | mod(X,Y) | Yes | | diff --git a/core/translate/expr.rs b/core/translate/expr.rs index c8e66821d..b1db3ac92 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1696,7 +1696,40 @@ pub fn translate_expr( }); Ok(target_register) } - _ => unimplemented!(), + + MathFuncArity::UnaryOrBinary => { + let args = if let Some(args) = args { + if args.len() > 2 { + crate::bail_parse_error!( + "{} function with more than 2 arguments", + math_func + ); + } + args + } else { + crate::bail_parse_error!("{} function with no arguments", math_func); + }; + + let regs = program.alloc_registers(args.len()); + + for (i, arg) in args.iter().enumerate() { + translate_expr( + program, + referenced_tables, + arg, + regs + i, + precomputed_exprs_to_registers, + )?; + } + + program.emit_insn(Insn::Function { + constant_mask: 0, + start_reg: regs, + dest: target_register, + func: func_ctx, + }); + Ok(target_register) + } }, } } diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 796eeff94..547cde53b 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -2509,7 +2509,24 @@ impl Program { state.registers[*dest] = result; } - _ => unimplemented!(), + MathFuncArity::UnaryOrBinary => match math_func { + MathFunc::Log => { + let lhs = &state.registers[*start_reg]; + let rhs = state.registers.get(*start_reg + 1); + + let result = if let Some(arg) = rhs { + exec_math_log(arg, Some(lhs)) + } else { + exec_math_log(lhs, None) + }; + + state.registers[*dest] = result; + } + _ => unreachable!( + "Unexpected mathematical UnaryOrBinary function {:?}", + math_func + ), + }, }, crate::function::Func::Agg(_) => { unreachable!("Aggregate functions should not be handled here") @@ -3699,6 +3716,27 @@ fn exec_math_binary(lhs: &OwnedValue, rhs: &OwnedValue, function: &MathFunc) -> } } +fn exec_math_log(arg: &OwnedValue, base: Option<&OwnedValue>) -> OwnedValue { + let f = match to_f64(arg) { + Some(f) => f, + None => return OwnedValue::Null, + }; + + let base = match base { + Some(base) => match to_f64(base) { + Some(f) => f, + None => return OwnedValue::Null, + }, + None => 10.0, + }; + + if f <= 0.0 || base <= 0.0 || base == 1.0 { + return OwnedValue::Null; + } + + OwnedValue::Float(f.log(base)) +} + #[cfg(test)] mod tests { diff --git a/testing/math.test b/testing/math.test index c75c387e6..064de41e3 100644 --- a/testing/math.test +++ b/testing/math.test @@ -976,3 +976,68 @@ do_execsql_test power-null-int { do_execsql_test power-int-null { SELECT power(5, null) } {} + + +do_execsql_test_tolerance log-int { + SELECT log(1) +} {0.0} $tolerance + +do_execsql_test_tolerance log-float { + SELECT log(1.5) +} {0.176091259055681} $tolerance + +do_execsql_test_tolerance log-str { + SELECT log('1.5') +} {0.176091259055681} $tolerance + +do_execsql_test log-negative { + SELECT log(-1.5) +} {} + +do_execsql_test log-null { + SELECT log(null) +} {} + +do_execsql_test_tolerance log-int-int { + SELECT log(5, 1) +} {0.0} $tolerance + +do_execsql_test_tolerance log-int-float { + SELECT log(5, 1.5) +} {0.251929636412592} $tolerance + +do_execsql_test_tolerance log-int-str { + SELECT log(5, '1.5') +} {0.251929636412592} $tolerance + +do_execsql_test_tolerance log-float-int { + SELECT log(5.5, 10) +} {1.35068935021985} $tolerance + +do_execsql_test_tolerance log-float-float { + SELECT log(5.5, 1.5) +} {0.237844588273313} $tolerance + +do_execsql_test_tolerance log-float-str { + SELECT log(5.5, '1.5') +} {0.237844588273313} $tolerance + +do_execsql_test_tolerance log-str-str { + SELECT log('5.5', '1.5') +} {0.237844588273313} $tolerance + +do_execsql_test log-negative-negative { + SELECT log(-1.5, -1.5) +} {} + +do_execsql_test log-float-negative { + SELECT log(1.5, -1.5) +} {} + +do_execsql_test log-null-int { + SELECT log(null, 5) +} {} + +do_execsql_test log-int-null { + SELECT log(5, null) +} {}