Initial Dever 0.0.1

This commit is contained in:
Codex committed 2026-11-03 03:32:07 +00:00
commit 81233d098c
1479 files changed
+163782

No files matched your search

+79
View File
@@ -0,0 +1,79 @@
// 独立 Rust 参照直接使用相同版本的 dec,逐项清理和检查状态。
// 两侧使用同一输入、Decimal128 精度、舍入和顺序,不用二进制浮点代替。
fn generated(values: &[Decimal]) -> Result<Decimal, Fault> {
GENERATED_DECIMAL_SUM(values)
}
fn reference(values: &[Decimal]) -> Result<Decimal, Fault> {
let mut context = dec::Context::<dec::Decimal128>::default();
let mut total = dec::Decimal128::ZERO;
for value in values {
context.clear_status();
total = context.add(total, dec::Decimal128::from_be_bytes(value.to_be_bytes()));
if !total.is_finite() || context.status().overflow() || context.status().invalid_operation()
{
return Err(&FaultInfo {
message: "Decimal fault",
source: "Rust reference",
line: 0,
column: 0,
});
}
}
Ok(Decimal::from_be_bytes(total.to_be_bytes()))
}
fn score(value: &Decimal) -> u64 {
u64::from_be_bytes(value.to_be_bytes()[8..].try_into().unwrap())
}
fn main() {
let maximum = Decimal::parse("9.999999999999999999999999999999999E+6144").unwrap();
let zero = Decimal::from_int(0);
for values in [
vec![maximum, maximum, -maximum],
vec![-maximum, -maximum, maximum],
vec![zero],
Vec::new(),
] {
let actual = generated(&values);
let expected = reference(&values);
assert_eq!(actual.is_err(), expected.is_err());
if let (Ok(actual), Ok(expected)) = (actual, expected) {
assert_eq!(actual.to_be_bytes(), expected.to_be_bytes());
}
}
let short = [
"0.1", "0.2", "-0.05", "1.25", "-0.00", "10", "-2.50", "0.001",
]
.map(|text| Decimal::parse(text).unwrap());
let integers = (0..256).map(Decimal::from_int).collect::<Vec<_>>();
let palette = [
"-1234567890123456789012345678901234",
"1.000000000000000000000000000000001",
"-0.001",
"-1E-6176",
"-0.00",
"0E+100",
"1E-6176",
"0.9999999999999999999999999999999999",
"1234567890123456789012345678901234",
]
.map(|text| Decimal::parse(text).unwrap());
let mixed = (0..256)
.map(|index| palette[index % palette.len()])
.collect::<Vec<_>>();
println!("case,sample,implementation,elapsed_ns,checksum");
for (name, values, count) in [
("decimal_sum_short", short.as_slice(), 10000),
("decimal_sum_integers", integers.as_slice(), 1000),
("decimal_sum_mixed", mixed.as_slice(), 1000),
] {
assert_eq!(
generated(values).unwrap().to_be_bytes(),
reference(values).unwrap().to_be_bytes()
);
compare(name, values, count, generated, reference, score);
}
}
+203
View File
@@ -0,0 +1,203 @@
// 保留直接 Rust 参照,分别观察值参数、借用参数和 Result 对循环优化的影响。
fn generated(values: &[Option<f64>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += GENERATED_FLOAT(value)?;
}
Ok(total)
}
fn reference(values: &[Option<f64>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += match value {
None => -1,
Some(value) if *value == 1.0 => 1,
Some(value) if *value == 0.0 => 0,
Some(_) => 2,
};
}
Ok(total)
}
fn generated_batch(values: &[Option<f64>]) -> Result<i64, Fault> {
GENERATED_FLOAT_TOTAL(values)
}
fn reference_batch(values: &[Option<f64>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
let projected = match value {
None => -1,
Some(value) if *value == 1.0 => 1,
Some(value) if *value == 0.0 => 0,
Some(_) => 2,
};
total = total.checked_add(projected).ok_or(&FaultInfo {
message: "integer overflow",
source: "Rust reference",
line: 0,
column: 0,
})?;
}
Ok(total)
}
fn classify(value: Option<f64>) -> i64 {
match value {
None => -1,
Some(value) if value == 1.0 => 1,
Some(value) if value == 0.0 => 0,
Some(_) => 2,
}
}
fn classify_result(value: Option<f64>) -> Result<i64, Fault> {
Ok(classify(value))
}
fn classify_borrowed(value: &Option<f64>) -> i64 {
match value {
None => -1,
Some(value) if *value == 1.0 => 1,
Some(value) if *value == 0.0 => 0,
Some(_) => 2,
}
}
fn value_helper(values: &[Option<f64>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += classify(*value);
}
Ok(total)
}
fn result_helper(values: &[Option<f64>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += classify_result(*value)?;
}
Ok(total)
}
fn borrowed_helper(values: &[Option<f64>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += classify_borrowed(value);
}
Ok(total)
}
fn main() {
let special = [
None,
Some(0.0),
Some(-0.0),
Some(1.0),
Some(-1.0),
Some(f64::NAN),
Some(f64::INFINITY),
Some(f64::NEG_INFINITY),
];
let values = (0..256)
.map(|index| special[index % special.len()])
.collect::<Vec<_>>();
let mut state = 0x123456789abcdef0u64;
let mixed = (0..256)
.map(|index| {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
if index % 3 == 0 {
Some(f64::from_bits(state))
} else {
special[((state >> 32) as usize) % special.len()]
}
})
.collect::<Vec<_>>();
let references = [
(
"direct",
reference as fn(&[Option<f64>]) -> Result<i64, Fault>,
),
("value", value_helper),
("result", result_helper),
("borrowed", borrowed_helper),
];
for (name, operation) in references {
for value in values.iter().chain(&mixed) {
assert_eq!(generated(&[*value]).unwrap(), operation(&[*value]).unwrap());
}
eprintln!(
"{name}_equals_generated={}",
std::ptr::fn_addr_eq(
operation,
generated as fn(&[Option<f64>]) -> Result<i64, Fault>
)
);
}
println!("case,sample,implementation,elapsed_ns,checksum");
for (name, values) in [("float_batch", &values), ("float_batch_mixed", &mixed)] {
compare(
name,
values,
10000,
generated_batch,
reference_batch,
|value| *value as u64,
);
}
compare(
"float_direct",
&values,
10000,
generated,
reference,
|value| *value as u64,
);
compare(
"float_mixed",
&mixed,
10000,
generated,
reference,
|value| *value as u64,
);
compare(
"float_value_helper",
&values,
10000,
generated,
value_helper,
|value| *value as u64,
);
compare(
"float_result_helper",
&values,
10000,
generated,
result_helper,
|value| *value as u64,
);
compare(
"float_borrowed_helper",
&values,
10000,
generated,
borrowed_helper,
|value| *value as u64,
);
// 同一混合序列也经过各参照,避免把函数边界或布局差异归给 Dever。
for (name, operation) in [
(
"float_mixed_value_helper",
value_helper as fn(&[Option<f64>]) -> Result<i64, Fault>,
),
("float_mixed_result_helper", result_helper),
("float_mixed_borrowed_helper", borrowed_helper),
] {
compare(name, &mixed, 10000, generated, operation, |value| {
*value as u64
});
}
}
+104
View File
@@ -0,0 +1,104 @@
fn decimal_generated(values: &[Option<Decimal>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += GENERATED_DECIMAL(value)?;
}
Ok(total)
}
fn decimal_reference(values: &[Option<Decimal>]) -> Result<i64, Fault> {
let mut total = 0i64;
let zero = Decimal::from_int(0);
for value in values {
total += match value {
None => 2,
Some(value) if *value < zero => -1,
Some(value) if *value == zero => 0,
Some(_) => 1,
};
}
Ok(total)
}
fn edge_generated(values: &[Decimal]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += GENERATED_EDGE(*value)?;
}
Ok(total)
}
fn edge_reference(values: &[Decimal]) -> Result<i64, Fault> {
let mut total = 0i64;
let one = Decimal::from_int(1);
for value in values {
total += i64::from(*value > one);
}
Ok(total)
}
fn float_generated(values: &[Option<f64>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += GENERATED_FLOAT(value)?;
}
Ok(total)
}
fn float_reference(values: &[Option<f64>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += match value {
None => -1,
Some(value) if *value == 1.0 => 1,
Some(value) if *value == 0.0 => 0,
Some(_) => 2,
};
}
Ok(total)
}
fn main() {
let decimals = (0..256).map(|index| match index % 4 {
0 => None,
1 => Some(Decimal::from_int(-(index as i64))),
2 => Some(Decimal::from_int(0)),
_ => Some(Decimal::from_int(index as i64)),
}).collect::<Vec<_>>();
let edges = ["0", "1", "1.000000000000000000000000000000001", "2"];
let edges = (0..256).map(|index| Decimal::parse(edges[index % 4]).unwrap()).collect::<Vec<_>>();
let special = [None, Some(0.0), Some(-0.0), Some(1.0), Some(-1.0), Some(f64::NAN), Some(f64::INFINITY), Some(f64::NEG_INFINITY)];
let floats = (0..256).map(|index| special[index % special.len()]).collect::<Vec<_>>();
for value in &decimals { assert_eq!(GENERATED_DECIMAL(value).unwrap(), decimal_reference(&[*value]).unwrap()); }
for value in &edges { assert_eq!(GENERATED_EDGE(*value).unwrap(), edge_reference(&[*value]).unwrap()); }
for value in &floats { assert_eq!(GENERATED_FLOAT(value).unwrap(), float_reference(&[*value]).unwrap()); }
println!("case,sample,implementation,elapsed_ns,checksum");
compare("decimal_clauses", &decimals, 10000, decimal_generated, decimal_reference, |value| *value as u64);
compare("decimal_adjacent", &edges, 10000, edge_generated, edge_reference, |value| *value as u64);
compare("float_clauses", &floats, 10000, float_generated, float_reference, |value| *value as u64);
// 独立保留原三组;混合输入覆盖非整数、不同小数位、负零和指数极限。
let spellings = [
"-9.999999999999999999999999999999999E+6144",
"-1234567890123456789012345678901234",
"-1.000000000000000000000000000000001",
"-1.00", "-0.001", "-1E-6176", "-0.00", "0", "0E+100",
"1E-6176", "0.001", "0.9999999999999999999999999999999999",
"1", "1.000", "1.000000000000000000000000000000001",
"1234567890123456789012345678901234",
"9.999999999999999999999999999999999E+6144",
];
let palette = spellings.map(|text| Decimal::parse(text).unwrap());
let mut state = 0x123456789abcdef0u64;
let mixed = (0..256).map(|_| {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
palette[((state >> 32) as usize) % palette.len()]
}).collect::<Vec<_>>();
let nullable = mixed.iter().enumerate().map(|(index, value)| {
if index % 5 == 0 { None } else { Some(*value) }
}).collect::<Vec<_>>();
for value in &nullable { assert_eq!(GENERATED_DECIMAL(value).unwrap(), decimal_reference(&[*value]).unwrap()); }
for value in &mixed { assert_eq!(GENERATED_EDGE(*value).unwrap(), edge_reference(&[*value]).unwrap()); }
compare("decimal_mixed_clauses", &nullable, 10000, decimal_generated, decimal_reference, |value| *value as u64);
compare("decimal_mixed_adjacent", &mixed, 10000, edge_generated, edge_reference, |value| *value as u64);
}
+309
View File
@@ -0,0 +1,309 @@
// 算术参照直接调用 dec,逐步检查同样的错误;不降低精度或重排运算。
use dec::{Context, Decimal128};
const TWO: Decimal128 = Decimal128::from_ne_bytes(
u128::from_be_bytes([34, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]).to_ne_bytes(),
);
// 两套表示在计时前准备,Rust 循环不经过 Dever 的输入/输出转换。
struct Inputs<Generated, Native> {
generated: Vec<Generated>,
native: Vec<Native>,
}
fn inputs<Generated: Copy, Native>(
values: &[Generated],
convert: impl FnMut(Generated) -> Native,
) -> [Inputs<Generated, Native>; 1] {
[Inputs {
generated: values.to_vec(),
native: values.iter().copied().map(convert).collect(),
}]
}
fn native_bytes(value: Decimal128) -> [u8; 16] {
u128::from_ne_bytes(value.to_ne_bytes()).to_be_bytes()
}
fn raw(value: Decimal) -> Decimal128 {
Decimal128::from_be_bytes(value.to_be_bytes())
}
fn checked(value: Decimal128, context: &Context<Decimal128>) -> Result<Decimal128, Fault> {
if value.is_finite() && !context.status().overflow() && !context.status().invalid_operation() {
Ok(value)
} else {
Err(&FaultInfo {
message: "Decimal arithmetic fault",
source: "Rust reference",
line: 0,
column: 0,
})
}
}
fn operation<const KIND: usize>(
context: &mut Context<Decimal128>,
left: Decimal128,
right: Decimal128,
) -> Result<Decimal128, Fault> {
context.clear_status();
let value = match KIND {
0 => context.add(left, right),
1 => context.sub(left, right),
2 => context.mul(left, right),
3 => context.div(left, right),
4 => {
let product = operation::<2>(context, left, right)?;
let added = operation::<0>(context, product, left)?;
let divided = operation::<3>(context, added, right)?;
return operation::<1>(context, divided, left);
}
5 => context.mul(left, TWO),
_ => unreachable!(),
};
checked(value, context)
}
fn generated_one<const KIND: usize>(left: Decimal, right: Decimal) -> Result<Decimal, Fault> {
match KIND {
0 => GENERATED_ADD(left, right),
1 => GENERATED_SUBTRACT(left, right),
2 => GENERATED_MULTIPLY(left, right),
3 => GENERATED_DIVIDE(left, right),
4 => GENERATED_CALCULATE(left, right),
5 => GENERATED_SCALE(left),
_ => unreachable!(),
}
}
fn score(bytes: [u8; 16]) -> u64 {
u64::from_be_bytes(bytes[8..].try_into().unwrap())
}
fn generated<const KIND: usize>(
inputs: &[Inputs<(Decimal, Decimal), (Decimal128, Decimal128)>],
) -> Result<u64, Fault> {
let values = inputs.first().map_or(&[][..], |input| input.generated.as_slice());
let mut total = 0u64;
for (left, right) in values {
total = total.wrapping_add(score(generated_one::<KIND>(*left, *right)?.to_be_bytes()));
}
Ok(total)
}
fn reference<const KIND: usize>(
inputs: &[Inputs<(Decimal, Decimal), (Decimal128, Decimal128)>],
) -> Result<u64, Fault> {
let values = inputs.first().map_or(&[][..], |input| input.native.as_slice());
let mut context = Context::<Decimal128>::default();
let mut total = 0u64;
for (left, right) in values {
let value = operation::<KIND>(&mut context, *left, *right)?;
total = total.wrapping_add(score(native_bytes(value)));
}
Ok(total)
}
fn compare_operation<const KIND: usize>(name: &str, values: &[(Decimal, Decimal)]) {
let mut context = Context::<Decimal128>::default();
let maximum = Decimal::parse("9.999999999999999999999999999999999E+6144").unwrap();
for (left, right) in values
.iter()
.copied()
.chain([(maximum, maximum), (maximum, Decimal::from_int(0))])
{
let actual = generated_one::<KIND>(left, right);
let expected = operation::<KIND>(&mut context, raw(left), raw(right));
assert_eq!(actual.is_err(), expected.is_err());
if let (Ok(actual), Ok(expected)) = (actual, expected) {
assert_eq!(actual.to_be_bytes(), expected.to_be_bytes());
}
}
let inputs = inputs(values, |(left, right)| (raw(left), raw(right)));
compare(
name,
&inputs,
1000,
generated::<KIND>,
reference::<KIND>,
|value| *value,
);
}
fn projected(inputs: &[Inputs<Decimal, Decimal128>]) -> Result<[u8; 16], Fault> {
let values = inputs.first().map_or(&[][..], |input| input.generated.as_slice());
GENERATED_SCALED(values).map(Decimal::to_be_bytes)
}
fn projected_reference(inputs: &[Inputs<Decimal, Decimal128>]) -> Result<[u8; 16], Fault> {
let values = inputs.first().map_or(&[][..], |input| input.native.as_slice());
let mut context = Context::<Decimal128>::default();
let mut total = Decimal128::ZERO;
for value in values {
let next = operation::<5>(&mut context, *value, Decimal128::ZERO)?;
total = operation::<0>(&mut context, total, next)?;
}
Ok(native_bytes(total))
}
fn converted(values: &[i64]) -> Result<u64, Fault> {
let mut total = 0u64;
for value in values {
total = total.wrapping_add(score(GENERATED_CONVERT(*value)?.to_be_bytes()));
}
Ok(total)
}
fn converted_reference(values: &[i64]) -> Result<u64, Fault> {
let mut total = 0u64;
for value in values {
let decimal = if let Ok(narrow) = i32::try_from(*value) {
Decimal128::from(narrow)
} else if let Ok(narrow) = u32::try_from(*value) {
Decimal128::from(narrow)
} else {
Decimal128::from(*value)
};
total = total.wrapping_add(score(native_bytes(decimal)));
}
Ok(total)
}
fn converted_direct_reference(values: &[i64]) -> Result<u64, Fault> {
let mut total = 0u64;
for value in values {
total = total.wrapping_add(score(native_bytes(Decimal128::from(*value))));
}
Ok(total)
}
fn main() {
let constants = GENERATED_CONSTANTS().unwrap();
let expected = [
Decimal128::ZERO,
Context::<Decimal128>::default().parse("-0.00").unwrap(),
Decimal128::from(i64::MIN),
Decimal128::from(i64::MAX),
Decimal128::from(3i64),
];
assert_eq!(constants.len(), expected.len());
for (actual, expected) in constants.iter().zip(expected) {
assert_eq!(actual.to_be_bytes(), expected.to_be_bytes());
}
let left = [
"-0.00",
"1.25",
"-7.5",
"1E-60",
"9999999999999999999999999999999999",
"-123.456",
"0.9999999999999999999999999999999999",
"1234567.890",
]
.map(|text| Decimal::parse(text).unwrap());
let right = [
"3.25",
"-2",
"0.1",
"1.000000000000000000000000000000001",
"2",
"10",
"-3",
"1",
]
.map(|text| Decimal::parse(text).unwrap());
let values = (0..256)
.map(|index| (left[index % left.len()], right[index % right.len()]))
.collect::<Vec<_>>();
println!("case,sample,implementation,elapsed_ns,checksum");
compare_operation::<0>("decimal_add", &values);
compare_operation::<1>("decimal_subtract", &values);
compare_operation::<2>("decimal_multiply", &values);
compare_operation::<3>("decimal_divide", &values);
compare_operation::<4>("decimal_calculate", &values);
compare_operation::<5>("decimal_literal_scale", &values);
let items = values.iter().map(|(left, _)| *left).collect::<Vec<_>>();
let prepared = inputs(&items, raw);
assert_eq!(projected(&prepared).unwrap(), projected_reference(&prepared).unwrap());
let large = Decimal::parse("4E+6144").unwrap();
let maximum = Decimal::parse("9.999999999999999999999999999999999E+6144").unwrap();
for items in [vec![], vec![large, large, maximum], vec![maximum, large]] {
let prepared = inputs(&items, raw);
let actual = projected(&prepared);
let expected = projected_reference(&prepared);
assert_eq!(actual.is_err(), expected.is_err());
match (actual, expected) {
(Ok(actual), Ok(expected)) => assert_eq!(actual, expected),
(Err(actual), Err(_)) => {
let source = std::fs::read_to_string(actual.source).unwrap();
let needle = if items[0] == maximum {
"value * 2"
} else {
"sum(scale, values)"
};
assert_eq!(
actual.line,
source[..source.find(needle).unwrap()]
.bytes()
.filter(|byte| *byte == b'\n')
.count()
+ 1
);
}
_ => unreachable!(),
}
}
compare(
"decimal_projected_sum",
&prepared,
1000,
projected,
projected_reference,
|value| score(*value),
);
let numbers = [
i64::MIN,
i64::MAX,
-1,
0,
1,
i32::MIN as i64,
i32::MAX as i64,
1234567890123456789,
];
for value in numbers {
assert_eq!(
GENERATED_CONVERT(value).unwrap().to_be_bytes(),
Decimal128::from(value).to_be_bytes()
);
}
let integers = (0..256)
.map(|index| numbers[index % numbers.len()])
.collect::<Vec<_>>();
compare(
"decimal_int_conversion",
&integers,
1000,
converted,
converted_reference,
|value| *value,
);
for (name, numbers) in [
("decimal_int_conversion_narrow", [
i64::from(i32::MIN), -1, 0, 1, i64::from(i32::MAX),
i64::from(i32::MAX) + 1, i64::from(u32::MAX) - 1, i64::from(u32::MAX),
]),
("decimal_int_conversion_wide", [
i64::MIN, i64::MAX, i64::from(i32::MIN) - 1, i64::from(u32::MAX) + 1,
-1234567890123456789, 1234567890123456789, -4294967296, 4294967296,
]),
] {
let values = (0..256).map(|index| numbers[index % numbers.len()]).collect::<Vec<_>>();
compare(name, &values, 1000, converted, converted_reference, |value| *value);
if name.ends_with("wide") {
compare_named("decimal_int_conversion_wide_direct", &values, 1000, converted, converted_direct_reference, |value| *value, "rust_direct");
}
}
}
+69
View File
@@ -0,0 +1,69 @@
fn reference_ports(values: &[PORT_TYPE]) -> Result<i64, Fault> {
let fault = &FaultInfo { message: "integer arithmetic fault", source: "Rust reference", line: 0, column: 0 };
let mut total = 0i64;
for value in values {
let value = value.field0.checked_add(1).ok_or(fault)?;
total = total.checked_add(value).ok_or(fault)?;
}
Ok(total)
}
// 两处溢出保留各自来源;原单一错误参照仍用于观察诊断语义的成本。
fn reference_ports_distinct_faults(values: &[PORT_TYPE]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
let adjusted = value.field0.checked_add(1).ok_or(&FaultInfo {
message: "integer arithmetic fault", source: "Rust reference", line: 1, column: 1,
})?;
total = total.checked_add(adjusted).ok_or(&FaultInfo {
message: "integer overflow", source: "Rust reference", line: 2, column: 1,
})?;
}
Ok(total)
}
// Port 只允许 1..=65535,加一不可能溢出;累计仍按 Int 合同检查。
fn reference_ports_bounded(values: &[PORT_TYPE]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total = total.checked_add(value.field0.wrapping_add(1)).ok_or(&FaultInfo {
message: "integer overflow", source: "Rust reference", line: 2, column: 1,
})?;
}
Ok(total)
}
const SCALE: [u8; 16] = [34, 7, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0];
fn reference_rates(values: &[RATE_TYPE]) -> Result<Decimal, Fault> {
let fault = &FaultInfo { message: "decimal arithmetic fault", source: "Rust reference", line: 0, column: 0 };
let scale = dec::Decimal128::from_be_bytes(SCALE);
let mut context = dec::Context::<dec::Decimal128>::default();
let mut total = dec::Decimal128::ZERO;
for value in values {
context.clear_status();
let value = context.mul(dec::Decimal128::from_be_bytes(value.field0.to_be_bytes()), scale);
if !value.is_finite() || context.status().overflow() || context.status().invalid_operation() { return Err(fault); }
context.clear_status();
total = context.add(total, value);
if !total.is_finite() || context.status().overflow() || context.status().invalid_operation() { return Err(fault); }
}
Ok(Decimal::from_be_bytes(total.to_be_bytes()))
}
fn main() {
assert_eq!(Decimal::parse("100.0").unwrap().to_be_bytes(), SCALE);
for values in [vec![], vec![1], vec![65535], vec![1, 65535], vec![32768, 32767]] {
let ports = values.into_iter().map(|field0| PORT_TYPE { field0 }).collect::<Vec<_>>();
assert_eq!(GENERATED_TOTAL(&ports).map_err(|fault| fault.message), reference_ports_distinct_faults(&ports).map_err(|fault| fault.message));
assert_eq!(GENERATED_TOTAL(&ports).map_err(|fault| fault.message), reference_ports_bounded(&ports).map_err(|fault| fault.message));
}
println!("case,sample,implementation,elapsed_ns,checksum");
let ports = (0..64).map(|index| PORT_TYPE { field0: index * 1000 + 1 }).collect::<Vec<_>>();
compare("range_int", &ports, 100_000, GENERATED_TOTAL, reference_ports, |value| *value as u64);
compare("range_int_distinct_faults", &ports, 100_000, GENERATED_TOTAL, reference_ports_distinct_faults, |value| *value as u64);
compare("range_int_bounded", &ports, 100_000, GENERATED_TOTAL, reference_ports_bounded, |value| *value as u64);
let rates = (0..32).map(|index| RATE_TYPE { field0: Decimal::parse(&format!("0.{index:02}")).unwrap() }).collect::<Vec<_>>();
assert_eq!(GENERATED_RATES(&rates).unwrap().to_be_bytes(), reference_rates(&rates).unwrap().to_be_bytes());
compare("range_decimal", &rates, 5_000, GENERATED_RATES, reference_rates, |value| u64::from(value.to_be_bytes()[15]));
}
+256
View File
@@ -0,0 +1,256 @@
// 标准库入口来自实际 Dever 源码;参照只使用 dec,不调用 Dever 运行库。
use dec::{Context, Decimal128};
use std::sync::LazyLock;
static NATIVE_CONTEXT: LazyLock<Context<Decimal128>> = LazyLock::new(Context::default);
struct DecimalInput {
text: String,
value: Decimal,
native: Decimal128,
places: i64,
}
fn native_parse(text: &str) -> Option<Decimal128> {
let mut context = NATIVE_CONTEXT.clone();
let value = context.parse(text).ok()?;
(value.is_finite() && !context.status().inexact()).then_some(value)
}
fn parsed<const NATIVE: bool>(values: &[String]) -> Result<Vec<Option<[u8; 16]>>, Fault> {
values
.iter()
.map(|text| {
Ok(if NATIVE {
native_parse(text).map(|value| value.to_be_bytes())
} else {
DECIMAL_PARSE(text)?.map(Decimal::to_be_bytes)
})
})
.collect()
}
struct NativeDisplay(Decimal128);
impl std::fmt::Display for NativeDisplay {
fn fmt(&self, output: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = Context::<Decimal128>::default().reduce(self.0);
if value.is_zero() {
output.write_str("0")
} else {
write!(output, "{value}")
}
}
}
fn formatted<const NATIVE: bool>(values: &[DecimalInput]) -> Result<Vec<String>, Fault> {
values
.iter()
.map(|input| {
if NATIVE {
Ok(NativeDisplay(input.native).to_string())
} else {
DECIMAL_TEXT(input.value)
}
})
.collect()
}
fn native_round(value: Decimal128, places: i64) -> Option<Decimal128> {
let exponent = i32::try_from(places.checked_neg()?).ok()?;
if !(-6176..=6111).contains(&exponent) {
return None;
}
let mut context = NATIVE_CONTEXT.clone();
let mut quantum = Decimal128::ONE;
context.set_exponent(&mut quantum, exponent);
let value = context.quantize(value, quantum);
(value.is_finite() && !context.status().underflow() && !context.status().invalid_context())
.then_some(value)
}
fn rounded<const NATIVE: bool>(values: &[DecimalInput]) -> Result<Vec<Option<[u8; 16]>>, Fault> {
Ok(values
.iter()
.map(|input| {
if NATIVE {
native_round(input.native, input.places).map(|value| value.to_be_bytes())
} else {
DECIMAL_ROUND(input.value, input.places)
.ok()
.map(Decimal::to_be_bytes)
}
})
.collect())
}
fn negated<const NATIVE: bool>(values: &[DecimalInput]) -> Result<Vec<[u8; 16]>, Fault> {
Ok(values
.iter()
.map(|input| {
if NATIVE {
(-input.native).to_be_bytes()
} else {
(-input.value).to_be_bytes()
}
})
.collect())
}
fn compared<const NATIVE: bool>(
values: &[DecimalInput],
) -> Result<Vec<(bool, Option<std::cmp::Ordering>)>, Fault> {
Ok(values
.windows(2)
.map(|pair| {
if NATIVE {
(
pair[0].native == pair[1].native,
pair[0].native.partial_cmp(&pair[1].native),
)
} else {
(
pair[0].value == pair[1].value,
pair[0].value.partial_cmp(&pair[1].value),
)
}
})
.collect())
}
fn byte_score(values: &[Option<[u8; 16]>]) -> u64 {
values
.iter()
.flatten()
.flat_map(|value| value.iter())
.map(|value| u64::from(*value))
.sum()
}
fn text_score(values: &[String]) -> u64 {
values
.iter()
.flat_map(|value| value.bytes())
.map(u64::from)
.sum()
}
fn main() {
println!("case,sample,implementation,nanoseconds,checksum");
let spellings = [
"0",
"-0",
"1.2300",
"-12.345",
"1E-6176",
"1E6111",
"9.999999999999999999999999999999999E6144",
"1234567890123456789012345678901234",
"1.25",
"1.35",
"-250",
"+.125",
"1e-34",
"1.000000000000000000000000000000000",
];
let texts = spellings
.iter()
.cycle()
.take(112)
.map(|text| (*text).to_owned())
.collect::<Vec<_>>();
let inputs = texts
.iter()
.enumerate()
.map(|(index, text)| DecimalInput {
text: text.clone(),
value: Decimal::parse(text).unwrap(),
native: native_parse(text).unwrap(),
places: [0, 1, 2, -2, 6176, -6111][index % 6],
})
.collect::<Vec<_>>();
compare(
"decimal_parse_valid",
&texts,
1000,
parsed::<false>,
parsed::<true>,
|value| byte_score(value),
);
let invalid = [
"",
"+",
"NaN",
"-sNaN",
"Infinity",
"nan123",
" 1",
"1 ",
"1_000",
"1e",
"1\0",
"雪",
"1e9999",
"1e-9999",
"12345678901234567890123456789012345",
]
.iter()
.cycle()
.take(120)
.map(|value| (*value).to_owned())
.collect::<Vec<_>>();
assert!(
parsed::<false>(&invalid)
.unwrap()
.iter()
.all(Option::is_none)
);
compare(
"decimal_parse_invalid",
&invalid,
1000,
parsed::<false>,
parsed::<true>,
|value| byte_score(value),
);
compare(
"decimal_format",
&inputs,
1000,
formatted::<false>,
formatted::<true>,
|value| text_score(value),
);
compare(
"decimal_round",
&inputs,
1000,
rounded::<false>,
rounded::<true>,
|value| byte_score(value),
);
compare(
"decimal_negate",
&inputs,
1000,
negated::<false>,
negated::<true>,
|value| value.iter().flatten().map(|value| u64::from(*value)).sum(),
);
compare(
"decimal_compare",
&inputs,
1000,
compared::<false>,
compared::<true>,
|value| {
value
.iter()
.map(|(equal, order)| {
u64::from(*equal) + u64::from(order == &Some(std::cmp::Ordering::Less))
})
.sum()
},
);
wire_cases(&inputs);
storage_cases();
}
+284
View File
@@ -0,0 +1,284 @@
// 在基准中编译同一份私有 PostgreSQL codec,不为测试扩大生产 API。
use dever_runtime::value;
mod database {
pub use dever_runtime::database::{Error, Kind};
pub mod scalar {
pub use super::{Error, Kind};
#[path = "DECIMAL_SCALAR_SOURCE"]
pub mod decimal;
}
pub mod codec {
#[path = "DECIMAL_CODEC_SOURCE"]
mod implementation;
pub fn encode(value: crate::Decimal, fixed: bool, output: &mut bytes::BytesMut) {
if fixed {
implementation::encode(value, 34, 4, output).unwrap();
} else {
implementation::encode_native(value, output);
}
}
pub fn decode(bytes: &[u8]) -> crate::Decimal {
implementation::decode(bytes).unwrap()
}
}
}
use database::codec as postgres_codec;
struct StoredDecimal {
value: Decimal,
native: Decimal128,
key: String,
postgres: Vec<u8>,
}
fn native_parts(coefficient: i128, exponent: i32) -> Option<Decimal128> {
let mut context = NATIVE_CONTEXT.clone();
// i128 的六次乘加以及 String 收缩均更慢;借用路径复用 CString 的容量特化。
let value = context
.parse(format!("{coefficient}E{exponent}").as_str())
.ok()?;
(value.is_finite() && !context.status().inexact()).then_some(value)
}
fn native_scaled(value: Decimal128, precision: u32, scale: u32) -> Option<i128> {
if !(1..=34).contains(&precision) || scale > precision {
return None;
}
let coefficient = value.coefficient();
let shift = value.exponent() + scale as i32;
let scaled = if coefficient == 0 {
0
} else if shift >= 0 {
coefficient.checked_mul(10_i128.checked_pow(shift as u32)?)?
} else {
let divisor = 10_i128.checked_pow(shift.unsigned_abs())?;
if coefficient % divisor != 0 {
return None;
}
coefficient / divisor
};
(scaled.unsigned_abs() < 10_u128.pow(precision)).then_some(scaled)
}
fn sqlite_encoded<const NATIVE: bool>(values: &[StoredDecimal]) -> Result<Vec<String>, Fault> {
Ok(values
.iter()
.map(|input| {
if NATIVE {
format!(
"{:035}",
native_scaled(input.native, 34, 4).unwrap() + 10_i128.pow(34)
)
} else {
dever_runtime::database::scalar::decimal::encode(input.value, 34, 4).unwrap()
}
})
.collect())
}
fn sqlite_decoded<const NATIVE: bool>(
values: &[StoredDecimal],
) -> Result<Vec<Option<[u8; 16]>>, Fault> {
Ok(values
.iter()
.map(|input| {
if NATIVE {
let text = &input.key;
if text.len() != 35 || !text.bytes().all(|byte| byte.is_ascii_digit()) {
return None;
}
let shifted = text.parse::<i128>().ok()?;
if shifted <= 0 || shifted >= 2 * 10_i128.pow(34) {
return None;
}
native_parts(shifted - 10_i128.pow(34), -4).map(|value| value.to_be_bytes())
} else {
dever_runtime::database::scalar::decimal::decode(&input.key, 34, 4)
.ok()
.map(Decimal::to_be_bytes)
}
})
.collect())
}
// 独立按协议生成分组;两侧均用栈数组,不给参照添加额外分配。
fn native_postgres(coefficient: i128, exponent: i32, scale: u16, output: &mut bytes::BytesMut) {
use bytes::BufMut;
let mut number = coefficient.unsigned_abs() * 10_u128.pow(exponent.rem_euclid(4) as u32);
let mut groups = [0u16; 10];
let mut count = 0;
while number != 0 {
groups[count] = (number % 10000) as u16;
count += 1;
number /= 10000;
}
let weight = exponent.div_euclid(4) + count as i32 - 1;
let skip = groups[..count]
.iter()
.take_while(|value| **value == 0)
.count();
output.put_i16((count - skip) as i16);
output.put_i16(if count == 0 { 0 } else { weight as i16 });
output.put_u16(if coefficient < 0 { 0x4000 } else { 0 });
output.put_u16(scale);
for value in groups[skip..count].iter().rev() {
output.put_u16(*value);
}
}
fn native_postgres_decode(bytes: &[u8]) -> Option<Decimal128> {
if bytes.len() < 8 || !bytes.len().is_multiple_of(2) {
return None;
}
let word = |index: usize| i16::from_be_bytes([bytes[index * 2], bytes[index * 2 + 1]]);
let count = word(0);
let weight = word(1);
let sign = word(2);
if count < 0
|| bytes.len() != 8 + count as usize * 2
|| !matches!(sign, 0 | 0x4000)
|| word(3) < 0
{
return None;
}
let mut coefficient = 0i128;
for index in 0..count as usize {
let digit = word(index + 4);
if !(0..10000).contains(&digit) {
return None;
}
coefficient = coefficient
.checked_mul(10000)?
.checked_add(i128::from(digit))?;
}
if coefficient == 0 {
return Some(Decimal128::ZERO);
}
let mut exponent = (i32::from(weight) + 1 - i32::from(count)) * 4;
while coefficient % 10 == 0 {
coefficient /= 10;
exponent += 1;
}
if coefficient >= 10_i128.pow(34) {
return None;
}
native_parts(if sign == 0 { coefficient } else { -coefficient }, exponent)
}
fn postgres_encoded<const NATIVE: bool, const FIXED: bool>(
values: &[StoredDecimal],
) -> Result<Vec<u8>, Fault> {
let mut output = bytes::BytesMut::with_capacity(values.len() * 28);
for input in values {
if NATIVE {
if FIXED {
native_postgres(
native_scaled(input.native, 34, 4).unwrap(),
-4,
4,
&mut output,
);
} else {
native_postgres(
input.native.coefficient(),
input.native.exponent(),
(-input.native.exponent()).max(0) as u16,
&mut output,
);
}
} else {
postgres_codec::encode(input.value, FIXED, &mut output);
}
}
Ok(output.to_vec())
}
fn postgres_decoded<const NATIVE: bool>(values: &[StoredDecimal]) -> Result<Vec<[u8; 16]>, Fault> {
Ok(values
.iter()
.map(|input| {
if NATIVE {
native_postgres_decode(&input.postgres)
.unwrap()
.to_be_bytes()
} else {
postgres_codec::decode(&input.postgres).to_be_bytes()
}
})
.collect())
}
fn storage_cases() {
let inputs = [
"0",
"-0",
"1.2300",
"-9999.0001",
"999999999999999999999999999999.9999",
"-999999999999999999999999999999.9999",
]
.iter()
.cycle()
.take(120)
.map(|text| {
let value = Decimal::parse(*text).unwrap();
let native = native_parse(text).unwrap();
let mut postgres = bytes::BytesMut::new();
native_postgres(
native.coefficient(),
native.exponent(),
(-native.exponent()).max(0) as u16,
&mut postgres,
);
StoredDecimal {
value,
native,
key: format!(
"{:035}",
native_scaled(native, 34, 4).unwrap() + 10_i128.pow(34)
),
postgres: postgres.to_vec(),
}
})
.collect::<Vec<_>>();
compare(
"decimal_sqlite_encode",
&inputs,
1000,
sqlite_encoded::<false>,
sqlite_encoded::<true>,
|value| text_score(value),
);
compare(
"decimal_sqlite_decode",
&inputs,
1000,
sqlite_decoded::<false>,
sqlite_decoded::<true>,
|value| byte_score(value),
);
compare(
"decimal_postgres_encode",
&inputs,
1000,
postgres_encoded::<false, true>,
postgres_encoded::<true, true>,
|value| value.iter().map(|value| u64::from(*value)).sum(),
);
compare(
"decimal_postgres_native",
&inputs,
1000,
postgres_encoded::<false, false>,
postgres_encoded::<true, false>,
|value| value.iter().map(|value| u64::from(*value)).sum(),
);
compare(
"decimal_postgres_decode",
&inputs,
1000,
postgres_decoded::<false>,
postgres_decoded::<true>,
|value| value.iter().flatten().map(|value| u64::from(*value)).sum(),
);
}
+111
View File
@@ -0,0 +1,111 @@
// JSON 参照独立实现 Serde 的 Decimal 字符串合同,复用同一个 serde_json 解析器。
impl serde::Serialize for NativeDisplay {
fn serialize<Encoder: serde::Serializer>(
&self,
encoder: Encoder,
) -> Result<Encoder::Ok, Encoder::Error> {
encoder.collect_str(self)
}
}
impl<'input> serde::Deserialize<'input> for NativeDisplay {
fn deserialize<Decoder: serde::Deserializer<'input>>(
decoder: Decoder,
) -> Result<Self, Decoder::Error> {
struct Visitor;
impl<'input> serde::de::Visitor<'input> for Visitor {
type Value = NativeDisplay;
fn expecting(&self, output: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
output.write_str("an exact Decimal string")
}
fn visit_str<Error: serde::de::Error>(self, text: &str) -> Result<Self::Value, Error> {
native_parse(text)
.map(NativeDisplay)
.ok_or_else(|| Error::custom("invalid Decimal"))
}
}
decoder.deserialize_str(Visitor)
}
}
fn json_encoded<const NATIVE: bool>(values: &[DecimalInput]) -> Result<Vec<String>, Fault> {
Ok(values
.iter()
.map(|input| {
if NATIVE {
serde_json::to_string(&NativeDisplay(input.native)).unwrap()
} else {
serde_json::to_string(&input.value).unwrap()
}
})
.collect())
}
fn json_decoded<const NATIVE: bool>(values: &[String]) -> Result<Vec<Option<[u8; 16]>>, Fault> {
Ok(values
.iter()
.map(|text| {
if NATIVE {
serde_json::from_str::<NativeDisplay>(text)
.ok()
.map(|value| value.0.to_be_bytes())
} else {
serde_json::from_str::<Decimal>(text)
.ok()
.map(Decimal::to_be_bytes)
}
})
.collect())
}
fn wire_cases(inputs: &[DecimalInput]) {
let texts = inputs
.iter()
.map(|input| serde_json::to_string(&input.text).unwrap())
.collect::<Vec<_>>();
compare(
"decimal_json_encode",
inputs,
1000,
json_encoded::<false>,
json_encoded::<true>,
|value| text_score(value),
);
compare(
"decimal_json_decode",
&texts,
1000,
json_decoded::<false>,
json_decoded::<true>,
|value| byte_score(value),
);
let invalid = [
"1.25",
"null",
"[]",
"\"NaN\"",
"\"1e9999\"",
"\" 1\"",
"\"1\\u0000\"",
"\"1\" true",
]
.iter()
.cycle()
.take(112)
.map(|value| (*value).to_owned())
.collect::<Vec<_>>();
assert!(
json_decoded::<false>(&invalid)
.unwrap()
.iter()
.all(Option::is_none)
);
compare(
"decimal_json_invalid",
&invalid,
1000,
json_decoded::<false>,
json_decoded::<true>,
|value| byte_score(value),
);
}