382 lines
9.4 KiB
Rust
382 lines
9.4 KiB
Rust
//! 工具函数
|
||
//!
|
||
//! 本模块提供XTZH AI系统中常用的工具函数。
|
||
|
||
use crate::constants::*;
|
||
use crate::error::{Error, Result};
|
||
use blake3::Hasher;
|
||
use chrono::Timelike;
|
||
|
||
// ============================================================================
|
||
// 哈希工具(遵循ARCH_003:Blake3哈希统一)
|
||
// ============================================================================
|
||
|
||
/// 计算Blake3哈希
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `data` - 待哈希的数据
|
||
///
|
||
/// # 返回
|
||
///
|
||
/// 32字节Blake3哈希值
|
||
///
|
||
/// # 示例
|
||
///
|
||
/// ```
|
||
/// use xtzh_ai::utils::blake3_hash;
|
||
///
|
||
/// let data = b"Hello, NAC!";
|
||
/// let hash = blake3_hash(data);
|
||
/// assert_eq!(hash.len(), 32);
|
||
/// ```
|
||
pub fn blake3_hash(data: &[u8]) -> [u8; 32] {
|
||
let mut hasher = Hasher::new();
|
||
hasher.update(data);
|
||
hasher.finalize().into()
|
||
}
|
||
|
||
/// 计算Blake3哈希并返回十六进制字符串
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `data` - 待哈希的数据
|
||
///
|
||
/// # 返回
|
||
///
|
||
/// 64字符十六进制字符串
|
||
pub fn blake3_hash_hex(data: &[u8]) -> String {
|
||
let hash = blake3_hash(data);
|
||
hex::encode(hash)
|
||
}
|
||
|
||
// ============================================================================
|
||
// 数值转换工具
|
||
// ============================================================================
|
||
|
||
/// 将浮点数汇率转换为定点数(1e6精度)
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `rate` - 浮点数汇率(例如:1.034)
|
||
///
|
||
/// # 返回
|
||
///
|
||
/// 定点数汇率(例如:1034000)
|
||
///
|
||
/// # 示例
|
||
///
|
||
/// ```
|
||
/// use xtzh_ai::utils::rate_to_fixed;
|
||
///
|
||
/// let rate = 1.034;
|
||
/// let fixed = rate_to_fixed(rate);
|
||
/// assert_eq!(fixed, 1034000);
|
||
/// ```
|
||
pub fn rate_to_fixed(rate: f64) -> u64 {
|
||
(rate * RATE_PRECISION as f64).round() as u64
|
||
}
|
||
|
||
/// 将定点数汇率转换为浮点数
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `fixed` - 定点数汇率(例如:1034000)
|
||
///
|
||
/// # 返回
|
||
///
|
||
/// 浮点数汇率(例如:1.034)
|
||
pub fn fixed_to_rate(fixed: u64) -> f64 {
|
||
fixed as f64 / RATE_PRECISION as f64
|
||
}
|
||
|
||
/// 将百分比权重转换为基点
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `percentage` - 百分比权重(例如:38.5)
|
||
///
|
||
/// # 返回
|
||
///
|
||
/// 基点权重(例如:3850)
|
||
///
|
||
/// # 示例
|
||
///
|
||
/// ```
|
||
/// use xtzh_ai::utils::percentage_to_bps;
|
||
///
|
||
/// let percentage = 38.5;
|
||
/// let bps = percentage_to_bps(percentage);
|
||
/// assert_eq!(bps, 3850);
|
||
/// ```
|
||
pub fn percentage_to_bps(percentage: f64) -> u32 {
|
||
(percentage * 100.0).round() as u32
|
||
}
|
||
|
||
/// 将基点权重转换为百分比
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `bps` - 基点权重(例如:3850)
|
||
///
|
||
/// # 返回
|
||
///
|
||
/// 百分比权重(例如:38.5)
|
||
pub fn bps_to_percentage(bps: u32) -> f64 {
|
||
bps as f64 / 100.0
|
||
}
|
||
|
||
// ============================================================================
|
||
// 量化工具
|
||
// ============================================================================
|
||
|
||
/// 将浮点数特征量化为INT8
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `value` - 浮点数特征值
|
||
///
|
||
/// # 返回
|
||
///
|
||
/// INT8量化值
|
||
///
|
||
/// # 示例
|
||
///
|
||
/// ```
|
||
/// use xtzh_ai::utils::quantize_to_int8;
|
||
///
|
||
/// let value = 0.5;
|
||
/// let quantized = quantize_to_int8(value);
|
||
/// assert!(quantized >= -128 && quantized <= 127);
|
||
/// ```
|
||
pub fn quantize_to_int8(value: f64) -> i8 {
|
||
let scaled = (value / QUANTIZATION_SCALE as f64).round();
|
||
scaled.clamp(INT8_MIN as f64, INT8_MAX as f64) as i8
|
||
}
|
||
|
||
/// 将INT8量化值反量化为浮点数
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `quantized` - INT8量化值
|
||
///
|
||
/// # 返回
|
||
///
|
||
/// 浮点数特征值
|
||
pub fn dequantize_from_int8(quantized: i8) -> f64 {
|
||
quantized as f64 * QUANTIZATION_SCALE as f64
|
||
}
|
||
|
||
// ============================================================================
|
||
// 验证工具
|
||
// ============================================================================
|
||
|
||
/// 验证权重和是否等于100%
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `w_fx` - 货币层权重(基点)
|
||
/// * `w_au` - 黄金层权重(基点)
|
||
/// * `w_com` - 商品层权重(基点)
|
||
///
|
||
/// # 返回
|
||
///
|
||
/// 如果权重和等于10000基点(100%),返回Ok(()),否则返回错误
|
||
pub fn validate_weight_sum(w_fx: u32, w_au: u32, w_com: u32) -> Result<()> {
|
||
let sum = w_fx + w_au + w_com;
|
||
if sum == WEIGHT_SUM {
|
||
Ok(())
|
||
} else {
|
||
Err(Error::DataValidationError(format!(
|
||
"权重和错误: 期望 {}, 实际 {}",
|
||
WEIGHT_SUM, sum
|
||
)))
|
||
}
|
||
}
|
||
|
||
/// 验证黄金层权重是否在宪法约束范围内
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `w_au` - 黄金层权重(基点)
|
||
///
|
||
/// # 返回
|
||
///
|
||
/// 如果黄金层权重在5%-20%范围内,返回Ok(()),否则返回错误
|
||
pub fn validate_gold_weight(w_au: u32) -> Result<()> {
|
||
if w_au >= W_AU_MIN && w_au <= W_AU_MAX {
|
||
Ok(())
|
||
} else {
|
||
Err(Error::DataValidationError(format!(
|
||
"黄金层权重超出宪法约束: 期望 [{}, {}], 实际 {}",
|
||
W_AU_MIN, W_AU_MAX, w_au
|
||
)))
|
||
}
|
||
}
|
||
|
||
/// 验证商品偏离系数是否在范围内
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `delta` - 商品偏离系数
|
||
///
|
||
/// # 返回
|
||
///
|
||
/// 如果偏离系数在[-30, 30]范围内,返回Ok(()),否则返回错误
|
||
pub fn validate_commodity_delta(delta: i8) -> Result<()> {
|
||
if delta >= DELTA_MIN && delta <= DELTA_MAX {
|
||
Ok(())
|
||
} else {
|
||
Err(Error::DataValidationError(format!(
|
||
"商品偏离系数超出范围: 期望 [{}, {}], 实际 {}",
|
||
DELTA_MIN, DELTA_MAX, delta
|
||
)))
|
||
}
|
||
}
|
||
|
||
/// 验证商品偏离系数的零和约束
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `deltas` - 18维商品偏离系数
|
||
/// * `betas` - 18维商品权重(基点)
|
||
///
|
||
/// # 返回
|
||
///
|
||
/// 如果∑β·Δ = 0,返回Ok(()),否则返回错误
|
||
pub fn validate_zero_sum_constraint(deltas: &[i8], betas: &[u32]) -> Result<()> {
|
||
if deltas.len() != COMMODITY_DELTA_DIM || betas.len() != COMMODITY_DELTA_DIM {
|
||
return Err(Error::DataValidationError(format!(
|
||
"维度错误: deltas={}, betas={}, 期望={}",
|
||
deltas.len(),
|
||
betas.len(),
|
||
COMMODITY_DELTA_DIM
|
||
)));
|
||
}
|
||
|
||
let sum: i64 = deltas
|
||
.iter()
|
||
.zip(betas.iter())
|
||
.map(|(d, b)| *d as i64 * *b as i64)
|
||
.sum();
|
||
|
||
if sum == 0 {
|
||
Ok(())
|
||
} else {
|
||
Err(Error::DataValidationError(format!(
|
||
"商品偏离零和约束违反: ∑β·Δ = {}",
|
||
sum
|
||
)))
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 时间工具
|
||
// ============================================================================
|
||
|
||
/// 获取当前UTC时间戳(秒)
|
||
pub fn current_timestamp() -> u64 {
|
||
chrono::Utc::now().timestamp() as u64
|
||
}
|
||
|
||
/// 检查是否为数据更新时间(UTC 12:00)
|
||
pub fn is_data_update_time() -> bool {
|
||
let now = chrono::Utc::now();
|
||
now.hour() == DATA_UPDATE_HOUR
|
||
}
|
||
|
||
// ============================================================================
|
||
// 测试
|
||
// ============================================================================
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_blake3_hash() {
|
||
let data = b"Hello, NAC!";
|
||
let hash = blake3_hash(data);
|
||
assert_eq!(hash.len(), 32);
|
||
|
||
// 相同输入应产生相同哈希
|
||
let hash2 = blake3_hash(data);
|
||
assert_eq!(hash, hash2);
|
||
}
|
||
|
||
#[test]
|
||
fn test_blake3_hash_hex() {
|
||
let data = b"Hello, NAC!";
|
||
let hex = blake3_hash_hex(data);
|
||
assert_eq!(hex.len(), 64);
|
||
}
|
||
|
||
#[test]
|
||
fn test_rate_conversion() {
|
||
let rate = 1.034;
|
||
let fixed = rate_to_fixed(rate);
|
||
assert_eq!(fixed, 1034000);
|
||
|
||
let rate2 = fixed_to_rate(fixed);
|
||
assert!((rate2 - rate).abs() < 1e-6);
|
||
}
|
||
|
||
#[test]
|
||
fn test_percentage_bps_conversion() {
|
||
let percentage = 38.5;
|
||
let bps = percentage_to_bps(percentage);
|
||
assert_eq!(bps, 3850);
|
||
|
||
let percentage2 = bps_to_percentage(bps);
|
||
assert!((percentage2 - percentage).abs() < 1e-6);
|
||
}
|
||
|
||
#[test]
|
||
fn test_quantization() {
|
||
let value = 0.5;
|
||
let quantized = quantize_to_int8(value);
|
||
let dequantized = dequantize_from_int8(quantized);
|
||
assert!((dequantized - value).abs() < QUANTIZATION_SCALE as f64);
|
||
}
|
||
|
||
#[test]
|
||
fn test_validate_weight_sum() {
|
||
assert!(validate_weight_sum(4000, 1000, 5000).is_ok());
|
||
assert!(validate_weight_sum(4000, 1000, 4999).is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn test_validate_gold_weight() {
|
||
assert!(validate_gold_weight(1000).is_ok());
|
||
assert!(validate_gold_weight(500).is_ok());
|
||
assert!(validate_gold_weight(2000).is_ok());
|
||
assert!(validate_gold_weight(400).is_err());
|
||
assert!(validate_gold_weight(2100).is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn test_validate_commodity_delta() {
|
||
assert!(validate_commodity_delta(0).is_ok());
|
||
assert!(validate_commodity_delta(-30).is_ok());
|
||
assert!(validate_commodity_delta(30).is_ok());
|
||
assert!(validate_commodity_delta(-31).is_err());
|
||
assert!(validate_commodity_delta(31).is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn test_validate_zero_sum_constraint() {
|
||
let deltas = vec![10, -5, -5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||
let betas = vec![
|
||
500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500,
|
||
500,
|
||
];
|
||
assert!(validate_zero_sum_constraint(&deltas, &betas).is_ok());
|
||
}
|
||
|
||
#[test]
|
||
fn test_current_timestamp() {
|
||
let ts = current_timestamp();
|
||
assert!(ts > 0);
|
||
}
|
||
}
|