From 9e0a9b5490ea64682686a3f97faaf1cd0a7c7b20 Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Sun, 24 Nov 2024 22:27:26 +0200 Subject: [PATCH 01/12] Add mathematical functions table to COMPAT.md --- COMPAT.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/COMPAT.md b/COMPAT.md index ec9473d48..e65b05c67 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -160,6 +160,41 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html). | upper(X) | Yes | | | zeroblob(N) | Yes | | +### Mathematical functions + +| Function | Status | Comment | +| ---------- | ------ | ------- | +| acos(X) | No | | +| acosh(X) | No | | +| asin(X) | No | | +| asinh(X) | No | | +| atan(X) | No | | +| atan2(Y,X) | No | | +| atanh(X) | No | | +| ceil(X) | No | | +| ceiling(X) | No | | +| cos(X) | No | | +| cosh(X) | No | | +| degrees(X) | No | | +| exp(X) | No | | +| floor(X) | No | | +| ln(X) | No | | +| log(B,X) | No | | +| log(X) | No | | +| log10(X) | No | | +| log2(X) | No | | +| mod(X,Y) | No | | +| pi() | No | | +| pow(X,Y) | No | | +| power(X,Y) | No | | +| radians(X) | No | | +| sin(X) | No | | +| sinh(X) | No | | +| sqrt(X) | No | | +| tan(X) | No | | +| tanh(X) | No | | +| trunc(X) | No | | + ### Aggregate functions | Function | Status | Comment | From 9720f63a55db370fe2da6da2107ba81c7602a513 Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Sun, 24 Nov 2024 23:24:28 +0200 Subject: [PATCH 02/12] Add types for mathematical functions --- core/function.rs | 146 +++++++++++++++++++++++++++++++++++++++++ core/translate/expr.rs | 5 +- core/vdbe/mod.rs | 5 +- 3 files changed, 154 insertions(+), 2 deletions(-) diff --git a/core/function.rs b/core/function.rs index 9e2dca0f3..c4885925f 100644 --- a/core/function.rs +++ b/core/function.rs @@ -139,10 +139,126 @@ impl Display for ScalarFunc { } } +#[derive(Debug, Clone, PartialEq)] +pub enum MathFunc { + Acos, + Acosh, + Asin, + Asinh, + Atan, + Atan2, + Atanh, + Ceil, + Ceiling, + Cos, + Cosh, + Degrees, + Exp, + Floor, + Ln, + Log, + Log10, + Log2, + Mod, + Pi, + Pow, + Power, + Radians, + Sin, + Sinh, + Sqrt, + Tan, + Tanh, + Trunc, +} + +pub enum MathFuncArity { + Nullary, + Unary, + Binary, + UnaryOrBinary, +} + +impl MathFunc { + pub fn arity(&self) -> MathFuncArity { + match self { + MathFunc::Pi => MathFuncArity::Nullary, + + MathFunc::Acos + | MathFunc::Acosh + | MathFunc::Asin + | MathFunc::Asinh + | MathFunc::Atan + | MathFunc::Atanh + | MathFunc::Ceil + | MathFunc::Ceiling + | MathFunc::Cos + | MathFunc::Cosh + | MathFunc::Degrees + | MathFunc::Exp + | MathFunc::Floor + | MathFunc::Ln + | MathFunc::Log10 + | MathFunc::Log2 + | MathFunc::Radians + | MathFunc::Sin + | MathFunc::Sinh + | MathFunc::Sqrt + | MathFunc::Tan + | MathFunc::Tanh + | MathFunc::Trunc => MathFuncArity::Unary, + + MathFunc::Atan2 | MathFunc::Mod | MathFunc::Pow | MathFunc::Power => { + MathFuncArity::Binary + } + + MathFunc::Log => MathFuncArity::UnaryOrBinary, + } + } +} + +impl Display for MathFunc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let str = match self { + MathFunc::Acos => "acos".to_string(), + MathFunc::Acosh => "acosh".to_string(), + MathFunc::Asin => "asin".to_string(), + MathFunc::Asinh => "asinh".to_string(), + MathFunc::Atan => "atan".to_string(), + MathFunc::Atan2 => "atan2".to_string(), + MathFunc::Atanh => "atanh".to_string(), + MathFunc::Ceil => "ceil".to_string(), + MathFunc::Ceiling => "ceiling".to_string(), + MathFunc::Cos => "cos".to_string(), + MathFunc::Cosh => "cosh".to_string(), + MathFunc::Degrees => "degrees".to_string(), + MathFunc::Exp => "exp".to_string(), + MathFunc::Floor => "floor".to_string(), + MathFunc::Ln => "ln".to_string(), + MathFunc::Log => "log".to_string(), + MathFunc::Log10 => "log10".to_string(), + MathFunc::Log2 => "log2".to_string(), + MathFunc::Mod => "mod".to_string(), + MathFunc::Pi => "pi".to_string(), + MathFunc::Pow => "pow".to_string(), + MathFunc::Power => "power".to_string(), + MathFunc::Radians => "radians".to_string(), + MathFunc::Sin => "sin".to_string(), + MathFunc::Sinh => "sinh".to_string(), + MathFunc::Sqrt => "sqrt".to_string(), + MathFunc::Tan => "tan".to_string(), + MathFunc::Tanh => "tanh".to_string(), + MathFunc::Trunc => "trunc".to_string(), + }; + write!(f, "{}", str) + } +} + #[derive(Debug)] pub enum Func { Agg(AggFunc), Scalar(ScalarFunc), + Math(MathFunc), #[cfg(feature = "json")] Json(JsonFunc), } @@ -152,6 +268,7 @@ impl Display for Func { match self { Func::Agg(agg_func) => write!(f, "{}", agg_func.to_string()), Func::Scalar(scalar_func) => write!(f, "{}", scalar_func), + Func::Math(math_func) => write!(f, "{}", math_func), #[cfg(feature = "json")] Func::Json(json_func) => write!(f, "{}", json_func), } @@ -216,6 +333,35 @@ impl Func { "unhex" => Ok(Func::Scalar(ScalarFunc::Unhex)), "zeroblob" => Ok(Func::Scalar(ScalarFunc::ZeroBlob)), "soundex" => Ok(Func::Scalar(ScalarFunc::Soundex)), + "acos" => Ok(Func::Math(MathFunc::Acos)), + "acosh" => Ok(Func::Math(MathFunc::Acosh)), + "asin" => Ok(Func::Math(MathFunc::Asin)), + "asinh" => Ok(Func::Math(MathFunc::Asinh)), + "atan" => Ok(Func::Math(MathFunc::Atan)), + "atan2" => Ok(Func::Math(MathFunc::Atan2)), + "atanh" => Ok(Func::Math(MathFunc::Atanh)), + "ceil" => Ok(Func::Math(MathFunc::Ceil)), + "ceiling" => Ok(Func::Math(MathFunc::Ceiling)), + "cos" => Ok(Func::Math(MathFunc::Cos)), + "cosh" => Ok(Func::Math(MathFunc::Cosh)), + "degrees" => Ok(Func::Math(MathFunc::Degrees)), + "exp" => Ok(Func::Math(MathFunc::Exp)), + "floor" => Ok(Func::Math(MathFunc::Floor)), + "ln" => Ok(Func::Math(MathFunc::Ln)), + "log" => Ok(Func::Math(MathFunc::Log)), + "log10" => Ok(Func::Math(MathFunc::Log10)), + "log2" => Ok(Func::Math(MathFunc::Log2)), + "mod" => Ok(Func::Math(MathFunc::Mod)), + "pi" => Ok(Func::Math(MathFunc::Pi)), + "pow" => Ok(Func::Math(MathFunc::Pow)), + "power" => Ok(Func::Math(MathFunc::Power)), + "radians" => Ok(Func::Math(MathFunc::Radians)), + "sin" => Ok(Func::Math(MathFunc::Sin)), + "sinh" => Ok(Func::Math(MathFunc::Sinh)), + "sqrt" => Ok(Func::Math(MathFunc::Sqrt)), + "tan" => Ok(Func::Math(MathFunc::Tan)), + "tanh" => Ok(Func::Math(MathFunc::Tanh)), + "trunc" => Ok(Func::Math(MathFunc::Trunc)), _ => Err(()), } } diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 04ea1fc98..45ceb6ec3 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -2,7 +2,7 @@ use sqlite3_parser::ast::{self, UnaryOperator}; #[cfg(feature = "json")] use crate::function::JsonFunc; -use crate::function::{AggFunc, Func, FuncCtx, ScalarFunc}; +use crate::function::{AggFunc, Func, FuncCtx, MathFuncArity, ScalarFunc}; use crate::schema::Type; use crate::util::normalize_ident; use crate::vdbe::{builder::ProgramBuilder, BranchOffset, Insn}; @@ -1603,6 +1603,9 @@ pub fn translate_expr( } } } + Func::Math(mfs) => match mfs { + _ => unimplemented!(), + }, } } ast::Expr::FunctionCallStar { .. } => todo!(), diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index fa07fa69e..0da9926f9 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -24,7 +24,7 @@ pub mod sorter; mod datetime; use crate::error::{LimboError, SQLITE_CONSTRAINT_PRIMARYKEY}; -use crate::function::{AggFunc, FuncCtx, ScalarFunc}; +use crate::function::{AggFunc, FuncCtx, MathFunc, MathFuncArity, ScalarFunc}; use crate::pseudo::PseudoCursor; use crate::schema::Table; use crate::storage::sqlite3_ondisk::DatabaseHeader; @@ -2491,6 +2491,9 @@ impl Program { state.registers[*dest] = exec_replace(source, pattern, replacement); } }, + crate::function::Func::Math(math_func) => match math_func { + _ => unimplemented!(), + }, crate::function::Func::Agg(_) => { unreachable!("Aggregate functions should not be handled here") } From 793a85a14cdcc0fae3280638ae0b2a6362f7e46e Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Thu, 28 Nov 2024 19:28:00 +0200 Subject: [PATCH 03/12] Tolerate floating point minor differences in compatibility tests --- testing/tester.tcl | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/testing/tester.tcl b/testing/tester.tcl index 5ccb2165b..1aae417ca 100644 --- a/testing/tester.tcl +++ b/testing/tester.tcl @@ -32,3 +32,36 @@ proc do_execsql_test_on_specific_db {db_name test_name sql_statements expected_o set combined_expected_output [join $expected_outputs "\n"] run_test $::sqlite_exec $db_name $combined_sql $combined_expected_output } + +proc within_tolerance {actual expected tolerance} { + expr {abs($actual - $expected) <= $tolerance} +} + +proc do_execsql_test_tolerance {test_name sql_statements expected_outputs tolerance} { + puts "Running test: $test_name" + set combined_sql [string trim $sql_statements] + set actual_output [evaluate_sql $::sqlite_exec $combined_sql] + set actual_values [split $actual_output "\n"] + set expected_values [split $expected_outputs "\n"] + + if {[llength $actual_values] != [llength $expected_values]} { + puts "Test FAILED: '$sql_statements'" + puts "returned '$actual_output'" + puts "expected '$expected_outputs'" + exit 1 + } + + for {set i 0} {$i < [llength $actual_values]} {incr i} { + set actual [lindex $actual_values $i] + set expected [lindex $expected_values $i] + + if {![within_tolerance $actual $expected $tolerance]} { + set lower_bound [expr {$expected - $tolerance}] + set upper_bound [expr {$expected + $tolerance}] + puts "Test FAILED: '$sql_statements'" + puts "returned '$actual'" + puts "expected a value within the range \[$lower_bound, $upper_bound\]" + exit 1 + } + } +} From f69fdc1645d64f4b5b474111a594536994ad0732 Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Mon, 25 Nov 2024 00:03:06 +0200 Subject: [PATCH 04/12] Support unary math functions --- COMPAT.md | 46 ++--- core/translate/expr.rs | 33 +++- core/vdbe/mod.rs | 64 +++++- testing/math.test | 430 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 548 insertions(+), 25 deletions(-) diff --git a/COMPAT.md b/COMPAT.md index e65b05c67..c3eb70e00 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -164,36 +164,36 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html). | Function | Status | Comment | | ---------- | ------ | ------- | -| acos(X) | No | | -| acosh(X) | No | | -| asin(X) | No | | -| asinh(X) | No | | -| atan(X) | No | | +| acos(X) | Yes | | +| acosh(X) | Yes | | +| asin(X) | Yes | | +| asinh(X) | Yes | | +| atan(X) | Yes | | | atan2(Y,X) | No | | -| atanh(X) | No | | -| ceil(X) | No | | -| ceiling(X) | No | | -| cos(X) | No | | -| cosh(X) | No | | -| degrees(X) | No | | -| exp(X) | No | | -| floor(X) | No | | -| ln(X) | No | | +| atanh(X) | Yes | | +| ceil(X) | Yes | | +| ceiling(X) | Yes | | +| cos(X) | Yes | | +| cosh(X) | Yes | | +| degrees(X) | Yes | | +| exp(X) | Yes | | +| floor(X) | Yes | | +| ln(X) | Yes | | | log(B,X) | No | | | log(X) | No | | -| log10(X) | No | | -| log2(X) | No | | +| log10(X) | Yes | | +| log2(X) | Yes | | | mod(X,Y) | No | | | pi() | No | | | pow(X,Y) | No | | | power(X,Y) | No | | -| radians(X) | No | | -| sin(X) | No | | -| sinh(X) | No | | -| sqrt(X) | No | | -| tan(X) | No | | -| tanh(X) | No | | -| trunc(X) | No | | +| radians(X) | Yes | | +| sin(X) | Yes | | +| sinh(X) | Yes | | +| sqrt(X) | Yes | | +| tan(X) | Yes | | +| tanh(X) | Yes | | +| trunc(X) | Yes | | ### Aggregate functions diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 45ceb6ec3..da2c260b8 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1603,7 +1603,38 @@ pub fn translate_expr( } } } - Func::Math(mfs) => match mfs { + Func::Math(math_func) => match math_func.arity() { + MathFuncArity::Unary => { + let args = if let Some(args) = args { + if args.len() != 1 { + crate::bail_parse_error!( + "{} function with not exactly 1 argument", + math_func + ); + } + args + } else { + crate::bail_parse_error!("{} function with no arguments", math_func); + }; + + let reg = program.alloc_register(); + + translate_expr( + program, + referenced_tables, + &args[0], + reg, + precomputed_exprs_to_registers, + )?; + + program.emit_insn(Insn::Function { + constant_mask: 0, + start_reg: reg, + dest: target_register, + func: func_ctx, + }); + Ok(target_register) + } _ => unimplemented!(), }, } diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 0da9926f9..c97aa6202 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -2491,7 +2491,12 @@ impl Program { state.registers[*dest] = exec_replace(source, pattern, replacement); } }, - crate::function::Func::Math(math_func) => match math_func { + crate::function::Func::Math(math_func) => match math_func.arity() { + MathFuncArity::Unary => { + let reg_value = &state.registers[*start_reg]; + let result = exec_math_unary(reg_value, math_func); + state.registers[*dest] = result; + } _ => unimplemented!(), }, crate::function::Func::Agg(_) => { @@ -3600,6 +3605,63 @@ fn execute_sqlite_version(version_integer: i64) -> String { format!("{}.{}.{}", major, minor, release) } +fn to_f64(reg: &OwnedValue) -> Option { + match reg { + OwnedValue::Integer(i) => Some(*i as f64), + OwnedValue::Float(f) => Some(*f), + OwnedValue::Text(t) => t.parse::().ok(), + OwnedValue::Agg(ctx) => to_f64(ctx.final_value()), + _ => None, + } +} + +fn exec_math_unary(reg: &OwnedValue, function: &MathFunc) -> OwnedValue { + // In case of some functions and integer input, return the input as is + if let OwnedValue::Integer(_) = reg { + if matches! { function, MathFunc::Ceil | MathFunc::Ceiling | MathFunc::Floor | MathFunc::Trunc } + { + return reg.clone(); + } + } + + let f = match to_f64(reg) { + Some(f) => f, + None => return OwnedValue::Null, + }; + + let result = match function { + MathFunc::Acos => f.acos(), + MathFunc::Acosh => f.acosh(), + MathFunc::Asin => f.asin(), + MathFunc::Asinh => f.asinh(), + MathFunc::Atan => f.atan(), + MathFunc::Atanh => f.atanh(), + MathFunc::Ceil | MathFunc::Ceiling => f.ceil(), + MathFunc::Cos => f.cos(), + MathFunc::Cosh => f.cosh(), + MathFunc::Degrees => f.to_degrees(), + MathFunc::Exp => f.exp(), + MathFunc::Floor => f.floor(), + MathFunc::Ln => f.ln(), + MathFunc::Log10 => f.log10(), + MathFunc::Log2 => f.log2(), + MathFunc::Radians => f.to_radians(), + MathFunc::Sin => f.sin(), + MathFunc::Sinh => f.sinh(), + MathFunc::Sqrt => f.sqrt(), + MathFunc::Tan => f.tan(), + MathFunc::Tanh => f.tanh(), + MathFunc::Trunc => f.trunc(), + _ => unreachable!("Unexpected mathematical unary function {:?}", function), + }; + + if result.is_nan() { + OwnedValue::Null + } else { + OwnedValue::Float(result) + } +} + #[cfg(test)] mod tests { diff --git a/testing/math.test b/testing/math.test index af477fca4..bd795ef15 100644 --- a/testing/math.test +++ b/testing/math.test @@ -361,3 +361,433 @@ do_execsql_test bitwise-not-zero { SELECT ~0 } {-1} + +set tolerance 1e-13 + +do_execsql_test_tolerance acos-int { + SELECT acos(1) +} {0.0} $tolerance + +do_execsql_test_tolerance acos-float { + SELECT acos(-0.5) +} {2.0943951023931957} $tolerance + +do_execsql_test_tolerance acos-str { + SELECT acos('-0.5') +} {2.0943951023931957} $tolerance + +do_execsql_test_tolerance acos-null { + SELECT acos(null) +} {} $tolerance + + +do_execsql_test_tolerance acosh-int { + SELECT acosh(1) +} {0.0} $tolerance + +do_execsql_test_tolerance acosh-float { + SELECT acosh(1.5) +} {0.962423650119207} $tolerance + +do_execsql_test_tolerance acosh-str { + SELECT acosh('1.5') +} {0.962423650119207} $tolerance + +do_execsql_test_tolerance acosh-invalid { + SELECT acosh(0.99) +} {} $tolerance + +do_execsql_test_tolerance acosh-null { + SELECT acosh(null) +} {} $tolerance + + +do_execsql_test_tolerance asin-int { + SELECT asin(1) +} {1.5707963267948966} $tolerance + +do_execsql_test_tolerance asin-float { + SELECT asin(-0.5) +} {-0.5235987755982989} $tolerance + +do_execsql_test_tolerance asin-str { + SELECT asin('-0.5') +} {-0.5235987755982989} $tolerance + +do_execsql_test_tolerance asin-null { + SELECT asin(null) +} {} $tolerance + + +do_execsql_test_tolerance sin-int { + SELECT sin(1) +} {0.841470984807897} $tolerance + +do_execsql_test_tolerance sin-float { + SELECT sin(-0.5) +} {-0.479425538604203} $tolerance + +do_execsql_test_tolerance sin-str { + SELECT sin('-0.5') +} {-0.479425538604203} $tolerance + +do_execsql_test_tolerance sin-null { + SELECT sin(null) +} {} $tolerance + +do_execsql_test_tolerance sin-products-id { + SELECT sin(id) from products +} {0.8414709848078965 +0.9092974268256817 +0.1411200080598672 +-0.7568024953079282 +-0.9589242746631385 +-0.27941549819892586 +0.6569865987187891 +0.9893582466233818 +0.4121184852417566 +-0.5440211108893698 +-0.9999902065507035} $tolerance + + +do_execsql_test_tolerance asinh-int { + SELECT asinh(1) +} {0.881373587019543} $tolerance + +do_execsql_test_tolerance asinh-float { + SELECT asinh(-0.5) +} {-0.48121182505960347} $tolerance + +do_execsql_test_tolerance asinh-str { + SELECT asinh('-0.5') +} {-0.48121182505960347} $tolerance + +do_execsql_test_tolerance asinh-null { + SELECT asinh(null) +} {} $tolerance + + +do_execsql_test_tolerance atan-int { + SELECT atan(1) +} {0.7853981633974483} $tolerance + +do_execsql_test_tolerance atan-float { + SELECT atan(-0.5) +} {-0.4636476090008061} $tolerance + +do_execsql_test_tolerance atan-str { + SELECT atan('-0.5') +} {-0.4636476090008061} $tolerance + +do_execsql_test_tolerance atan-null { + SELECT atan(null) +} {} $tolerance + + +do_execsql_test_tolerance tan-int { + SELECT tan(1) +} {1.5574077246549} $tolerance + +do_execsql_test_tolerance tan-float { + SELECT tan(-0.5) +} {-0.54630248984379} $tolerance + +do_execsql_test_tolerance tan-str { + SELECT tan('-0.5') +} {-0.54630248984379} $tolerance + +do_execsql_test_tolerance tan-null { + SELECT tan(null) +} {} $tolerance + + +do_execsql_test_tolerance atanh-int { + SELECT atanh(0) +} {0.0} $tolerance + +do_execsql_test_tolerance atanh-float { + SELECT atanh(-0.5) +} {-0.5493061443340548} $tolerance + +do_execsql_test_tolerance atanh-str { + SELECT atanh('-0.5') +} {-0.5493061443340548} $tolerance + +do_execsql_test_tolerance atanh-null { + SELECT atanh(null) +} {} $tolerance + + +do_execsql_test ceil-int { + SELECT ceil(1) +} {1} + +do_execsql_test ceil-float { + SELECT ceil(-1.5) +} {-1.0} + +do_execsql_test ceil-str { + SELECT ceil('1.5') +} {2.0} + +do_execsql_test ceil-null { + SELECT ceil(null) +} {} + + +do_execsql_test ceiling-int { + SELECT ceiling(1) +} {1} + +do_execsql_test ceiling-float { + SELECT ceiling(-1.5) +} {-1.0} + +do_execsql_test ceiling-str { + SELECT ceiling('1.5') +} {2.0} + +do_execsql_test ceiling-null { + SELECT ceiling(null) +} {} + + +do_execsql_test_tolerance cos-int { + SELECT cos(1) +} {0.54030230586814} $tolerance + +do_execsql_test_tolerance cos-float { + SELECT cos(-0.5) +} {0.877582561890373} $tolerance + +do_execsql_test_tolerance cos-str { + SELECT cos('-0.5') +} {0.877582561890373} $tolerance + +do_execsql_test_tolerance cos-null { + SELECT cos(null) +} {} $tolerance + + +do_execsql_test_tolerance cosh-int { + SELECT cosh(1) +} {1.54308063481524} $tolerance + +do_execsql_test_tolerance cosh-float { + SELECT cosh(-0.5) +} {1.12762596520638} $tolerance + +do_execsql_test_tolerance cosh-str { + SELECT cosh('-0.5') +} {1.12762596520638} $tolerance + +do_execsql_test_tolerance cosh-null { + SELECT cosh(null) +} {} $tolerance + + +do_execsql_test_tolerance degrees-int { + SELECT degrees(1) +} {57.2957795130823} $tolerance + +do_execsql_test_tolerance degrees-float { + SELECT degrees(-0.5) +} {-28.6478897565412} $tolerance + +do_execsql_test_tolerance degrees-str { + SELECT degrees('-0.5') +} {-28.6478897565412} $tolerance + +do_execsql_test_tolerance degrees-null { + SELECT degrees(null) +} {} $tolerance + + +do_execsql_test_tolerance exp-int { + SELECT exp(1) +} {2.71828182845905} $tolerance + +do_execsql_test_tolerance exp-float { + SELECT exp(-0.5) +} {0.606530659712633} $tolerance + +do_execsql_test_tolerance exp-str { + SELECT exp('-0.5') +} {0.606530659712633} $tolerance + +do_execsql_test_tolerance exp-null { + SELECT exp(null) +} {} $tolerance + + +do_execsql_test floor-int { + SELECT floor(1) +} {1} + +do_execsql_test floor-float { + SELECT floor(-1.5) +} {-2.0} + +do_execsql_test floor-str { + SELECT floor('1.5') +} {1.0} + +do_execsql_test floor-null { + SELECT floor(null) +} {} + + +do_execsql_test_tolerance ln-int { + SELECT ln(1) +} {0.0} $tolerance + +do_execsql_test_tolerance ln-float { + SELECT ln(0.5) +} {-0.693147180559945} $tolerance + +do_execsql_test_tolerance ln-str { + SELECT ln('0.5') +} {-0.693147180559945} $tolerance + +do_execsql_test_tolerance ln-negative { + SELECT ln(-0.5) +} {} $tolerance + +do_execsql_test_tolerance ln-null { + SELECT ln(null) +} {} $tolerance + + +do_execsql_test_tolerance log10-int { + SELECT log10(1) +} {0.0} $tolerance + +do_execsql_test_tolerance log10-float { + SELECT log10(0.5) +} {-0.301029995663981} $tolerance + +do_execsql_test_tolerance log10-str { + SELECT log10('0.5') +} {-0.301029995663981} $tolerance + +do_execsql_test_tolerance log10-negative { + SELECT log10(-0.5) +} {} $tolerance + +do_execsql_test_tolerance log10-null { + SELECT log10(null) +} {} $tolerance + + +do_execsql_test_tolerance log2-int { + SELECT log2(1) +} {0.0} $tolerance + +do_execsql_test_tolerance log2-float { + SELECT log2(0.5) +} {-1.0} $tolerance + +do_execsql_test_tolerance log2-str { + SELECT log2('0.5') +} {-1.0} $tolerance + +do_execsql_test_tolerance log2-negative { + SELECT log2(-0.5) +} {} $tolerance + +do_execsql_test_tolerance log2-null { + SELECT log2(null) +} {} $tolerance + + +do_execsql_test_tolerance radians-int { + SELECT radians(1) +} {0.0174532925199433} $tolerance + +do_execsql_test_tolerance radians-float { + SELECT radians(-0.5) +} {-0.00872664625997165} $tolerance + +do_execsql_test_tolerance radians-str { + SELECT radians('-0.5') +} {-0.00872664625997165} $tolerance + +do_execsql_test_tolerance radians-null { + SELECT radians(null) +} {} $tolerance + + +do_execsql_test_tolerance sinh-int { + SELECT sinh(1) +} {1.1752011936438} $tolerance + +do_execsql_test_tolerance sinh-float { + SELECT sinh(-0.5) +} {-0.521095305493747} $tolerance + +do_execsql_test_tolerance sinh-str { + SELECT sinh('-0.5') +} {-0.521095305493747} $tolerance + +do_execsql_test_tolerance sinh-null { + SELECT sinh(null) +} {} $tolerance + + +do_execsql_test_tolerance sqrt-int { + SELECT sqrt(1) +} {1.0} $tolerance + +do_execsql_test_tolerance sqrt-float { + SELECT sqrt(0.5) +} {0.707106781186548} $tolerance + +do_execsql_test_tolerance sqrt-str { + SELECT sqrt('0.5') +} {0.707106781186548} $tolerance + +do_execsql_test_tolerance sqrt-negative { + SELECT sqrt(-0.5) +} {} $tolerance + +do_execsql_test_tolerance sqrt-null { + SELECT sqrt(null) +} {} $tolerance + + +do_execsql_test_tolerance tanh-int { + SELECT tanh(1) +} {0.761594155955765} $tolerance + +do_execsql_test_tolerance tanh-float { + SELECT tanh(-0.5) +} {-0.46211715726001} $tolerance + +do_execsql_test_tolerance tanh-str { + SELECT tanh('-0.5') +} {-0.46211715726001} $tolerance + +do_execsql_test_tolerance tanh-null { + SELECT tanh(null) +} {} $tolerance + + +do_execsql_test trunc-int { + SELECT trunc(1) +} {1} + +do_execsql_test trunc-float { + SELECT trunc(2.5) +} {2.0} + +do_execsql_test trunc-float-negative { + SELECT trunc(-2.5) +} {-2.0} + +do_execsql_test trunc-str { + SELECT trunc('2.5') +} {2.0} + +do_execsql_test trunc-null { + SELECT trunc(null) +} {} From 5e426a7624c59f1c158bc227cb9858ebaedc6c22 Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Tue, 26 Nov 2024 00:27:39 +0200 Subject: [PATCH 05/12] Support binary math functions --- COMPAT.md | 8 +- core/translate/expr.rs | 47 +++++++++++ core/vdbe/mod.rs | 32 ++++++++ testing/math.test | 180 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 263 insertions(+), 4 deletions(-) diff --git a/COMPAT.md b/COMPAT.md index c3eb70e00..9580799fd 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -169,7 +169,7 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html). | asin(X) | Yes | | | asinh(X) | Yes | | | atan(X) | Yes | | -| atan2(Y,X) | No | | +| atan2(Y,X) | Yes | | | atanh(X) | Yes | | | ceil(X) | Yes | | | ceiling(X) | Yes | | @@ -183,10 +183,10 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html). | log(X) | No | | | log10(X) | Yes | | | log2(X) | Yes | | -| mod(X,Y) | No | | +| mod(X,Y) | Yes | | | pi() | No | | -| pow(X,Y) | No | | -| power(X,Y) | No | | +| pow(X,Y) | Yes | | +| power(X,Y) | Yes | | | radians(X) | Yes | | | sin(X) | Yes | | | sinh(X) | Yes | | diff --git a/core/translate/expr.rs b/core/translate/expr.rs index da2c260b8..7bc08f2c2 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1635,6 +1635,53 @@ pub fn translate_expr( }); Ok(target_register) } + + MathFuncArity::Binary => { + let args = if let Some(args) = args { + if args.len() != 2 { + crate::bail_parse_error!( + "{} function with not exactly 2 arguments", + math_func + ); + } + args + } else { + crate::bail_parse_error!("{} function with no arguments", math_func); + }; + + let reg1 = program.alloc_register(); + let reg2 = program.alloc_register(); + + translate_expr( + program, + referenced_tables, + &args[0], + reg1, + precomputed_exprs_to_registers, + )?; + if let ast::Expr::Literal(_) = &args[0] { + program.mark_last_insn_constant(); + } + + translate_expr( + program, + referenced_tables, + &args[1], + reg2, + precomputed_exprs_to_registers, + )?; + if let ast::Expr::Literal(_) = &args[1] { + program.mark_last_insn_constant(); + } + + program.emit_insn(Insn::Function { + constant_mask: 0, + start_reg: target_register + 1, + dest: target_register, + func: func_ctx, + }); + Ok(target_register) + } _ => unimplemented!(), }, } diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index c97aa6202..09f52d892 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -2497,6 +2497,13 @@ impl Program { let result = exec_math_unary(reg_value, math_func); state.registers[*dest] = result; } + + MathFuncArity::Binary => { + let lhs = &state.registers[*start_reg]; + let rhs = &state.registers[*start_reg + 1]; + let result = exec_math_binary(lhs, rhs, math_func); + state.registers[*dest] = result; + } _ => unimplemented!(), }, crate::function::Func::Agg(_) => { @@ -3662,6 +3669,31 @@ fn exec_math_unary(reg: &OwnedValue, function: &MathFunc) -> OwnedValue { } } +fn exec_math_binary(lhs: &OwnedValue, rhs: &OwnedValue, function: &MathFunc) -> OwnedValue { + let lhs = match to_f64(lhs) { + Some(f) => f, + None => return OwnedValue::Null, + }; + + let rhs = match to_f64(rhs) { + Some(f) => f, + None => return OwnedValue::Null, + }; + + let result = match function { + MathFunc::Atan2 => lhs.atan2(rhs), + MathFunc::Mod => lhs % rhs, + MathFunc::Pow | MathFunc::Power => lhs.powf(rhs), + _ => unreachable!("Unexpected mathematical binary function {:?}", function), + }; + + if result.is_nan() { + OwnedValue::Null + } else { + OwnedValue::Float(result) + } +} + #[cfg(test)] mod tests { diff --git a/testing/math.test b/testing/math.test index bd795ef15..9b43b95f9 100644 --- a/testing/math.test +++ b/testing/math.test @@ -791,3 +791,183 @@ do_execsql_test trunc-str { do_execsql_test trunc-null { SELECT trunc(null) } {} + + +do_execsql_test_tolerance atan2-int-int { + SELECT atan2(5, -1) +} {1.76819188664478} $tolerance + +do_execsql_test_tolerance atan2-int-float { + SELECT atan2(5, -1.5) +} {1.86225312127276} $tolerance + +do_execsql_test_tolerance atan2-int-str { + SELECT atan2(5, '-1.5') +} {1.86225312127276} $tolerance + +do_execsql_test_tolerance atan2-float-int { + SELECT atan2(5.5, 10) +} {0.502843210927861} $tolerance + +do_execsql_test_tolerance atan2-float-float { + SELECT atan2(5.5, -1.5) +} {1.83704837594582} $tolerance + +do_execsql_test_tolerance atan2-float-str { + SELECT atan2(5.5, '-1.5') +} {1.83704837594582} $tolerance + +do_execsql_test_tolerance atan2-str-str { + SELECT atan2('5.5', '-1.5') +} {1.83704837594582} $tolerance + +do_execsql_test atan2-null-int { + SELECT atan2(null, 5) +} {} + +do_execsql_test atan2-int-null { + SELECT atan2(5, null) +} {} + + +do_execsql_test_tolerance mod-int-int { + SELECT mod(10, -3) +} {1.0} $tolerance + +do_execsql_test_tolerance mod-int-float { + SELECT mod(5, -1.5) +} {0.5} $tolerance + +do_execsql_test_tolerance mod-int-str { + SELECT mod(5, '-1.5') +} {0.5} $tolerance + +do_execsql_test_tolerance mod-float-int { + SELECT mod(5.5, 2) +} {1.5} $tolerance + +do_execsql_test_tolerance mod-float-float { + SELECT mod(5.5, -1.5) +} {1.0} $tolerance + +do_execsql_test_tolerance mod-float-str { + SELECT mod(5.5, '-1.5') +} {1.0} $tolerance + +do_execsql_test_tolerance mod-str-str { + SELECT mod('5.5', '-1.5') +} {1.0} $tolerance + +do_execsql_test mod-null-int { + SELECT mod(null, 5) +} {} + +do_execsql_test mod-int-null { + SELECT mod(5, null) +} {} + +do_execsql_test mod-float-zero { + SELECT mod(1.5, 0) +} {} + +do_execsql_test mod-products-id { + SELECT mod(products.id, 3) from products +} {1.0 +2.0 +0.0 +1.0 +2.0 +0.0 +1.0 +2.0 +0.0 +1.0 +2.0} + +do_execsql_test mod-products-price-id { + SELECT mod(products.price, products.id) from products +} {0.0 +0.0 +0.0 +1.0 +4.0 +4.0 +1.0 +2.0 +1.0 +3.0 +4.0} + + +do_execsql_test_tolerance pow-int-int { + SELECT pow(5, -1) +} {0.2} $tolerance + +do_execsql_test_tolerance pow-int-float { + SELECT pow(5, -1.5) +} {0.0894427190999916} $tolerance + +do_execsql_test_tolerance pow-int-str { + SELECT pow(5, '-1.5') +} {0.0894427190999916} $tolerance + +do_execsql_test_tolerance pow-float-int { + SELECT pow(5.5, 2) +} {30.25} $tolerance + +do_execsql_test_tolerance pow-float-float { + SELECT pow(5.5, -1.5) +} {0.077527533220222} $tolerance + +do_execsql_test_tolerance pow-float-str { + SELECT pow(5.5, '-1.5') +} {0.077527533220222} $tolerance + +do_execsql_test_tolerance pow-str-str { + SELECT pow('5.5', '-1.5') +} {0.077527533220222} $tolerance + +do_execsql_test pow-null-int { + SELECT pow(null, 5) +} {} + +do_execsql_test pow-int-null { + SELECT pow(5, null) +} {} + + +do_execsql_test_tolerance power-int-int { + SELECT power(5, -1) +} {0.2} $tolerance + +do_execsql_test_tolerance power-int-float { + SELECT power(5, -1.5) +} {0.0894427190999916} $tolerance + +do_execsql_test_tolerance power-int-str { + SELECT power(5, '-1.5') +} {0.0894427190999916} $tolerance + +do_execsql_test_tolerance power-float-int { + SELECT power(5.5, 2) +} {30.25} $tolerance + +do_execsql_test_tolerance power-float-float { + SELECT power(5.5, -1.5) +} {0.077527533220222} $tolerance + +do_execsql_test_tolerance power-float-str { + SELECT power(5.5, '-1.5') +} {0.077527533220222} $tolerance + +do_execsql_test_tolerance power-str-str { + SELECT power('5.5', '-1.5') +} {0.077527533220222} $tolerance + +do_execsql_test power-null-int { + SELECT power(null, 5) +} {} + +do_execsql_test power-int-null { + SELECT power(5, null) +} {} From 89d0289444da695730a8d565eea41b9027cdf834 Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Thu, 12 Dec 2024 22:54:28 +0200 Subject: [PATCH 06/12] Support `pi()` function --- COMPAT.md | 2 +- core/translate/expr.rs | 14 ++++++++++++++ core/vdbe/mod.rs | 5 +++++ testing/math.test | 5 +++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/COMPAT.md b/COMPAT.md index 9580799fd..25dae3614 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -184,7 +184,7 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html). | log10(X) | Yes | | | log2(X) | Yes | | | mod(X,Y) | Yes | | -| pi() | No | | +| pi() | Yes | | | pow(X,Y) | Yes | | | power(X,Y) | Yes | | | radians(X) | Yes | | diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 7bc08f2c2..c8e66821d 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1604,6 +1604,20 @@ pub fn translate_expr( } } Func::Math(math_func) => match math_func.arity() { + MathFuncArity::Nullary => { + if args.is_some() { + crate::bail_parse_error!("{} function with arguments", math_func); + } + + program.emit_insn(Insn::Function { + constant_mask: 0, + start_reg: 0, + dest: target_register, + func: func_ctx, + }); + Ok(target_register) + } + MathFuncArity::Unary => { let args = if let Some(args) = args { if args.len() != 1 { diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 09f52d892..796eeff94 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -2492,6 +2492,10 @@ impl Program { } }, crate::function::Func::Math(math_func) => match math_func.arity() { + MathFuncArity::Nullary => { + state.registers[*dest] = OwnedValue::Float(std::f64::consts::PI); + } + MathFuncArity::Unary => { let reg_value = &state.registers[*start_reg]; let result = exec_math_unary(reg_value, math_func); @@ -2504,6 +2508,7 @@ impl Program { let result = exec_math_binary(lhs, rhs, math_func); state.registers[*dest] = result; } + _ => unimplemented!(), }, crate::function::Func::Agg(_) => { diff --git a/testing/math.test b/testing/math.test index 9b43b95f9..c75c387e6 100644 --- a/testing/math.test +++ b/testing/math.test @@ -364,6 +364,11 @@ do_execsql_test bitwise-not-zero { set tolerance 1e-13 +do_execsql_test_tolerance pi { + SELECT pi() +} {3.14159265358979} $tolerance + + do_execsql_test_tolerance acos-int { SELECT acos(1) } {0.0} $tolerance From e69ee80fac5eaab156559a5c38464b2606df74ed Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Sun, 15 Dec 2024 22:30:04 +0200 Subject: [PATCH 07/12] 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) +} {} From f5c82503f949a84f06c23f5d416a9e693fc74aa2 Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Mon, 16 Dec 2024 19:51:13 +0200 Subject: [PATCH 08/12] Be more explicit with `pi()` being the only nullary math function --- core/vdbe/mod.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 547cde53b..154d6f7e6 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -2492,9 +2492,18 @@ impl Program { } }, crate::function::Func::Math(math_func) => match math_func.arity() { - MathFuncArity::Nullary => { - state.registers[*dest] = OwnedValue::Float(std::f64::consts::PI); - } + MathFuncArity::Nullary => match math_func { + MathFunc::Pi => { + state.registers[*dest] = + OwnedValue::Float(std::f64::consts::PI); + } + _ => { + unreachable!( + "Unexpected mathematical Nullary function {:?}", + math_func + ); + } + }, MathFuncArity::Unary => { let reg_value = &state.registers[*start_reg]; From aa821647173781d0e1a0c772d8d4b089e3c1d41e Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Mon, 16 Dec 2024 20:05:13 +0200 Subject: [PATCH 09/12] Add FIXME comments about floating point comparison tolerance --- testing/math.test | 6 ++++-- testing/tester.tcl | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/testing/math.test b/testing/math.test index 064de41e3..f2faa4cd2 100644 --- a/testing/math.test +++ b/testing/math.test @@ -3,6 +3,10 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +# Tolerance for floating point comparisons +# FIXME: When Limbo's floating point presentation matches to SQLite, this could/should be removed +set tolerance 1e-13 + do_execsql_test add-int { SELECT 10 + 1 } {11} @@ -362,8 +366,6 @@ do_execsql_test bitwise-not-zero { } {-1} -set tolerance 1e-13 - do_execsql_test_tolerance pi { SELECT pi() } {3.14159265358979} $tolerance diff --git a/testing/tester.tcl b/testing/tester.tcl index 1aae417ca..1ce0a3755 100644 --- a/testing/tester.tcl +++ b/testing/tester.tcl @@ -37,6 +37,8 @@ proc within_tolerance {actual expected tolerance} { expr {abs($actual - $expected) <= $tolerance} } +# This function is used to test floating point values within a tolerance +# FIXME: When Limbo's floating point presentation matches to SQLite, this could/should be removed proc do_execsql_test_tolerance {test_name sql_statements expected_outputs tolerance} { puts "Running test: $test_name" set combined_sql [string trim $sql_statements] From ca418c2674d436aa03d86d92328780fb6c175ef1 Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Mon, 16 Dec 2024 21:55:55 +0200 Subject: [PATCH 10/12] Run `do_execsql_test_tolerance` for each database --- testing/tester.tcl | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/testing/tester.tcl b/testing/tester.tcl index 1ce0a3755..04a43c3eb 100644 --- a/testing/tester.tcl +++ b/testing/tester.tcl @@ -40,30 +40,32 @@ proc within_tolerance {actual expected tolerance} { # This function is used to test floating point values within a tolerance # FIXME: When Limbo's floating point presentation matches to SQLite, this could/should be removed proc do_execsql_test_tolerance {test_name sql_statements expected_outputs tolerance} { - puts "Running test: $test_name" - set combined_sql [string trim $sql_statements] - set actual_output [evaluate_sql $::sqlite_exec $combined_sql] - set actual_values [split $actual_output "\n"] - set expected_values [split $expected_outputs "\n"] + foreach db $::test_dbs { + puts [format "(%s) %s Running test: %s" $db [string repeat " " [expr {40 - [string length $db]}]] $test_name] + set combined_sql [string trim $sql_statements] + set actual_output [evaluate_sql $::sqlite_exec $db $combined_sql] + set actual_values [split $actual_output "\n"] + set expected_values [split $expected_outputs "\n"] - if {[llength $actual_values] != [llength $expected_values]} { - puts "Test FAILED: '$sql_statements'" - puts "returned '$actual_output'" - puts "expected '$expected_outputs'" - exit 1 - } - - for {set i 0} {$i < [llength $actual_values]} {incr i} { - set actual [lindex $actual_values $i] - set expected [lindex $expected_values $i] - - if {![within_tolerance $actual $expected $tolerance]} { - set lower_bound [expr {$expected - $tolerance}] - set upper_bound [expr {$expected + $tolerance}] + if {[llength $actual_values] != [llength $expected_values]} { puts "Test FAILED: '$sql_statements'" - puts "returned '$actual'" - puts "expected a value within the range \[$lower_bound, $upper_bound\]" + puts "returned '$actual_output'" + puts "expected '$expected_outputs'" exit 1 } + + for {set i 0} {$i < [llength $actual_values]} {incr i} { + set actual [lindex $actual_values $i] + set expected [lindex $expected_values $i] + + if {![within_tolerance $actual $expected $tolerance]} { + set lower_bound [expr {$expected - $tolerance}] + set upper_bound [expr {$expected + $tolerance}] + puts "Test FAILED: '$sql_statements'" + puts "returned '$actual'" + puts "expected a value within the range \[$lower_bound, $upper_bound\]" + exit 1 + } + } } } From fe429302391000bb4749f86fda2f6dc09a7e22bc Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Mon, 16 Dec 2024 23:48:58 +0200 Subject: [PATCH 11/12] Take `log` function argument count from function context --- core/vdbe/mod.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 154d6f7e6..069782c9a 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -2520,15 +2520,21 @@ impl Program { 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) + let result = match arg_count { + 1 => { + let arg = &state.registers[*start_reg]; + exec_math_log(arg, None) + } + 2 => { + let base = &state.registers[*start_reg]; + let arg = &state.registers[*start_reg + 1]; + exec_math_log(arg, Some(base)) + } + _ => unreachable!( + "{:?} function with unexpected number of arguments", + math_func + ), }; - state.registers[*dest] = result; } _ => unreachable!( From a1c77af8a8a14ef939543c3ffb21922cb8a01479 Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Tue, 17 Dec 2024 00:03:48 +0200 Subject: [PATCH 12/12] Limit `sin` and `mod` tests rows --- testing/math.test | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/testing/math.test b/testing/math.test index f2faa4cd2..7b27495e3 100644 --- a/testing/math.test +++ b/testing/math.test @@ -443,18 +443,12 @@ do_execsql_test_tolerance sin-null { } {} $tolerance do_execsql_test_tolerance sin-products-id { - SELECT sin(id) from products + SELECT sin(id) from products limit 5 } {0.8414709848078965 0.9092974268256817 0.1411200080598672 -0.7568024953079282 --0.9589242746631385 --0.27941549819892586 -0.6569865987187891 -0.9893582466233818 -0.4121184852417566 --0.5440211108893698 --0.9999902065507035} $tolerance +-0.9589242746631385} $tolerance do_execsql_test_tolerance asinh-int { @@ -878,31 +872,19 @@ do_execsql_test mod-float-zero { } {} do_execsql_test mod-products-id { - SELECT mod(products.id, 3) from products + SELECT mod(products.id, 3) from products limit 5 } {1.0 2.0 0.0 1.0 -2.0 -0.0 -1.0 -2.0 -0.0 -1.0 2.0} do_execsql_test mod-products-price-id { - SELECT mod(products.price, products.id) from products + SELECT mod(products.price, products.id) from products limit 5 } {0.0 0.0 0.0 1.0 -4.0 -4.0 -1.0 -2.0 -1.0 -3.0 4.0}