use super::{Directory, fixture}; use dever::compiler::{build, emit, project::Project}; use dever_runtime::value::decimal::Decimal; #[test] fn decimal_constant_bytes_preserve_sign_quantum_and_full_representation() { const ONE: Decimal = Decimal::from_be_bytes([34, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); const NEGATIVE_ZERO: Decimal = Decimal::from_be_bytes([162, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); assert_eq!( ONE.to_be_bytes(), Decimal::parse("1").unwrap().to_be_bytes() ); assert_eq!( NEGATIVE_ZERO.to_be_bytes(), Decimal::parse("-0").unwrap().to_be_bytes() ); // 除数值相等外,还核对编码;不能丢掉负零、尾零或极端指数。 for text in [ "-0.00", "0E+100", "1.2300", "-1.000000000000000000000000000000001", "1E-6176", "9.999999999999999999999999999999999E+6144", ] { let original = Decimal::parse(text).unwrap(); let restored = Decimal::from_be_bytes(original.to_be_bytes()); assert_eq!(restored, original, "{text}"); assert_eq!(restored.to_be_bytes(), original.to_be_bytes(), "{text}"); } for seed in 0..256u16 { let bytes = std::array::from_fn(|index| (seed + index as u16 * 17) as u8); let original = dec::Decimal128::from_be_bytes(bytes); let restored = Decimal::from_be_bytes(bytes); assert_eq!(restored.to_be_bytes(), original.to_be_bytes()); } } #[test] fn numeric_clauses_execute_with_nullable_values_nan_infinity_and_signed_zero() { let directory = Directory::new(); let path = fixture().with_file_name("numeric.dever"); let project = Project::load(&path).unwrap(); let program = project.check().unwrap(); let source = emit::generate( &program, &project.sources, &(0..program.functions.len()).collect(), ); assert!(source.contains("Decimal::from_be_bytes")); assert!(!source.contains("Decimal::parse")); let binary = build::build( &program, &project.sources, "verify", &directory.0.join("numeric"), ) .unwrap(); let result = std::process::Command::new(binary).output().unwrap(); assert!( result.status.success(), "{}", String::from_utf8_lossy(&result.stderr) ); assert_eq!(result.stdout, b"true\n"); } #[test] fn integer_decimal_conversion_preserves_full_encoding_at_narrowing_boundaries() { let verify = |value| { assert_eq!( Decimal::from_int(value).to_be_bytes(), dec::Decimal128::from(value).to_be_bytes(), "{value}" ); }; for value in [ i64::MIN, i64::MAX, -1, 0, 1, i64::from(i32::MIN) - 1, i64::from(i32::MIN), i64::from(i32::MIN) + 1, i64::from(i32::MAX) - 1, i64::from(i32::MAX), i64::from(i32::MAX) + 1, i64::from(u32::MAX) - 1, i64::from(u32::MAX), i64::from(u32::MAX) + 1, ] { verify(value); } let mut state = 0x9e3779b97f4a7c15u64; for _ in 0..256 { state = state .wrapping_mul(6364136223846793005) .wrapping_add(1442695040888963407); verify(state as i64); verify(i64::from(state as i32)); verify(i64::from(state as u32)); } } #[test] fn decimal_sum_preserves_rounding_first_fault_and_context_independence() { let maximum = "9.999999999999999999999999999999999E+6144"; for texts in [ vec![], vec!["-0.00"], vec!["0.1", "0.2", "-0.05"], vec!["1E-6176", "1E-6176"], vec![ "1000000000000000000000000000000000", "0.1", "-1000000000000000000000000000000000", ], vec![ "1000000000000000000000000000000000", "-1000000000000000000000000000000000", "0.1", ], vec![maximum, "1"], vec![ maximum, maximum, "-9.999999999999999999999999999999999E+6144", ], ] { let values = texts .iter() .map(|text| Decimal::parse(text).unwrap()) .collect::>(); let before = values .iter() .map(|value| value.to_be_bytes()) .collect::>(); let mut context = dec::Context::::default(); let expected = values .iter() .try_fold(dec::Decimal128::ZERO, |total, value| { context.clear_status(); let value = context.add(total, dec::Decimal128::from_be_bytes(value.to_be_bytes())); (value.is_finite() && !context.status().overflow() && !context.status().invalid_operation()) .then_some(value) }); assert_eq!( Decimal::sum(&values).map(Decimal::to_be_bytes), expected.map(|value| value.to_be_bytes()), "{texts:?}" ); assert_eq!( values .iter() .map(|value| value.to_be_bytes()) .collect::>(), before ); } let one = Decimal::from_int(1); let mut context = Decimal::context(); assert!( one.checked_div(Decimal::from_int(0), &mut context) .is_none() ); assert_eq!(Decimal::sum(&[one, one]), Some(Decimal::from_int(2))); assert_eq!( one.checked_add(one, &mut context), Some(Decimal::from_int(2)) ); } #[test] fn decimal_sum_executes_through_generated_code() { let directory = Directory::new(); let project = Project::load(&fixture().with_file_name("decimal.dever")).unwrap(); let program = project.check().unwrap(); let binary = build::build( &program, &project.sources, "verify", &directory.0.join("decimal"), ) .unwrap(); let output = std::process::Command::new(binary).output().unwrap(); assert!( output.status.success(), "{}", String::from_utf8_lossy(&output.stderr) ); assert_eq!(output.stdout, b"true\n"); } #[test] fn decimal_operations_keep_local_status_across_threads() { let start = std::sync::Barrier::new(4); std::thread::scope(|scope| { for _ in 0..4 { scope.spawn(|| { start.wait(); let one = Decimal::from_int(1); let two = Decimal::from_int(2); let maximum = Decimal::parse("9.999999999999999999999999999999999E+6144").unwrap(); let mut context = Decimal::context(); for _ in 0..32 { assert!(maximum.checked_mul(two, &mut context).is_none()); assert_eq!(one.checked_add(one, &mut context), Some(two)); assert!(one.checked_div(Decimal::ZERO, &mut context).is_none()); assert_eq!(two.checked_sub(one, &mut context), Some(one)); assert!(Decimal::parse("0.12345678901234567890123456789012345").is_err()); assert_eq!( Decimal::parse("1.25").unwrap(), one.checked_div(Decimal::parse("0.8").unwrap(), &mut context) .unwrap() ); assert_eq!(Decimal::sum(&[one, one]), Some(two)); } }); } }); } #[test] fn decimal_status_matches_finite_results_at_arithmetic_boundaries() { let texts = [ "0", "-0.00", "1", "-1", "2", "3", "0.1", "-0.1", "1.000000000000000000000000000000001", "0.9999999999999999999999999999999999", "9999999999999999999999999999999999", "1E-6176", "-1E-6176", "1E-6143", "-1E-6143", "1E+6144", "-1E+6144", "9.999999999999999999999999999999999E+6144", "-9.999999999999999999999999999999999E+6144", ]; let values = texts.map(|text| Decimal::parse(text).unwrap()); let mut actual_context = Decimal::context(); let mut reference_context = dec::Context::::default(); for left in values { for right in values { for operation in 0..4 { reference_context.clear_status(); let native_left = dec::Decimal128::from_be_bytes(left.to_be_bytes()); let native_right = dec::Decimal128::from_be_bytes(right.to_be_bytes()); let expected = match operation { 0 => reference_context.add(native_left, native_right), 1 => reference_context.sub(native_left, native_right), 2 => reference_context.mul(native_left, native_right), 3 => reference_context.div(native_left, native_right), _ => unreachable!(), }; let expected = (expected.is_finite() && !reference_context.status().overflow() && !reference_context.status().invalid_operation()) .then(|| expected.to_be_bytes()); let actual = match operation { 0 => left.checked_add(right, &mut actual_context), 1 => left.checked_sub(right, &mut actual_context), 2 => left.checked_mul(right, &mut actual_context), 3 => left.checked_div(right, &mut actual_context), _ => unreachable!(), }; assert_eq!( actual.map(Decimal::to_be_bytes), expected, "{left} operation {operation} {right}" ); } } for places in [-6112, -6111, -614, -1, 0, 1, 33, 6143, 6176, 6177] { reference_context.clear_status(); let expected = if (-6176..=6111).contains(&-places) { let mut quantum = dec::Decimal128::ONE; reference_context.set_exponent(&mut quantum, -places); let value = reference_context .quantize(dec::Decimal128::from_be_bytes(left.to_be_bytes()), quantum); (value.is_finite() && !reference_context.status().overflow() && !reference_context.status().invalid_operation() && !reference_context.status().underflow() && !reference_context.status().invalid_context()) .then(|| value.to_be_bytes()) } else { None }; assert_eq!( left.round(i64::from(places)).map(Decimal::to_be_bytes), expected ); } } for invalid in ["NaN", "sNaN", "Infinity", "-Infinity", "1E+6145"] { assert!(Decimal::parse(invalid).is_err()); } } #[test] fn range_contracts_execute_without_runtime_wrappers() { let directory = super::Directory::new(); let project = dever::compiler::project::Project::load(&super::fixture().with_file_name("ranges.dever")) .unwrap(); let program = project.check().unwrap(); let binary = dever::compiler::build::build( &program, &project.sources, "verify", &directory.0.join("ranges"), ) .unwrap(); let result = std::process::Command::new(binary).output().unwrap(); assert!( result.status.success(), "{}", String::from_utf8_lossy(&result.stderr) ); assert_eq!(result.stdout, b"true\n"); }