257 lines
6.4 KiB
Rust
257 lines
6.4 KiB
Rust
// 标准库入口来自实际 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();
|
|
}
|