310 lines
10 KiB
Rust
310 lines
10 KiB
Rust
// 算术参照直接调用 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");
|
|
}
|
|
}
|
|
}
|