From 3eec7fb7ff793a96ada7995dda6576f0290085c1 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 15 Aug 2026 17:35:27 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20f64=20=E8=BD=AC=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E4=B8=B2=20ryu=20=E8=B7=AF=E5=BE=84=E8=B4=9F=E6=95=B0=E6=94=B9?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=E9=98=B2=E7=A0=B4=E5=9D=8F=E9=9D=9E=E7=A9=BA?= =?UTF-8?q?=E7=BC=93=E5=86=B2=E5=89=8D=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_runtime_api/src/lib.rs | 32 ++++++++++++++++++----- crates/oxide_vm/tests/arithmetic_tests.rs | 26 ++++++++++++++++++ crates/oxide_vm/tests/concat_n_tests.rs | 9 +++++++ crates/oxide_vm/tests/json_tests.rs | 10 +++++++ crates/oxide_vm/tests/template_tests.rs | 7 +++++ 5 files changed, 78 insertions(+), 6 deletions(-) diff --git a/crates/oxide_runtime_api/src/lib.rs b/crates/oxide_runtime_api/src/lib.rs index a6aeddd..0608b19 100644 --- a/crates/oxide_runtime_api/src/lib.rs +++ b/crates/oxide_runtime_api/src/lib.rs @@ -367,11 +367,12 @@ pub fn write_number_into(d: f64, out: &mut String) { } let neg = d.is_sign_negative(); let abs = d.abs(); + // 有限非零负数先追加负号(追加语义:插到当前数值之前,不扰动 out 已有前缀)。 + if neg { + out.push('-'); + } // 整数 double 快路径:无小数部分且在 2^53 内(精确整数,直写与最短表示一致)。 if abs.fract() == 0.0 && abs < 9_007_199_254_740_992.0 { - if neg { - out.push('-'); - } use std::fmt::Write; let _ = write!(out, "{}", abs as i64); return; @@ -428,9 +429,6 @@ pub fn write_number_into(d: f64, out: &mut String) { } else { out.push_str(s); } - if neg { - out.insert(0, '-'); - } } /// ToUint32(ECMA-262 §7.1.6):对数值取模 2^32。NaN/±0/Infinity 归零。 @@ -1041,6 +1039,28 @@ mod tests { } } + #[test] + fn push_to_string_negative_double_appends_to_nonempty_buffer() { + // 追加语义回归:非空前缀下负数 double 的负号必须紧跟当前数值,不能插到 + // 整个缓冲最前。覆盖整数快路径、ryu 定点(≥2^53)、ryu 科学计数与边界值。 + let samples: Vec = vec![ + -42.0, // 整数快路径 + -1.5, // ryu 定点 + -9_007_199_254_740_994.0, // 2^53+2:≥2^53 可精确表示整数,落 ryu 路径 + -2f64.powi(63), // ryu 科学计数(规范最短表示) + -1e21, // ryu 科学计数 + -1e-7, // ryu 科学计数(小指数) + -f64::MAX, + -5e-324, + ]; + for &d in &samples { + let mut buf = String::from("pre"); + push_to_string(JsValue::float(d), &mut buf); + let expected = format!("pre{}", js_number_to_string(d)); + assert_eq!(buf, expected, "非空前缀 + double {d} 的追加语义破坏,实际 {buf}"); + } + } + #[test] fn parse_js_number_rules() { assert_eq!(parse_js_number(""), 0.0); diff --git a/crates/oxide_vm/tests/arithmetic_tests.rs b/crates/oxide_vm/tests/arithmetic_tests.rs index 404e7e1..5db2615 100644 --- a/crates/oxide_vm/tests/arithmetic_tests.rs +++ b/crates/oxide_vm/tests/arithmetic_tests.rs @@ -19,6 +19,20 @@ fn eval(source: &str) -> String { } } +/// 求值并取字符串结果内容(字符串值走 lookup_str,非字符串走 Display)。 +fn eval_str(source: &str) -> String { + let allocator = Allocator::default(); + let program = oxide_parser::parse(&allocator, source).expect("parse ok"); + let module = Compiler::new().compile(&program).expect("compile ok"); + let mut vm = Vm::new(); + let val = vm.run(&module).expect("run ok"); + if val.is_string() { + vm.lookup_str(val).unwrap_or_default() + } else { + format!("{val}") + } +} + #[test] fn eval_arithmetic_add() { assert_eq!(eval("1 + 2"), "3"); @@ -64,6 +78,18 @@ fn eval_string_concat() { assert_eq!(eval("1 + 2 + 3"), "6"); } +#[test] +fn eval_string_concat_negative_double_appends() { + // 非空缓冲 + ryu 路径负数:负号紧跟当前数值追加,不插到整个结果最前。 + assert_eq!(eval_str("\"a\" + -1.5"), "a-1.5"); + assert_eq!(eval_str("\"a\" + (-1e21)"), "a-1e+21"); + // 整数负数(int 直写)与整数 double 快路径负数同样追加正确。 + assert_eq!(eval_str("\"a\" + -42"), "a-42"); + assert_eq!(eval_str("\"a\" + (-42.0)"), "a-42"); + // ≥2^53 可精确表示整数负数(2^53+2)落 ryu 定点路径,追加语义保持一致。 + assert_eq!(eval_str("\"a\" + -9007199254740994"), "a-9007199254740994"); +} + #[test] fn eval_compound_add() { assert_eq!(eval("var x=5; x+=3; x"), "8"); diff --git a/crates/oxide_vm/tests/concat_n_tests.rs b/crates/oxide_vm/tests/concat_n_tests.rs index 7cc09ad..b12f8d1 100644 --- a/crates/oxide_vm/tests/concat_n_tests.rs +++ b/crates/oxide_vm/tests/concat_n_tests.rs @@ -110,6 +110,15 @@ fn concat_n_right_parentheses_not_flattened() { assert_eq!(eval_str("0.1 + (0.2 + 0.3)"), "0.6"); } +#[test] +fn concat_n_negative_double_appends() { + // CONCAT_N 单趟拼接(≥3 操作数):非空缓冲 + 负 double,负号追加在当前数前。 + assert_eq!(eval_str("'a' + 'b' + (-1.5)"), "ab-1.5"); + assert_eq!(eval_str("'a' + 'b' + (-1e21)"), "ab-1e+21"); + // ≥2^53 可精确表示整数负数(2^53+2)落 ryu 定点路径,追加语义保持一致。 + assert_eq!(eval_str("'a' + 'b' + (-9007199254740994)"), "ab-9007199254740994"); +} + #[test] fn concat_n_two_and_multi_operand_equivalence() { // 同一语义表达式以 2 操作数(ADD)与 3+ 操作数(CONCAT_N)两种写法输出一致。 diff --git a/crates/oxide_vm/tests/json_tests.rs b/crates/oxide_vm/tests/json_tests.rs index f7a495e..05d3ef4 100644 --- a/crates/oxide_vm/tests/json_tests.rs +++ b/crates/oxide_vm/tests/json_tests.rs @@ -115,6 +115,16 @@ fn json_stringify_array() { assert_eq!(s, "[1,2,3]"); } +#[test] +fn json_stringify_array_with_negative_double() { + // 数组内负 double:out 已含 "[1,",负号必须追加在当前数前(ryu 路径)。 + let mut vm = Vm::new(); + let result = eval(&mut vm, "JSON.stringify([1,-1.5])").unwrap(); + assert!(result.is_string()); + let s = string_value(&vm, result); + assert_eq!(s, "[1,-1.5]"); +} + #[test] fn json_stringify_string() { let mut vm = Vm::new(); diff --git a/crates/oxide_vm/tests/template_tests.rs b/crates/oxide_vm/tests/template_tests.rs index 96a4dc0..1df29f6 100644 --- a/crates/oxide_vm/tests/template_tests.rs +++ b/crates/oxide_vm/tests/template_tests.rs @@ -99,6 +99,13 @@ fn template_with_double_bigint_and_bool() { assert_eq!(to_str(&vm, result.unwrap()), "a1.5b123ctrued"); } +#[test] +fn template_with_negative_double_appends() { + // 原语段负 double:result 已含 "x",负号必须追加在当前数前(ryu 路径)。 + let (vm, result) = eval_val("`x${-1.5}`"); + assert_eq!(to_str(&vm, result.unwrap()), "x-1.5"); +} + #[test] fn template_with_object_uses_tostring() { // 对象段保留完整 ToString(ToPrimitive 触发用户 toString)。