Diario de desarrollo de contratos inteligentes en Rust (7) Problemas de precisión en operaciones con números de punto flotante e enteros
Este artículo discutirá los problemas de precisión en las operaciones de punto flotante e enteros en contratos inteligentes de Rust, así como cómo escribir contratos inteligentes de cálculo numérico.
1. Problema de precisión en operaciones con números de punto flotante
El lenguaje Rust admite de forma nativa operaciones con números de punto flotante, pero estas operaciones presentan problemas de precisión de cálculo que son inevitables. Al escribir contratos inteligentes, no se recomienda utilizar operaciones con números de punto flotante, especialmente al tratar con tasas o intereses que afectan decisiones económicas/financieras importantes.
En el lenguaje Rust, los números de punto flotante adoptan el estándar IEEE 754 y se representan utilizando notación científica con base 2. Algunos decimales como (, como 0.7), no se pueden representar con precisión utilizando números de punto flotante de longitud finita, lo que puede dar lugar a fenómenos de "redondeo".
Por ejemplo, al distribuir 0.7 tokens NEAR a 10 usuarios en la cadena de bloques NEAR:
óxido
#[test]
fn precision_test_float() {
let amount: f64 = 0.7;
let divisor: f64 = 10.0;
let result_0 = amount / divisor;
assert_eq!(result_0, 0.07, "");
}
El resultado de la ejecución muestra que el valor de amount no es exactamente 0.7, sino un valor aproximado de 0.69999999999999995559. El resultado de la operación de división tampoco es preciso, siendo 0.06999999999999999 en lugar del esperado 0.07.
Para resolver este problema, se puede considerar el uso de números de punto fijo. En el Protocolo NEAR, generalmente se utiliza la forma de representación 1 NEAR = 10^24 yoctoNEAR:
óxido
#[test]
fn precision_test_integer() {
let N: u128 = 1_000_000_000_000_000_000_000_000;
let amount: u128 = 700_000_000_000_000_000_000_000;
let divisor: u128 = 10;
let result_0 = amount / divisor;
assert_eq!(result_0, 70_000_000_000_000_000_000_000, "");
}
De esta manera, se obtiene el resultado del cálculo actuarial: 0.7 NEAR / 10 = 0.07 NEAR.
2. Problemas de precisión en el cálculo de enteros en Rust
2.1 orden de operaciones
El orden de las operaciones de multiplicación y división con la misma prioridad aritmética puede afectar directamente el resultado del cálculo:
óxido
#[test]
fn precision_test_div_before_mul() {
let a: u128 = 1_0000;
let b: u128 = 10_0000;
let c: u128 = 20;
let result_0 = a.checked_mul(c).expect("ERR_MUL")
.checked_div(b).expect("ERR_DIV");
let result_1 = a.checked_div(b).expect("ERR_DIV")
.checked_mul(c).expect("ERR_MUL");
assert_eq!(result_0,result_1,"");
}
El resultado de la ejecución muestra que result_0 y result_1 no son iguales. La razón es que la división entera descarta la precisión menor que el divisor. Al calcular result_1, (a / b) perderá primero precisión y se convertirá en 0; mientras que al calcular result_0, calcular primero a * c puede evitar la pérdida de precisión.
2.2 orden de magnitud demasiado pequeño
La magnitud demasiado pequeña también puede causar problemas de precisión:
óxido
#[test]
fn precision_test_decimals() {
let a: u128 = 10;
let b: u128 = 3;
let c: u128 = 4;
let decimal: u128 = 100_0000;
let result_0 = a.checked_div(b).expect("ERR_DIV")
.checked_mul(c).expect("ERR_MUL");
let result_1 = a.checked_mul(decimal).expect("ERR_MUL")
.checked_div(b).expect("ERR_DIV")
.checked_mul(c).expect("ERR_MUL")
.checked_div(decimal).expect("ERR_DIV");
assert_eq!(result_0, result_1, "");
}
Los resultados muestran result_0=12, result_1=13, siendo este último más cercano al valor esperado de 13.3333.
3. Cómo escribir contratos inteligentes de evaluación numérica en Rust
Para mejorar la precisión, se pueden tomar las siguientes medidas de protección:
3.1 Ajustar el orden de las operaciones
Hacer que la multiplicación de enteros tenga prioridad sobre la división de enteros.
3.2 aumentar el orden de magnitud de los enteros
Usar una mayor magnitud para crear moléculas más grandes. Por ejemplo, representar 5.123 NEAR como 5.123 * 10^10 = 51_230_000_000.
3.3 Pérdida de precisión de cálculo acumulativo
Registro de la pérdida acumulada de precisión de cálculo:
óxido
const USER_NUM: u128 = 3;
u128 {
let token_to_distribute = offset + amount;
let per_user_share = token_to_distribute / USER_NUM;
let recorded_offset = token_to_distribute - per_user_share * USER_NUM;
recorded_offset
}
#(
fn record_offset_test)[test] {
let mut offset: u128 = 0;
para i en 1..7 {
offset = distribute(to_yocto)"10"(, offset(;
}
}
De esta manera, se pueden almacenar temporalmente los tokens no distribuidos y se entregarán todos juntos en la próxima distribución.
) 3.4 Uso de la biblioteca Rust Crate rust-decimal
Esta biblioteca es adecuada para cálculos financieros decimales que requieren una precisión efectiva y que no tienen errores de redondeo.
) 3.5 Considerar el mecanismo de redondeo
En el diseño de contratos inteligentes, generalmente se adopta el principio de "quiero aprovecharme, los demás no pueden aprovecharse de mí". Según la situación, se elige redondear hacia abajo o hacia arriba, y rara vez se utiliza el redondeo.
Esta página puede contener contenido de terceros, que se proporciona únicamente con fines informativos (sin garantías ni declaraciones) y no debe considerarse como un respaldo por parte de Gate a las opiniones expresadas ni como asesoramiento financiero o profesional. Consulte el Descargo de responsabilidad para obtener más detalles.
12 me gusta
Recompensa
12
8
Compartir
Comentar
0/400
SandwichHunter
· 07-16 18:07
el pequeño pollo de rust nuevamente se tumbó en el suelo
Ver originalesResponder0
WhaleStalker
· 07-16 17:00
Los errores de código son realmente mortales...
Ver originalesResponder0
GhostInTheChain
· 07-15 16:28
rust todavía tiene que llenar decimales... no es fácil
Ver originalesResponder0
BoredWatcher
· 07-13 18:40
¿Se puede escribir contratos con la biblioteca rust?
Ver originalesResponder0
GasFeeNightmare
· 07-13 18:35
La precisión de los cálculos es tan molesta como mi gas...
Ver originalesResponder0
EyeOfTheTokenStorm
· 07-13 18:34
La tasa de pérdida de precisión afecta directamente la relación de ganancias y pérdidas, ¿quién todavía utiliza números de punto flotante para la cuantificación? Los apostadores ya se han ido a hacer un Rug Pull, ¿verdad?
Ver originalesResponder0
TokenEconomist
· 07-13 18:32
en realidad, la pérdida de precisión = f(orden_de_ops, factor_de_escala) ... rust-decimal lib por siempre
Ver originalesResponder0
just_another_wallet
· 07-13 18:25
¿Los números de punto flotante son realmente tan problemáticos...?
Problemas de precisión en las operaciones numéricas en contratos inteligentes de Rust y soluciones de optimización
Diario de desarrollo de contratos inteligentes en Rust (7) Problemas de precisión en operaciones con números de punto flotante e enteros
Este artículo discutirá los problemas de precisión en las operaciones de punto flotante e enteros en contratos inteligentes de Rust, así como cómo escribir contratos inteligentes de cálculo numérico.
1. Problema de precisión en operaciones con números de punto flotante
El lenguaje Rust admite de forma nativa operaciones con números de punto flotante, pero estas operaciones presentan problemas de precisión de cálculo que son inevitables. Al escribir contratos inteligentes, no se recomienda utilizar operaciones con números de punto flotante, especialmente al tratar con tasas o intereses que afectan decisiones económicas/financieras importantes.
En el lenguaje Rust, los números de punto flotante adoptan el estándar IEEE 754 y se representan utilizando notación científica con base 2. Algunos decimales como (, como 0.7), no se pueden representar con precisión utilizando números de punto flotante de longitud finita, lo que puede dar lugar a fenómenos de "redondeo".
Por ejemplo, al distribuir 0.7 tokens NEAR a 10 usuarios en la cadena de bloques NEAR:
óxido #[test] fn precision_test_float() { let amount: f64 = 0.7;
let divisor: f64 = 10.0;
let result_0 = amount / divisor;
assert_eq!(result_0, 0.07, ""); }
El resultado de la ejecución muestra que el valor de amount no es exactamente 0.7, sino un valor aproximado de 0.69999999999999995559. El resultado de la operación de división tampoco es preciso, siendo 0.06999999999999999 en lugar del esperado 0.07.
Para resolver este problema, se puede considerar el uso de números de punto fijo. En el Protocolo NEAR, generalmente se utiliza la forma de representación 1 NEAR = 10^24 yoctoNEAR:
óxido #[test] fn precision_test_integer() { let N: u128 = 1_000_000_000_000_000_000_000_000;
let amount: u128 = 700_000_000_000_000_000_000_000; let divisor: u128 = 10;
let result_0 = amount / divisor; assert_eq!(result_0, 70_000_000_000_000_000_000_000, ""); }
De esta manera, se obtiene el resultado del cálculo actuarial: 0.7 NEAR / 10 = 0.07 NEAR.
2. Problemas de precisión en el cálculo de enteros en Rust
2.1 orden de operaciones
El orden de las operaciones de multiplicación y división con la misma prioridad aritmética puede afectar directamente el resultado del cálculo:
óxido #[test] fn precision_test_div_before_mul() { let a: u128 = 1_0000; let b: u128 = 10_0000; let c: u128 = 20;
}
El resultado de la ejecución muestra que result_0 y result_1 no son iguales. La razón es que la división entera descarta la precisión menor que el divisor. Al calcular result_1, (a / b) perderá primero precisión y se convertirá en 0; mientras que al calcular result_0, calcular primero a * c puede evitar la pérdida de precisión.
2.2 orden de magnitud demasiado pequeño
La magnitud demasiado pequeña también puede causar problemas de precisión:
óxido #[test] fn precision_test_decimals() { let a: u128 = 10; let b: u128 = 3; let c: u128 = 4; let decimal: u128 = 100_0000;
}
Los resultados muestran result_0=12, result_1=13, siendo este último más cercano al valor esperado de 13.3333.
3. Cómo escribir contratos inteligentes de evaluación numérica en Rust
Para mejorar la precisión, se pueden tomar las siguientes medidas de protección:
3.1 Ajustar el orden de las operaciones
Hacer que la multiplicación de enteros tenga prioridad sobre la división de enteros.
3.2 aumentar el orden de magnitud de los enteros
Usar una mayor magnitud para crear moléculas más grandes. Por ejemplo, representar 5.123 NEAR como 5.123 * 10^10 = 51_230_000_000.
3.3 Pérdida de precisión de cálculo acumulativo
Registro de la pérdida acumulada de precisión de cálculo:
óxido const USER_NUM: u128 = 3;
u128 { let token_to_distribute = offset + amount; let per_user_share = token_to_distribute / USER_NUM; let recorded_offset = token_to_distribute - per_user_share * USER_NUM; recorded_offset }
#( fn record_offset_test)[test] { let mut offset: u128 = 0; para i en 1..7 { offset = distribute(to_yocto)"10"(, offset(; } }
De esta manera, se pueden almacenar temporalmente los tokens no distribuidos y se entregarán todos juntos en la próxima distribución.
) 3.4 Uso de la biblioteca Rust Crate rust-decimal
Esta biblioteca es adecuada para cálculos financieros decimales que requieren una precisión efectiva y que no tienen errores de redondeo.
) 3.5 Considerar el mecanismo de redondeo
En el diseño de contratos inteligentes, generalmente se adopta el principio de "quiero aprovecharme, los demás no pueden aprovecharse de mí". Según la situación, se elige redondear hacia abajo o hacia arriba, y rara vez se utiliza el redondeo.
![]###https://img-cdn.gateio.im/webp-social/moments-6e8b4081214a69423fc7ae022d05c728.webp###