Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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]));
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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(),
|
||||
);
|
||||
}
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user