From b31363aecb6fa23f73236b363fe002990790c179 Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Mon, 24 Feb 2025 21:31:06 -0500 Subject: [PATCH] More improvements/cleanups to vdbe around casting --- core/util.rs | 316 +++++++++++++++++++++++++++++++++++++++++++++- core/vdbe/insn.rs | 16 +-- core/vdbe/mod.rs | 136 +------------------- testing/math.test | 4 +- 4 files changed, 327 insertions(+), 145 deletions(-) diff --git a/core/util.rs b/core/util.rs index aa91836fd..cb57823a4 100644 --- a/core/util.rs +++ b/core/util.rs @@ -3,16 +3,17 @@ use std::{rc::Rc, sync::Arc}; use crate::{ schema::{self, Column, Schema, Type}, + types::{OwnedValue, OwnedValueType}, LimboError, OpenFlags, Result, Statement, StepResult, IO, }; pub trait RoundToPrecision { - fn round_to_precision(self, precision: f64) -> f64; + fn round_to_precision(self, precision: i32) -> f64; } impl RoundToPrecision for f64 { - fn round_to_precision(self, precision: f64) -> f64 { - let factor = 10f64.powf(precision); + fn round_to_precision(self, precision: i32) -> f64 { + let factor = 10f64.powi(precision); (self * factor).round() / factor } } @@ -613,6 +614,156 @@ pub fn decode_percent(uri: &str) -> String { String::from_utf8_lossy(&decoded).to_string() } +/// When casting a TEXT value to INTEGER, the longest possible prefix of the value that can be interpreted as an integer number +/// is extracted from the TEXT value and the remainder ignored. Any leading spaces in the TEXT value when converting from TEXT to INTEGER are ignored. +/// If there is no prefix that can be interpreted as an integer number, the result of the conversion is 0. +/// If the prefix integer is greater than +9223372036854775807 then the result of the cast is exactly +9223372036854775807. +/// Similarly, if the prefix integer is less than -9223372036854775808 then the result of the cast is exactly -9223372036854775808. +/// When casting to INTEGER, if the text looks like a floating point value with an exponent, the exponent will be ignored +/// because it is no part of the integer prefix. For example, "CAST('123e+5' AS INTEGER)" results in 123, not in 12300000. +/// The CAST operator understands decimal integers only — conversion of hexadecimal integers stops at the "x" in the "0x" prefix of the hexadecimal integer string and thus result of the CAST is always zero. +pub fn cast_text_to_integer(text: &str) -> OwnedValue { + let text = text.trim(); + if text.is_empty() { + return OwnedValue::Integer(0); + } + if let Ok(i) = text.parse::() { + return OwnedValue::Integer(i); + } + let bytes = text.as_bytes(); + let mut end = 0; + if bytes[0] == b'-' { + end = 1; + } + while end < bytes.len() && bytes[end].is_ascii_digit() { + end += 1; + } + text[..end] + .parse::() + .map_or(OwnedValue::Integer(0), OwnedValue::Integer) +} + +/// When casting a TEXT value to REAL, the longest possible prefix of the value that can be interpreted +/// as a real number is extracted from the TEXT value and the remainder ignored. Any leading spaces in +/// the TEXT value are ignored when converging from TEXT to REAL. +/// If there is no prefix that can be interpreted as a real number, the result of the conversion is 0.0. +pub fn cast_text_to_real(text: &str) -> OwnedValue { + let trimmed = text.trim(); + if trimmed.is_empty() { + return OwnedValue::Float(0.0); + } + let Ok((_, text)) = parse_numeric_str(trimmed) else { + return OwnedValue::Float(0.0); + }; + text.parse::() + .map_or(OwnedValue::Float(0.0), OwnedValue::Float) +} + +/// NUMERIC Casting a TEXT or BLOB value into NUMERIC yields either an INTEGER or a REAL result. +/// If the input text looks like an integer (there is no decimal point nor exponent) and the value +/// is small enough to fit in a 64-bit signed integer, then the result will be INTEGER. +/// Input text that looks like floating point (there is a decimal point and/or an exponent) +/// and the text describes a value that can be losslessly converted back and forth between IEEE 754 +/// 64-bit float and a 51-bit signed integer, then the result is INTEGER. (In the previous sentence, +/// a 51-bit integer is specified since that is one bit less than the length of the mantissa of an +/// IEEE 754 64-bit float and thus provides a 1-bit of margin for the text-to-float conversion operation.) +/// Any text input that describes a value outside the range of a 64-bit signed integer yields a REAL result. +/// Casting a REAL or INTEGER value to NUMERIC is a no-op, even if a real value could be losslessly converted to an integer. +pub fn checked_cast_text_to_numeric(text: &str) -> std::result::Result { + // sqlite will parse the first N digits of a string to numeric value, then determine + // whether _that_ value is more likely a real or integer value. e.g. + // '-100234-2344.23e14' evaluates to -100234 instead of -100234.0 + let (kind, text) = parse_numeric_str(text)?; + match kind { + OwnedValueType::Integer => { + match text.parse::() { + Ok(i) => Ok(OwnedValue::Integer(i)), + Err(e) => { + if matches!( + e.kind(), + std::num::IntErrorKind::PosOverflow | std::num::IntErrorKind::NegOverflow + ) { + // if overflow, we return the representation as a real: + // we have to match sqlite exactly here, so we match sqlite3AtoF + let value = text.parse::().unwrap_or_default(); + let factor = 10f64.powi(15 - value.abs().log10().ceil() as i32); + Ok(OwnedValue::Float((value * factor).round() / factor)) + } else { + Err(()) + } + } + } + } + OwnedValueType::Float => Ok(text + .parse::() + .map_or(OwnedValue::Float(0.0), OwnedValue::Float)), + _ => unreachable!(), + } +} + +fn parse_numeric_str(text: &str) -> Result<(OwnedValueType, &str), ()> { + let bytes = text.trim_start().as_bytes(); + if bytes.is_empty() { + return Err(()); + } + let mut end = 0; + let mut has_decimal = false; + let mut has_exponent = false; + if bytes[0] == b'-' { + end = 1; + } + while end < bytes.len() { + match bytes[end] { + b'0'..=b'9' => end += 1, + b'.' if !has_decimal && !has_exponent => { + has_decimal = true; + end += 1; + } + b'e' | b'E' if !has_exponent => { + has_exponent = true; + end += 1; + // allow exponent sign + if end < bytes.len() && (bytes[end] == b'+' || bytes[end] == b'-') { + end += 1; + } + } + _ => break, + } + } + if end == 0 || (end == 1 && bytes[0] == b'-') { + return Err(()); + } + // edge case: if it ends with exponent, strip and cast valid digits as float + let last = bytes[end - 1]; + if last.eq_ignore_ascii_case(&b'e') { + return Ok((OwnedValueType::Float, &text[0..end - 1])); + // edge case: ends with extponent / sign + } else if has_exponent && (last == b'-' || last == b'+') { + return Ok((OwnedValueType::Float, &text[0..end - 2])); + } + Ok(( + if !has_decimal && !has_exponent { + OwnedValueType::Integer + } else { + OwnedValueType::Float + }, + &text[..end], + )) +} + +pub fn cast_text_to_numeric(txt: &str) -> OwnedValue { + checked_cast_text_to_numeric(txt).unwrap_or(OwnedValue::Integer(0)) +} + +// Check if float can be losslessly converted to 51-bit integer +pub fn cast_real_to_integer(float: f64) -> std::result::Result { + let i = float as i64; + if float == i as f64 && i.abs() < (1i64 << 51) { + return Ok(i); + } + Err(()) +} + #[cfg(test)] pub mod tests { use super::*; @@ -1163,4 +1314,163 @@ pub mod tests { "/home/user/db.sqlite" ); } + + #[test] + fn test_text_to_integer() { + assert_eq!(cast_text_to_integer("1"), OwnedValue::Integer(1),); + assert_eq!(cast_text_to_integer("-1"), OwnedValue::Integer(-1),); + assert_eq!( + cast_text_to_integer("1823400-00000"), + OwnedValue::Integer(1823400), + ); + assert_eq!( + cast_text_to_integer("-10000000"), + OwnedValue::Integer(-10000000), + ); + assert_eq!(cast_text_to_integer("123xxx"), OwnedValue::Integer(123),); + assert_eq!( + cast_text_to_integer("9223372036854775807"), + OwnedValue::Integer(i64::MAX), + ); + assert_eq!( + cast_text_to_integer("9223372036854775808"), + OwnedValue::Integer(0), + ); + assert_eq!( + cast_text_to_integer("-9223372036854775808"), + OwnedValue::Integer(i64::MIN), + ); + assert_eq!( + cast_text_to_integer("-9223372036854775809"), + OwnedValue::Integer(0), + ); + assert_eq!(cast_text_to_integer("-"), OwnedValue::Integer(0),); + } + + #[test] + fn test_text_to_real() { + assert_eq!(cast_text_to_real("1"), OwnedValue::Float(1.0)); + assert_eq!(cast_text_to_real("-1"), OwnedValue::Float(-1.0)); + assert_eq!(cast_text_to_real("1.0"), OwnedValue::Float(1.0)); + assert_eq!(cast_text_to_real("-1.0"), OwnedValue::Float(-1.0)); + assert_eq!(cast_text_to_real("1e10"), OwnedValue::Float(1e10)); + assert_eq!(cast_text_to_real("-1e10"), OwnedValue::Float(-1e10)); + assert_eq!(cast_text_to_real("1e-10"), OwnedValue::Float(1e-10)); + assert_eq!(cast_text_to_real("-1e-10"), OwnedValue::Float(-1e-10)); + assert_eq!(cast_text_to_real("1.123e10"), OwnedValue::Float(1.123e10)); + assert_eq!(cast_text_to_real("-1.123e10"), OwnedValue::Float(-1.123e10)); + assert_eq!(cast_text_to_real("1.123e-10"), OwnedValue::Float(1.123e-10)); + assert_eq!(cast_text_to_real("-1.123-e-10"), OwnedValue::Float(-1.123)); + assert_eq!(cast_text_to_real("1-282584294928"), OwnedValue::Float(1.0)); + assert_eq!( + cast_text_to_real("1.7976931348623157e309"), + OwnedValue::Float(f64::INFINITY), + ); + assert_eq!( + cast_text_to_real("-1.7976931348623157e308"), + OwnedValue::Float(f64::MIN), + ); + assert_eq!( + cast_text_to_real("-1.7976931348623157e309"), + OwnedValue::Float(f64::NEG_INFINITY), + ); + assert_eq!(cast_text_to_real("1E"), OwnedValue::Float(1.0)); + assert_eq!(cast_text_to_real("1EE"), OwnedValue::Float(1.0)); + assert_eq!(cast_text_to_real("-1E"), OwnedValue::Float(-1.0)); + assert_eq!(cast_text_to_real("1."), OwnedValue::Float(1.0)); + assert_eq!(cast_text_to_real("-1."), OwnedValue::Float(-1.0)); + assert_eq!(cast_text_to_real("1.23E"), OwnedValue::Float(1.23)); + assert_eq!(cast_text_to_real(".1.23E-"), OwnedValue::Float(0.1)); + assert_eq!(cast_text_to_real("0"), OwnedValue::Float(0.0)); + assert_eq!(cast_text_to_real("-0"), OwnedValue::Float(0.0)); + assert_eq!(cast_text_to_real("-0"), OwnedValue::Float(0.0)); + assert_eq!(cast_text_to_real("-0.0"), OwnedValue::Float(0.0)); + assert_eq!(cast_text_to_real("0.0"), OwnedValue::Float(0.0)); + assert_eq!(cast_text_to_real("-"), OwnedValue::Float(0.0)); + } + + #[test] + fn test_text_to_numeric() { + assert_eq!(cast_text_to_numeric("1"), OwnedValue::Integer(1)); + assert_eq!(cast_text_to_numeric("-1"), OwnedValue::Integer(-1)); + assert_eq!( + cast_text_to_numeric("1823400-00000"), + OwnedValue::Integer(1823400) + ); + assert_eq!( + cast_text_to_numeric("-10000000"), + OwnedValue::Integer(-10000000) + ); + assert_eq!(cast_text_to_numeric("123xxx"), OwnedValue::Integer(123)); + assert_eq!( + cast_text_to_numeric("9223372036854775807"), + OwnedValue::Integer(i64::MAX) + ); + assert_eq!( + cast_text_to_numeric("9223372036854775808"), + OwnedValue::Float(9.22337203685478e18) + ); // Exceeds i64, becomes float + assert_eq!( + cast_text_to_numeric("-9223372036854775808"), + OwnedValue::Integer(i64::MIN) + ); + assert_eq!( + cast_text_to_numeric("-9223372036854775809"), + OwnedValue::Float(-9.22337203685478e18) + ); // Exceeds i64, becomes float + + assert_eq!(cast_text_to_numeric("1.0"), OwnedValue::Float(1.0)); + assert_eq!(cast_text_to_numeric("-1.0"), OwnedValue::Float(-1.0)); + assert_eq!(cast_text_to_numeric("1e10"), OwnedValue::Float(1e10)); + assert_eq!(cast_text_to_numeric("-1e10"), OwnedValue::Float(-1e10)); + assert_eq!(cast_text_to_numeric("1e-10"), OwnedValue::Float(1e-10)); + assert_eq!(cast_text_to_numeric("-1e-10"), OwnedValue::Float(-1e-10)); + assert_eq!( + cast_text_to_numeric("1.123e10"), + OwnedValue::Float(1.123e10) + ); + assert_eq!( + cast_text_to_numeric("-1.123e10"), + OwnedValue::Float(-1.123e10) + ); + assert_eq!( + cast_text_to_numeric("1.123e-10"), + OwnedValue::Float(1.123e-10) + ); + assert_eq!( + cast_text_to_numeric("-1.123-e-10"), + OwnedValue::Float(-1.123) + ); + assert_eq!( + cast_text_to_numeric("1-282584294928"), + OwnedValue::Integer(1) + ); + assert_eq!(cast_text_to_numeric("xxx"), OwnedValue::Integer(0)); + assert_eq!( + cast_text_to_numeric("1.7976931348623157e309"), + OwnedValue::Float(f64::INFINITY) + ); + assert_eq!( + cast_text_to_numeric("-1.7976931348623157e308"), + OwnedValue::Float(f64::MIN) + ); + assert_eq!( + cast_text_to_numeric("-1.7976931348623157e309"), + OwnedValue::Float(f64::NEG_INFINITY) + ); + + assert_eq!(cast_text_to_numeric("1E"), OwnedValue::Float(1.0)); + assert_eq!(cast_text_to_numeric("1EE"), OwnedValue::Float(1.0)); + assert_eq!(cast_text_to_numeric("-1E"), OwnedValue::Float(-1.0)); + assert_eq!(cast_text_to_numeric("1."), OwnedValue::Float(1.0)); + assert_eq!(cast_text_to_numeric("-1."), OwnedValue::Float(-1.0)); + assert_eq!(cast_text_to_numeric("1.23E"), OwnedValue::Float(1.23)); + assert_eq!(cast_text_to_numeric("1.23E-"), OwnedValue::Float(1.23)); + + assert_eq!(cast_text_to_numeric("0"), OwnedValue::Integer(0)); + assert_eq!(cast_text_to_numeric("-0"), OwnedValue::Integer(0)); + assert_eq!(cast_text_to_numeric("-0.0"), OwnedValue::Float(0.0)); + assert_eq!(cast_text_to_numeric("0.0"), OwnedValue::Float(0.0)); + assert_eq!(cast_text_to_numeric("-"), OwnedValue::Integer(0)); + } } diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index c9893fed7..b923275f7 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -3,7 +3,6 @@ use std::num::NonZero; use super::{cast_text_to_numeric, AggFunc, BranchOffset, CursorID, FuncCtx, PageIdx}; use crate::storage::wal::CheckpointMode; use crate::types::{OwnedValue, Record}; -use crate::util::RoundToPrecision; use limbo_macros::Description; macro_rules! final_agg_values { @@ -712,9 +711,7 @@ pub fn exec_add(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { OwnedValue::Integer(result.0) } } - (OwnedValue::Float(lhs), OwnedValue::Float(rhs)) => { - OwnedValue::Float((lhs + rhs).round_to_precision(6.0)) - } + (OwnedValue::Float(lhs), OwnedValue::Float(rhs)) => OwnedValue::Float(lhs + rhs), (OwnedValue::Float(f), OwnedValue::Integer(i)) | (OwnedValue::Integer(i), OwnedValue::Float(f)) => OwnedValue::Float(*f + *i as f64), (OwnedValue::Null, _) | (_, OwnedValue::Null) => OwnedValue::Null, @@ -757,6 +754,7 @@ pub fn exec_subtract(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { _ => todo!(), } } + pub fn exec_multiply(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { final_agg_values!(lhs, rhs); match (lhs, rhs) { @@ -768,9 +766,7 @@ pub fn exec_multiply(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { OwnedValue::Integer(result.0) } } - (OwnedValue::Float(lhs), OwnedValue::Float(rhs)) => { - OwnedValue::Float((lhs * rhs).round_to_precision(6.0)) - } + (OwnedValue::Float(lhs), OwnedValue::Float(rhs)) => OwnedValue::Float(lhs * rhs), (OwnedValue::Integer(i), OwnedValue::Float(f)) | (OwnedValue::Float(f), OwnedValue::Integer(i)) => OwnedValue::Float(*i as f64 * { *f }), (OwnedValue::Null, _) | (_, OwnedValue::Null) => OwnedValue::Null, @@ -1160,7 +1156,7 @@ mod tests { ), (OwnedValue::Integer(0), OwnedValue::Text(Text::from_str(""))), ]; - let outpus = [ + let outputs = [ OwnedValue::Null, OwnedValue::Integer(1), OwnedValue::Null, @@ -1174,13 +1170,13 @@ mod tests { assert_eq!( inputs.len(), - outpus.len(), + outputs.len(), "Inputs and Outputs should have same size" ); for (i, (lhs, rhs)) in inputs.iter().enumerate() { assert_eq!( exec_or(lhs, rhs), - outpus[i], + outputs[i], "Wrong OR for lhs: {}, rhs: {}", lhs, rhs diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index a3339d048..f31de6630 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -39,10 +39,12 @@ use crate::storage::wal::CheckpointResult; use crate::storage::{btree::BTreeCursor, pager::Pager}; use crate::translate::plan::{ResultSetColumn, TableReference}; use crate::types::{ - AggContext, Cursor, CursorResult, ExternalAggState, OwnedValue, OwnedValueType, Record, - SeekKey, SeekOp, + AggContext, Cursor, CursorResult, ExternalAggState, OwnedValue, Record, SeekKey, SeekOp, +}; +use crate::util::{ + cast_real_to_integer, cast_text_to_integer, cast_text_to_numeric, cast_text_to_real, + checked_cast_text_to_numeric, parse_schema_rows, RoundToPrecision, }; -use crate::util::{parse_schema_rows, RoundToPrecision}; use crate::vdbe::builder::CursorType; use crate::vdbe::insn::Insn; use crate::vector::{vector32, vector64, vector_distance_cos, vector_extract}; @@ -3463,7 +3465,7 @@ fn exec_round(reg: &OwnedValue, precision: Option) -> OwnedValue { let reg = _to_float(reg); let round = |reg: f64, f: f64| { let precision = if f < 1.0 { 0.0 } else { f }; - OwnedValue::Float(reg.round_to_precision(precision)) + OwnedValue::Float(reg.round_to_precision(precision as i32)) }; match precision { Some(OwnedValue::Text(x)) => match cast_text_to_numeric(x.as_str()) { @@ -3644,132 +3646,6 @@ fn exec_replace(source: &OwnedValue, pattern: &OwnedValue, replacement: &OwnedVa } } -/// When casting a TEXT value to INTEGER, the longest possible prefix of the value that can be interpreted as an integer number -/// is extracted from the TEXT value and the remainder ignored. Any leading spaces in the TEXT value when converting from TEXT to INTEGER are ignored. -/// If there is no prefix that can be interpreted as an integer number, the result of the conversion is 0. -/// If the prefix integer is greater than +9223372036854775807 then the result of the cast is exactly +9223372036854775807. -/// Similarly, if the prefix integer is less than -9223372036854775808 then the result of the cast is exactly -9223372036854775808. -/// When casting to INTEGER, if the text looks like a floating point value with an exponent, the exponent will be ignored -/// because it is no part of the integer prefix. For example, "CAST('123e+5' AS INTEGER)" results in 123, not in 12300000. -/// The CAST operator understands decimal integers only — conversion of hexadecimal integers stops at the "x" in the "0x" prefix of the hexadecimal integer string and thus result of the CAST is always zero. -fn cast_text_to_integer(text: &str) -> OwnedValue { - let text = text.trim(); - if text.is_empty() { - return OwnedValue::Integer(0); - } - if let Ok(i) = text.parse::() { - return OwnedValue::Integer(i); - } - let bytes = text.as_bytes(); - let mut end = 0; - if bytes[0] == b'-' { - end = 1; - } - while end < bytes.len() && bytes[end].is_ascii_digit() { - end += 1; - } - text[..end] - .parse::() - .map_or(OwnedValue::Integer(0), OwnedValue::Integer) -} - -/// When casting a TEXT value to REAL, the longest possible prefix of the value that can be interpreted -/// as a real number is extracted from the TEXT value and the remainder ignored. Any leading spaces in -/// the TEXT value are ignored when converging from TEXT to REAL. -/// If there is no prefix that can be interpreted as a real number, the result of the conversion is 0.0. -fn cast_text_to_real(text: &str) -> OwnedValue { - let trimmed = text.trim(); - if trimmed.is_empty() { - return OwnedValue::Float(0.0); - } - if let Ok(num) = trimmed.parse::() { - return OwnedValue::Float(num); - } - let Ok((_, text)) = parse_numeric_str(trimmed) else { - return OwnedValue::Float(0.0); - }; - text.parse::() - .map_or(OwnedValue::Float(0.0), OwnedValue::Float) -} - -/// NUMERIC Casting a TEXT or BLOB value into NUMERIC yields either an INTEGER or a REAL result. -/// If the input text looks like an integer (there is no decimal point nor exponent) and the value -/// is small enough to fit in a 64-bit signed integer, then the result will be INTEGER. -/// Input text that looks like floating point (there is a decimal point and/or an exponent) -/// and the text describes a value that can be losslessly converted back and forth between IEEE 754 -/// 64-bit float and a 51-bit signed integer, then the result is INTEGER. (In the previous sentence, -/// a 51-bit integer is specified since that is one bit less than the length of the mantissa of an -/// IEEE 754 64-bit float and thus provides a 1-bit of margin for the text-to-float conversion operation.) -/// Any text input that describes a value outside the range of a 64-bit signed integer yields a REAL result. -/// Casting a REAL or INTEGER value to NUMERIC is a no-op, even if a real value could be losslessly converted to an integer. -pub fn checked_cast_text_to_numeric(text: &str) -> std::result::Result { - // sqlite will parse the first N digits of a string to numeric value, then determine - // whether _that_ value is more likely a real or integer value. e.g. - // '-100234-2344.23e14' evaluates to -100234 instead of -100234.0 - let (kind, text) = parse_numeric_str(text)?; - match kind { - OwnedValueType::Integer => Ok(text - .parse::() - .map_or(OwnedValue::Integer(0), OwnedValue::Integer)), - OwnedValueType::Float => Ok(text - .parse::() - .map_or(OwnedValue::Float(0.0), OwnedValue::Float)), - _ => unreachable!(), - } -} - -fn parse_numeric_str(text: &str) -> Result<(OwnedValueType, &str), ()> { - let bytes = text.trim_start().as_bytes(); - let mut end = 0; - let mut has_decimal = false; - let mut has_exponent = false; - if bytes[0] == b'-' { - end = 1; - } - while end < bytes.len() { - match bytes[end] { - b'0'..=b'9' => end += 1, - b'.' if !has_decimal && !has_exponent => { - has_decimal = true; - end += 1; - } - b'e' | b'E' if !has_exponent => { - has_exponent = true; - end += 1; - // allow exponent sign - if end < bytes.len() && (bytes[end] == b'+' || bytes[end] == b'-') { - end += 1; - } - } - _ => break, - } - } - if end == 0 || (end == 1 && bytes[0] == b'-') { - return Err(()); - } - Ok(( - if !has_decimal && !has_exponent { - OwnedValueType::Integer - } else { - OwnedValueType::Float - }, - &text[..end], - )) -} - -fn cast_text_to_numeric(txt: &str) -> OwnedValue { - checked_cast_text_to_numeric(txt).unwrap_or(OwnedValue::Integer(0)) -} - -// Check if float can be losslessly converted to 51-bit integer -fn cast_real_to_integer(float: f64) -> std::result::Result { - let i = float as i64; - if float == i as f64 && i.abs() < (1i64 << 51) { - return Ok(i); - } - Err(()) -} - fn execute_sqlite_version(version_integer: i64) -> String { let major = version_integer / 1_000_000; let minor = (version_integer % 1_000_000) / 1_000; diff --git a/testing/math.test b/testing/math.test index 7e5084638..5774be932 100644 --- a/testing/math.test +++ b/testing/math.test @@ -340,8 +340,8 @@ do_execsql_test multiply-agg-float-agg-int { } {41118.0} do_execsql_test multiply-str-floats-edgecase { - SELECT '-123.22342-24' * '232.3x32'; -} {-28624.800466} + SELECT '-123.22341-24' * '232.3x32'; +} {-28624.798143} do_execsql_test divide-agg-int {