333 lines
11 KiB
Rust
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");
|
|
}
|