Rust contratos inteligentes cálculo numérico: trampas de punto flotante y optimización de precisión entera

Diario de desarrollo de contratos inteligentes en Rust (7) Cálculo numérico

1. Problemas de precisión en las operaciones con números de punto flotante

El lenguaje Rust admite operaciones de punto flotante de forma nativa, pero estas operaciones presentan problemas de precisión de cálculo que son inevitables. Al escribir contratos inteligentes, no se recomienda utilizar operaciones de punto flotante, especialmente al tratar con tasas o intereses que implican decisiones económicas/financieras importantes.

En el lenguaje Rust, los números de punto flotante siguen el estándar IEEE 754. Tomando como ejemplo el tipo de punto flotante de doble precisión f64, su representación binaria interna es la siguiente:

Los números de punto flotante se expresan en notación científica de base 2. Por ejemplo, 0.8125 se puede representar con el número binario de un número finito de bits 0.1101:

0.8125 = 0.5 * 1 + 0.25 * 1 + 0.125 * 0 + 0.0625 * 1

Sin embargo, para decimales tan pequeños como 0.7, puede ocurrir una situación de ciclo infinito:

0.7 = 0.1011001100110011...

Esto lleva a que no se pueda representar con precisión utilizando números de punto flotante de longitud limitada, existiendo el fenómeno de "redondeo".

Tomando como ejemplo la distribución de 0.7 tokens NEAR a diez 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;
println!("El valor de la cantidad: {:.20}", amount); assert_eq!(result_0, 0.07, ""); }

Resultado de la ejecución:

ejecutando 1 prueba El valor de la cantidad: 0.69999999999999995559 hilo 'tests::precision_test_float' entró en pánico en 'la afirmación falló: (left == right) izquierda: 0.06999999999999999, derecha: 0.07: ', src/lib.rs:185:9

Se puede ver 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 también se convierte en un valor inexacto de 0.06999999999999999.

Para resolver este problema, se puede considerar el uso de números de punto fijo. En el Protocolo NEAR, generalmente se utiliza el método de representación donde 10^24 yoctoNEAR es equivalente a 1 token NEAR.

Código de prueba modificado:

ó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, ""); }

Resultado de la ejecución:

ejecutando 1 prueba test tests::precision_test_integer ... ok resultado de la prueba: ok. 1 aprobado; 0 fallido; 0 ignorado; 0 medido; 8 filtrados; terminado en 0.00s

2. Problema de precisión en los cálculos enteros de Rust

2.1 Orden de operaciones

El cambio en el orden de multiplicación y división con la misma prioridad aritmética puede afectar directamente el resultado del cálculo, lo que lleva a problemas de precisión en el cálculo de enteros.

ó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,"");

}

Resultado de la ejecución:

ejecutando 1 prueba el hilo 'tests::precision_test_0' se ha bloqueado en 'falló la afirmación: (left == right) izquierda: 2, derecha: 0: ', src/lib.rs:175:9

Se puede observar que result_0 = a * c / b y result_1 = (a / b) * c, aunque las fórmulas de cálculo son las mismas, los resultados son diferentes. La razón es que la división entera descartará la precisión que es menor que el divisor. En el cálculo de result_1, calcular primero (a / b) provoca que se pierda precisión y se convierta en 0; mientras que en el cálculo de result_0, primero se calcula a * c = 20_0000, que es mayor que el divisor b, evitando así la pérdida de precisión.

2.2 magnitud demasiado pequeña

ó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");

println!("{}:{}", result_0, result_1);
assert_eq!(result_0, result_1, "");

}

Resultado de la ejecución:

ejecutando 1 prueba 12:13 thread 'tests::precision_test_decimals' panicked at 'assertion failed: (left == right) left: 12, right: 13: ', src/lib.rs:214:9

Los resultados result_0 y result_1, que son equivalentes en el proceso de cálculo, son diferentes, y result_1 = 13 está más cerca de la expectativa real de 13.3333....

3. Cómo escribir contratos inteligentes de evaluación actuarial numérica en Rust

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

  • Los enteros utilizan un mayor orden de magnitud, creando numeradores más grandes.

3.3 Pérdida de precisión en operaciones acumulativas

Para los problemas de precisión en cálculos enteros que no se pueden evitar, se puede considerar registrar la pérdida acumulada de precisión en los cálculos.

óxido const USER_NUM: u128 = 3;

u128 { let token_to_distribute = offset + amount; let per_user_share = token_to_distribute / USER_NUM; println!("per_user_share {}", per_user_share); let recorded_offset = token_to_distribute - per_user_share * USER_NUM; recorded_offset }

#( fn record_offset_test)[test] { let mut offset: u128 = 0; for i in 1..7 { println!("Round {}", i); offset = distribuir(10_000_000_000_000_000_000_000_000, offset); println!("Offset {}\n", offset); } }

Resultado de la ejecución:

ejecutando 1 prueba Ronda 1 per_user_share 3333333333333333333333333 Desplazamiento 1

Ronda 2 per_user_share 3333333333333333333333333 Offset 2

Ronda 3 per_user_share 4000000000000000000000000 Offset 0

Ronda 4 per_user_share 3333333333333333333333333 Offset 1

Ronda 5 per_user_share 3333333333333333333333333 Offset 2

Ronda 6 per_user_share 4000000000000000000000000 Offset 0

test tests::record_offset_test ... ok resultado de la prueba: ok. 1 aprobado; 0 fallido; 0 ignorado; 0 medido; 9 filtrados; terminado en 0.00s

( 3.4 Uso de la biblioteca Rust Crate rust-decimal

Esta biblioteca es adecuada para cálculos financieros en decimales que requieren una precisión efectiva y no tienen errores de redondeo.

) 3.5 Considerar el mecanismo de redondeo

Al diseñar contratos inteligentes, el problema de redondeo generalmente sigue el principio de "quiero aprovecharme, los demás no deben aprovecharse de mí". Según este principio, si redondear hacia abajo me beneficia, entonces se hace hacia abajo; si redondear hacia arriba me beneficia, entonces se hace hacia arriba; el redondeo al más cercano no puede determinar a quién le beneficia, por lo tanto, se utiliza muy poco.

![]###https://img-cdn.gateio.im/webp-social/moments-1933a4a2dd723a847f0059d31d1780d1.webp###

Ver originales
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.
  • Recompensa
  • 5
  • Compartir
Comentar
0/400
LiquidationKingvip
· hace18h
Lo que dices de 0.7 no es nada importante, un gran colapso sería emocionante.
Ver originalesResponder0
BankruptcyArtistvip
· hace18h
Ay, realmente he sido estafado por los números de punto flotante al escribir contratos inteligentes.
Ver originalesResponder0
GmGmNoGnvip
· hace18h
Este bug puede hacer que pierda toda mi precisión.
Ver originalesResponder0
NotSatoshivip
· hace18h
En contratos inteligentes jugar con puntos flotantes puede hacer que la cabeza explote.
Ver originalesResponder0
SandwichVictimvip
· hace18h
El problema de la precisión de Schrödinger
Ver originalesResponder0
  • Anclado
Opere con criptomonedas en cualquier momento y lugar
qrCode
Escanee para descargar la aplicación Gate
Comunidad
Español
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)