Files
core/test/rust/native/numeric/mod.rs
T
2026-11-03 03:32:07 +00:00

333 lines
11 KiB
Rust

use super::{Directory, fixture};
use dever::compiler::{build, emit, project::Project};
use dever_runtime::value::decimal::Decimal;
#[test]
fn decimal_constant_bytes_preserve_sign_quantum_and_full_representation() {
const ONE: Decimal = Decimal::from_be_bytes([34, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
const NEGATIVE_ZERO: Decimal =
Decimal::from_be_bytes([162, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(
ONE.to_be_bytes(),
Decimal::parse("1").unwrap().to_be_bytes()
);
assert_eq!(
NEGATIVE_ZERO.to_be_bytes(),
Decimal::parse("-0").unwrap().to_be_bytes()
);
// 除数值相等外,还核对编码;不能丢掉负零、尾零或极端指数。
for text in [
"-0.00",
"0E+100",
"1.2300",
"-1.000000000000000000000000000000001",
"1E-6176",
"9.999999999999999999999999999999999E+6144",
] {
let original = Decimal::parse(text).unwrap();
let restored = Decimal::from_be_bytes(original.to_be_bytes());
assert_eq!(restored, original, "{text}");
assert_eq!(restored.to_be_bytes(), original.to_be_bytes(), "{text}");
}
for seed in 0..256u16 {
let bytes = std::array::from_fn(|index| (seed + index as u16 * 17) as u8);
let original = dec::Decimal128::from_be_bytes(bytes);
let restored = Decimal::from_be_bytes(bytes);
assert_eq!(restored.to_be_bytes(), original.to_be_bytes());
}
}
#[test]
fn numeric_clauses_execute_with_nullable_values_nan_infinity_and_signed_zero() {
let directory = Directory::new();
let path = fixture().with_file_name("numeric.dever");
let project = Project::load(&path).unwrap();
let program = project.check().unwrap();
let source = emit::generate(
&program,
&project.sources,
&(0..program.functions.len()).collect(),
);
assert!(source.contains("Decimal::from_be_bytes"));
assert!(!source.contains("Decimal::parse"));
let binary = build::build(
&program,
&project.sources,
"verify",
&directory.0.join("numeric"),
)
.unwrap();
let result = std::process::Command::new(binary).output().unwrap();
assert!(
result.status.success(),
"{}",
String::from_utf8_lossy(&result.stderr)
);
assert_eq!(result.stdout, b"true\n");
}
#[test]
fn integer_decimal_conversion_preserves_full_encoding_at_narrowing_boundaries() {
let verify = |value| {
assert_eq!(
Decimal::from_int(value).to_be_bytes(),
dec::Decimal128::from(value).to_be_bytes(),
"{value}"
);
};
for value in [
i64::MIN,
i64::MAX,
-1,
0,
1,
i64::from(i32::MIN) - 1,
i64::from(i32::MIN),
i64::from(i32::MIN) + 1,
i64::from(i32::MAX) - 1,
i64::from(i32::MAX),
i64::from(i32::MAX) + 1,
i64::from(u32::MAX) - 1,
i64::from(u32::MAX),
i64::from(u32::MAX) + 1,
] {
verify(value);
}
let mut state = 0x9e3779b97f4a7c15u64;
for _ in 0..256 {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
verify(state as i64);
verify(i64::from(state as i32));
verify(i64::from(state as u32));
}
}
#[test]
fn decimal_sum_preserves_rounding_first_fault_and_context_independence() {
let maximum = "9.999999999999999999999999999999999E+6144";
for texts in [
vec![],
vec!["-0.00"],
vec!["0.1", "0.2", "-0.05"],
vec!["1E-6176", "1E-6176"],
vec![
"1000000000000000000000000000000000",
"0.1",
"-1000000000000000000000000000000000",
],
vec![
"1000000000000000000000000000000000",
"-1000000000000000000000000000000000",
"0.1",
],
vec![maximum, "1"],
vec![
maximum,
maximum,
"-9.999999999999999999999999999999999E+6144",
],
] {
let values = texts
.iter()
.map(|text| Decimal::parse(text).unwrap())
.collect::<Vec<_>>();
let before = values
.iter()
.map(|value| value.to_be_bytes())
.collect::<Vec<_>>();
let mut context = dec::Context::<dec::Decimal128>::default();
let expected = values
.iter()
.try_fold(dec::Decimal128::ZERO, |total, value| {
context.clear_status();
let value = context.add(total, dec::Decimal128::from_be_bytes(value.to_be_bytes()));
(value.is_finite()
&& !context.status().overflow()
&& !context.status().invalid_operation())
.then_some(value)
});
assert_eq!(
Decimal::sum(&values).map(Decimal::to_be_bytes),
expected.map(|value| value.to_be_bytes()),
"{texts:?}"
);
assert_eq!(
values
.iter()
.map(|value| value.to_be_bytes())
.collect::<Vec<_>>(),
before
);
}
let one = Decimal::from_int(1);
let mut context = Decimal::context();
assert!(
one.checked_div(Decimal::from_int(0), &mut context)
.is_none()
);
assert_eq!(Decimal::sum(&[one, one]), Some(Decimal::from_int(2)));
assert_eq!(
one.checked_add(one, &mut context),
Some(Decimal::from_int(2))
);
}
#[test]
fn decimal_sum_executes_through_generated_code() {
let directory = Directory::new();
let project = Project::load(&fixture().with_file_name("decimal.dever")).unwrap();
let program = project.check().unwrap();
let binary = build::build(
&program,
&project.sources,
"verify",
&directory.0.join("decimal"),
)
.unwrap();
let output = std::process::Command::new(binary).output().unwrap();
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
assert_eq!(output.stdout, b"true\n");
}
#[test]
fn decimal_operations_keep_local_status_across_threads() {
let start = std::sync::Barrier::new(4);
std::thread::scope(|scope| {
for _ in 0..4 {
scope.spawn(|| {
start.wait();
let one = Decimal::from_int(1);
let two = Decimal::from_int(2);
let maximum = Decimal::parse("9.999999999999999999999999999999999E+6144").unwrap();
let mut context = Decimal::context();
for _ in 0..32 {
assert!(maximum.checked_mul(two, &mut context).is_none());
assert_eq!(one.checked_add(one, &mut context), Some(two));
assert!(one.checked_div(Decimal::ZERO, &mut context).is_none());
assert_eq!(two.checked_sub(one, &mut context), Some(one));
assert!(Decimal::parse("0.12345678901234567890123456789012345").is_err());
assert_eq!(
Decimal::parse("1.25").unwrap(),
one.checked_div(Decimal::parse("0.8").unwrap(), &mut context)
.unwrap()
);
assert_eq!(Decimal::sum(&[one, one]), Some(two));
}
});
}
});
}
#[test]
fn decimal_status_matches_finite_results_at_arithmetic_boundaries() {
let texts = [
"0",
"-0.00",
"1",
"-1",
"2",
"3",
"0.1",
"-0.1",
"1.000000000000000000000000000000001",
"0.9999999999999999999999999999999999",
"9999999999999999999999999999999999",
"1E-6176",
"-1E-6176",
"1E-6143",
"-1E-6143",
"1E+6144",
"-1E+6144",
"9.999999999999999999999999999999999E+6144",
"-9.999999999999999999999999999999999E+6144",
];
let values = texts.map(|text| Decimal::parse(text).unwrap());
let mut actual_context = Decimal::context();
let mut reference_context = dec::Context::<dec::Decimal128>::default();
for left in values {
for right in values {
for operation in 0..4 {
reference_context.clear_status();
let native_left = dec::Decimal128::from_be_bytes(left.to_be_bytes());
let native_right = dec::Decimal128::from_be_bytes(right.to_be_bytes());
let expected = match operation {
0 => reference_context.add(native_left, native_right),
1 => reference_context.sub(native_left, native_right),
2 => reference_context.mul(native_left, native_right),
3 => reference_context.div(native_left, native_right),
_ => unreachable!(),
};
let expected = (expected.is_finite()
&& !reference_context.status().overflow()
&& !reference_context.status().invalid_operation())
.then(|| expected.to_be_bytes());
let actual = match operation {
0 => left.checked_add(right, &mut actual_context),
1 => left.checked_sub(right, &mut actual_context),
2 => left.checked_mul(right, &mut actual_context),
3 => left.checked_div(right, &mut actual_context),
_ => unreachable!(),
};
assert_eq!(
actual.map(Decimal::to_be_bytes),
expected,
"{left} operation {operation} {right}"
);
}
}
for places in [-6112, -6111, -614, -1, 0, 1, 33, 6143, 6176, 6177] {
reference_context.clear_status();
let expected = if (-6176..=6111).contains(&-places) {
let mut quantum = dec::Decimal128::ONE;
reference_context.set_exponent(&mut quantum, -places);
let value = reference_context
.quantize(dec::Decimal128::from_be_bytes(left.to_be_bytes()), quantum);
(value.is_finite()
&& !reference_context.status().overflow()
&& !reference_context.status().invalid_operation()
&& !reference_context.status().underflow()
&& !reference_context.status().invalid_context())
.then(|| value.to_be_bytes())
} else {
None
};
assert_eq!(
left.round(i64::from(places)).map(Decimal::to_be_bytes),
expected
);
}
}
for invalid in ["NaN", "sNaN", "Infinity", "-Infinity", "1E+6145"] {
assert!(Decimal::parse(invalid).is_err());
}
}
#[test]
fn range_contracts_execute_without_runtime_wrappers() {
let directory = super::Directory::new();
let project =
dever::compiler::project::Project::load(&super::fixture().with_file_name("ranges.dever"))
.unwrap();
let program = project.check().unwrap();
let binary = dever::compiler::build::build(
&program,
&project.sources,
"verify",
&directory.0.join("ranges"),
)
.unwrap();
let result = std::process::Command::new(binary).output().unwrap();
assert!(
result.status.success(),
"{}",
String::from_utf8_lossy(&result.stderr)
);
assert_eq!(result.stdout, b"true\n");
}