fix: f64 转字符串 ryu 路径负数改追加防破坏非空缓冲前缀
This commit is contained in:
@@ -367,11 +367,12 @@ pub fn write_number_into(d: f64, out: &mut String) {
|
|||||||
}
|
}
|
||||||
let neg = d.is_sign_negative();
|
let neg = d.is_sign_negative();
|
||||||
let abs = d.abs();
|
let abs = d.abs();
|
||||||
|
// 有限非零负数先追加负号(追加语义:插到当前数值之前,不扰动 out 已有前缀)。
|
||||||
|
if neg {
|
||||||
|
out.push('-');
|
||||||
|
}
|
||||||
// 整数 double 快路径:无小数部分且在 2^53 内(精确整数,直写与最短表示一致)。
|
// 整数 double 快路径:无小数部分且在 2^53 内(精确整数,直写与最短表示一致)。
|
||||||
if abs.fract() == 0.0 && abs < 9_007_199_254_740_992.0 {
|
if abs.fract() == 0.0 && abs < 9_007_199_254_740_992.0 {
|
||||||
if neg {
|
|
||||||
out.push('-');
|
|
||||||
}
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let _ = write!(out, "{}", abs as i64);
|
let _ = write!(out, "{}", abs as i64);
|
||||||
return;
|
return;
|
||||||
@@ -428,9 +429,6 @@ pub fn write_number_into(d: f64, out: &mut String) {
|
|||||||
} else {
|
} else {
|
||||||
out.push_str(s);
|
out.push_str(s);
|
||||||
}
|
}
|
||||||
if neg {
|
|
||||||
out.insert(0, '-');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ToUint32(ECMA-262 §7.1.6):对数值取模 2^32。NaN/±0/Infinity 归零。
|
/// 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<f64> = 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]
|
#[test]
|
||||||
fn parse_js_number_rules() {
|
fn parse_js_number_rules() {
|
||||||
assert_eq!(parse_js_number(""), 0.0);
|
assert_eq!(parse_js_number(""), 0.0);
|
||||||
|
|||||||
@@ -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]
|
#[test]
|
||||||
fn eval_arithmetic_add() {
|
fn eval_arithmetic_add() {
|
||||||
assert_eq!(eval("1 + 2"), "3");
|
assert_eq!(eval("1 + 2"), "3");
|
||||||
@@ -64,6 +78,18 @@ fn eval_string_concat() {
|
|||||||
assert_eq!(eval("1 + 2 + 3"), "6");
|
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]
|
#[test]
|
||||||
fn eval_compound_add() {
|
fn eval_compound_add() {
|
||||||
assert_eq!(eval("var x=5; x+=3; x"), "8");
|
assert_eq!(eval("var x=5; x+=3; x"), "8");
|
||||||
|
|||||||
@@ -110,6 +110,15 @@ fn concat_n_right_parentheses_not_flattened() {
|
|||||||
assert_eq!(eval_str("0.1 + (0.2 + 0.3)"), "0.6");
|
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]
|
#[test]
|
||||||
fn concat_n_two_and_multi_operand_equivalence() {
|
fn concat_n_two_and_multi_operand_equivalence() {
|
||||||
// 同一语义表达式以 2 操作数(ADD)与 3+ 操作数(CONCAT_N)两种写法输出一致。
|
// 同一语义表达式以 2 操作数(ADD)与 3+ 操作数(CONCAT_N)两种写法输出一致。
|
||||||
|
|||||||
@@ -115,6 +115,16 @@ fn json_stringify_array() {
|
|||||||
assert_eq!(s, "[1,2,3]");
|
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]
|
#[test]
|
||||||
fn json_stringify_string() {
|
fn json_stringify_string() {
|
||||||
let mut vm = Vm::new();
|
let mut vm = Vm::new();
|
||||||
|
|||||||
@@ -99,6 +99,13 @@ fn template_with_double_bigint_and_bool() {
|
|||||||
assert_eq!(to_str(&vm, result.unwrap()), "a1.5b123ctrued");
|
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]
|
#[test]
|
||||||
fn template_with_object_uses_tostring() {
|
fn template_with_object_uses_tostring() {
|
||||||
// 对象段保留完整 ToString(ToPrimitive 触发用户 toString)。
|
// 对象段保留完整 ToString(ToPrimitive 触发用户 toString)。
|
||||||
|
|||||||
Reference in New Issue
Block a user