Skip to content

Commit a845bdc

Browse files
authored
Merge pull request #11 from rainlanguage/float-negate
2 parents 38f55d7 + e695e7f commit a845bdc

File tree

9 files changed

+428
-73
lines changed

9 files changed

+428
-73
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
use super::*;
2+
3+
// Helper to negate a Rain Float hex string while keeping full precision by
4+
// operating on the binary representation directly.
5+
fn float_negate_hex_to_hex(input_hex: &str) -> Result<String, String> {
6+
let trimmed = input_hex.trim();
7+
8+
if trimmed.is_empty() {
9+
return Err("Empty string is not a valid hex number".to_string());
10+
}
11+
12+
// Parse the input hex into a Float
13+
let float_val =
14+
Float::from_hex(trimmed).map_err(|e| format!("Failed to parse Float hex: {e}"))?;
15+
16+
// Negate the float directly to avoid any formatting or precision loss.
17+
let neg_float = (-float_val).map_err(|e| format!("Failed to negate Float value: {e}"))?;
18+
19+
// Return as hex string
20+
Ok(neg_float.as_hex())
21+
}
22+
23+
// SQLite scalar function wrapper: FLOAT_NEGATE(hex_text)
24+
pub unsafe extern "C" fn float_negate(
25+
context: *mut sqlite3_context,
26+
argc: c_int,
27+
argv: *mut *mut sqlite3_value,
28+
) {
29+
if argc != 1 {
30+
sqlite3_result_error(
31+
context,
32+
c"FLOAT_NEGATE() requires exactly 1 argument".as_ptr(),
33+
-1,
34+
);
35+
return;
36+
}
37+
38+
// Return early for NULL inputs using the documented type check.
39+
if sqlite3_value_type(*argv) == SQLITE_NULL {
40+
sqlite3_result_null(context);
41+
return;
42+
}
43+
44+
// Get the text value (now known to be non-NULL).
45+
let value_ptr = sqlite3_value_text(*argv);
46+
47+
let value_cstr = CStr::from_ptr(value_ptr as *const c_char);
48+
let value_str = match value_cstr.to_str() {
49+
Ok(value_str) => value_str,
50+
Err(_) => {
51+
sqlite3_result_error(context, c"invalid UTF-8".as_ptr(), -1);
52+
return;
53+
}
54+
};
55+
56+
match float_negate_hex_to_hex(value_str) {
57+
Ok(result_hex) => {
58+
if let Ok(result_cstr) = CString::new(result_hex) {
59+
sqlite3_result_text(
60+
context,
61+
result_cstr.as_ptr(),
62+
result_cstr.as_bytes().len() as c_int,
63+
SQLITE_TRANSIENT(),
64+
);
65+
} else {
66+
sqlite3_result_error(context, c"Failed to create result string".as_ptr(), -1);
67+
}
68+
}
69+
Err(e) => match CString::new(e) {
70+
Ok(error_msg) => {
71+
sqlite3_result_error(context, error_msg.as_ptr(), -1);
72+
}
73+
Err(_) => {
74+
sqlite3_result_error(
75+
context,
76+
c"Error message contained interior NUL".as_ptr(),
77+
-1,
78+
);
79+
}
80+
},
81+
}
82+
}
83+
84+
#[cfg(test)]
85+
mod tests {
86+
use super::*;
87+
use wasm_bindgen_test::*;
88+
89+
#[wasm_bindgen_test]
90+
fn test_float_negate_hex_to_hex_pos_to_neg() {
91+
let pos_hex = Float::parse("1.5".to_string()).unwrap().as_hex();
92+
let expected_neg_hex = Float::parse("-1.5".to_string()).unwrap().as_hex();
93+
let out = float_negate_hex_to_hex(&pos_hex).unwrap();
94+
assert_eq!(out, expected_neg_hex);
95+
}
96+
97+
#[wasm_bindgen_test]
98+
fn test_float_negate_hex_to_hex_neg_to_pos() {
99+
let neg_hex = Float::parse("-2.25".to_string()).unwrap().as_hex();
100+
let expected_pos_hex = Float::parse("2.25".to_string()).unwrap().as_hex();
101+
let out = float_negate_hex_to_hex(&neg_hex).unwrap();
102+
assert_eq!(out, expected_pos_hex);
103+
}
104+
105+
#[wasm_bindgen_test]
106+
fn test_float_negate_hex_to_hex_zero() {
107+
let zero_hex = Float::parse("0".to_string()).unwrap().as_hex();
108+
let expected_zero_hex = Float::parse("0".to_string()).unwrap().as_hex();
109+
let out = float_negate_hex_to_hex(&zero_hex).unwrap();
110+
assert_eq!(out, expected_zero_hex);
111+
}
112+
113+
#[wasm_bindgen_test]
114+
fn test_float_negate_hex_to_hex_high_precision() {
115+
let input = "300.123456789012345678";
116+
let in_hex = Float::parse(input.to_string()).unwrap().as_hex();
117+
let expected_hex = Float::parse(format!("-{input}")).unwrap().as_hex();
118+
let out = float_negate_hex_to_hex(&in_hex).unwrap();
119+
assert_eq!(out, expected_hex);
120+
}
121+
122+
#[wasm_bindgen_test]
123+
fn test_float_negate_hex_to_hex_whitespace() {
124+
let in_hex = Float::parse("10".to_string()).unwrap().as_hex();
125+
let wrapped = format!(" {in_hex} ");
126+
let expected_hex = Float::parse("-10".to_string()).unwrap().as_hex();
127+
let out = float_negate_hex_to_hex(&wrapped).unwrap();
128+
assert_eq!(out, expected_hex);
129+
}
130+
131+
#[wasm_bindgen_test]
132+
fn test_float_negate_hex_to_hex_invalid() {
133+
assert!(float_negate_hex_to_hex("0XBADHEX").is_err());
134+
assert!(float_negate_hex_to_hex("").is_err());
135+
assert!(float_negate_hex_to_hex("not_hex").is_err());
136+
}
137+
}

packages/sqlite-web-core/src/database_functions/float_sum.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -186,16 +186,18 @@ mod tests {
186186
fn test_float_sum_context_add_hex_without_prefix() {
187187
let mut context = FloatSumContext::new();
188188

189-
assert!(context
190-
.add_value("ffffffff0000000000000000000000000000000000000000000000000000000f")
191-
.is_ok()); // 1.5
189+
let one_point_five = Float::parse("1.5".to_string()).unwrap().as_hex();
190+
let one_point_five_no_prefix = one_point_five.trim_start_matches("0x").to_string();
191+
192+
assert!(context.add_value(&one_point_five_no_prefix).is_ok()); // 1.5
192193
let result_hex = context.get_total_as_hex().unwrap();
193194
let result_decimal = Float::from_hex(&result_hex).unwrap().format().unwrap();
194195
assert_eq!(result_decimal, "1.5");
195196

196-
assert!(context
197-
.add_value("fffffffe000000000000000000000000000000000000000000000000000000e1")
198-
.is_ok()); // 2.25
197+
let two_point_two_five = Float::parse("2.25".to_string()).unwrap().as_hex();
198+
let two_point_two_five_no_prefix = two_point_two_five.trim_start_matches("0x").to_string();
199+
200+
assert!(context.add_value(&two_point_two_five_no_prefix).is_ok()); // 2.25
199201
let result_hex = context.get_total_as_hex().unwrap();
200202
let result_decimal = Float::from_hex(&result_hex).unwrap().format().unwrap();
201203
assert_eq!(result_decimal, "3.75"); // 1.5 + 2.25 = 3.75
@@ -205,12 +207,17 @@ mod tests {
205207
fn test_float_sum_context_add_uppercase_hex() {
206208
let mut context = FloatSumContext::new();
207209

208-
assert!(context
209-
.add_value("0XFFFFFFFB0000000000000000000000000000000000000000000000000004CB2F")
210-
.is_err()); // Should fail - uppercase 0X not supported
211-
assert!(context
212-
.add_value("0XFFFFFFFF00000000000000000000000000000000000000000000000000000069")
213-
.is_err()); // Should fail - uppercase 0X not supported
210+
let upper_case_bad = Float::parse("-12345.6789".to_string())
211+
.unwrap()
212+
.as_hex()
213+
.replacen("0x", "0X", 1);
214+
assert!(context.add_value(&upper_case_bad).is_err()); // Should fail - uppercase 0X not supported
215+
216+
let another_upper = Float::parse("1024.125".to_string())
217+
.unwrap()
218+
.as_hex()
219+
.replacen("0x", "0X", 1);
220+
assert!(context.add_value(&another_upper).is_err()); // Should fail - uppercase 0X not supported
214221
}
215222

216223
#[wasm_bindgen_test]

packages/sqlite-web-core/src/database_functions/mod.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ use std::str::FromStr;
88

99
// Import the individual function modules
1010
mod bigint_sum;
11+
mod float_negate;
1112
mod float_sum;
1213
mod rain_math;
1314

1415
use bigint_sum::*;
16+
use float_negate::*;
1517
use float_sum::*;
1618

1719
pub use rain_math::*;
@@ -78,6 +80,26 @@ pub fn register_custom_functions(db: *mut sqlite3) -> Result<(), String> {
7880
return Err("Failed to register FLOAT_SUM function".to_string());
7981
}
8082

83+
// Register FLOAT_NEGATE scalar function
84+
let float_negate_name = CString::new("FLOAT_NEGATE").unwrap();
85+
let ret = unsafe {
86+
sqlite3_create_function_v2(
87+
db,
88+
float_negate_name.as_ptr(),
89+
1, // 1 argument
90+
SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS,
91+
std::ptr::null_mut(),
92+
Some(float_negate), // xFunc for scalar
93+
None, // No xStep
94+
None, // No xFinal
95+
None, // No destructor
96+
)
97+
};
98+
99+
if ret != SQLITE_OK {
100+
return Err("Failed to register FLOAT_NEGATE function".to_string());
101+
}
102+
81103
Ok(())
82104
}
83105

@@ -86,8 +108,6 @@ mod tests {
86108
use super::*;
87109
use wasm_bindgen_test::*;
88110

89-
wasm_bindgen_test_configure!(run_in_browser);
90-
91111
#[wasm_bindgen_test]
92112
fn test_cstring_conversion() {
93113
let test_string = "test string with spaces and symbols!@#$%";

svelte-test/package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

svelte-test/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@
4141
"type": "module",
4242
"dependencies": {
4343
"@rainlanguage/float": "^0.0.0-alpha.22",
44-
"@rainlanguage/sqlite-web": "file:../pkg/rainlanguage-sqlite-web-0.0.1-alpha.0.tgz"
44+
"@rainlanguage/sqlite-web": "file:../pkg/rainlanguage-sqlite-web-0.0.1-alpha.1.tgz"
4545
}
4646
}

0 commit comments

Comments
 (0)