Rust contratos inteligentes numéricos: armadilhas de ponto flutuante e otimização de precisão inteira

Diário de Desenvolvimento de Contratos Inteligentes Rust (7) Cálculo de Valores

1. Problemas de precisão em operações com números de ponto flutuante

A linguagem Rust suporta nativamente operações com números de ponto flutuante, mas essas operações apresentam problemas de precisão de cálculo que são inevitáveis. Ao escrever contratos inteligentes, não se recomenda o uso de operações com números de ponto flutuante, especialmente ao lidar com taxas ou juros que envolvem decisões econômicas/financeiras importantes.

Na linguagem Rust, os números de ponto flutuante seguem o padrão IEEE 754. Tomando como exemplo o tipo de ponto flutuante de dupla precisão f64, sua representação binária interna é a seguinte:

Os números de ponto flutuante são expressos em notação científica com base 2. Por exemplo, 0.8125 pode ser representado pelo número binário finito 0.1101:

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

No entanto, para decimais pequenos como 0.7, pode ocorrer uma situação de repetição infinita:

0.7 = 0.1011001100110011...

Isso leva à incapacidade de representar com precisão números de ponto flutuante de comprimento finito, existindo o fenômeno de "arredondamento".

Usando como exemplo a distribuição de 0,7 NEAR tokens a dez usuários na blockchain NEAR:

ferrugem #[test] fn precision_test_float() { let amount: f64 = 0.7;
let divisor: f64 = 10.0;
let result_0 = amount / divisor;
println!("O valor da quantia: {:.20}", amount); assert_eq!(result_0, 0.07, ""); }

Resultado da execução:

executando 1 teste O valor da quantia: 0.69999999999999995559 thread 'tests::precision_test_float' panicked at 'assertion failed: (left == right) left: 0.06999999999999999, right: 0.07: ', src/lib.rs:185:9

Como pode ser visto, o valor de amount não é exatamente 0.7, mas sim um valor aproximado de 0.69999999999999995559. O resultado de uma operação de divisão adicional também se torna impreciso, resultando em 0.06999999999999999.

Para resolver este problema, pode-se considerar o uso de números fixos. No NEAR Protocol, geralmente utiliza-se a representação de 10^24 yoctoNEAR equivalente a 1 token NEAR.

Código de teste modificado:

ferrugem #[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 da execução:

executando 1 teste test tests::precision_test_integer ... ok resultado do teste: ok. 1 passado; 0 falhado; 0 ignorado; 0 medido; 8 filtrados; terminado em 0.00s

2. Problema de precisão na computação de inteiros em Rust

2.1 Ordem das operações

A mudança na ordem de multiplicação e divisão com a mesma prioridade aritmética pode afetar diretamente o resultado do cálculo, levando a problemas de precisão nos cálculos inteiros.

ferrugem #[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 da execução:

executando 1 teste thread 'tests::precision_test_0' panicked at 'assertion failed: (left == right) left: 2, right: 0: ', src/lib.rs:175:9

Pode-se observar que result_0 = a * c / b e result_1 = (a / b) * c, embora as fórmulas de cálculo sejam as mesmas, os resultados são diferentes. A razão é que a divisão inteira descartará a precisão menor que o divisor. Ao calcular result_1, primeiro calcular (a / b) resulta numa perda de precisão que se torna 0; enquanto ao calcular result_0, primeiro calcula-se a * c = 20_0000, que é maior que o divisor b, evitando assim a perda de precisão.

2.2 quantidade muito pequena

ferrugem #[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 da execução:

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

É visível que os resultados result_0 e result_1, que são equivalentes no processo de cálculo, são diferentes, e result_1 = 13 está mais próximo da expectativa real de 13.3333....

3. Como escrever contratos inteligentes de avaliação numérica em Rust

3.1 Ajustar a ordem das operações

  • Fazer a multiplicação de inteiros ter prioridade sobre a divisão de inteiros.

3.2 aumentar a ordem de grandeza dos inteiros

  • Números inteiros usam ordens de magnitude maiores, criando numeradores maiores.

3.3 perda de precisão acumulada

Para os problemas de precisão de cálculo inteiro que não podem ser evitados, pode-se considerar registrar a perda acumulada de precisão dos cálculos.

ferrugem 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; para i em 1..7 { println!("Round {}", i); offset = distribute(10_000_000_000_000_000_000_000_000, offset); println!("Offset {}\n", offset); } }

Resultado da execução:

executando 1 teste Rodada 1 per_user_share 3333333333333333333333333 Offset 1

Rodada 2 per_user_share 3333333333333333333333333 Offset 2

Rodada 3 per_user_share 4000000000000000000000000 Offset 0

Rodada 4 per_user_share 3333333333333333333333333 Offset 1

Rodada 5 per_user_share 3333333333333333333333333 Offset 2

Rodada 6 per_user_share 4000000000000000000000000 Offset 0

test tests::record_offset_test ... ok resultado do teste: ok. 1 passado; 0 falhou; 0 ignorado; 0 medido; 9 filtrados; terminado em 0.00s

( 3.4 Utilizando a biblioteca Rust Crate rust-decimal

Esta biblioteca é adequada para cálculos financeiros de números decimais que exigem precisão efetiva e que não têm erro de arredondamento.

) 3.5 considerar o mecanismo de arredondamento

Ao projetar contratos inteligentes, o problema do arredondamento geralmente adota o princípio "Quero me beneficiar, os outros não devem me explorar". De acordo com este princípio, se arredondar para baixo for benéfico para mim, então arredondo para baixo; se arredondar para cima for benéfico para mim, então arredondo para cima; o arredondamento para o mais próximo não pode determinar a quem é benéfico, portanto, raramente é adotado.

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

Ver original
Esta página pode conter conteúdos de terceiros, que são fornecidos apenas para fins informativos (sem representações/garantias) e não devem ser considerados como uma aprovação dos seus pontos de vista pela Gate, nem como aconselhamento financeiro ou profissional. Consulte a Declaração de exoneração de responsabilidade para obter mais informações.
  • Recompensa
  • 5
  • Partilhar
Comentar
0/400
LiquidationKingvip
· 07-16 06:01
O 0,7 que você mencionou não é nada de mais, um grande colapso é que seria emocionante.
Ver originalResponder0
BankruptcyArtistvip
· 07-16 05:57
Ai, escrever contratos inteligentes realmente me prejudicou muito com números de ponto flutuante.
Ver originalResponder0
GmGmNoGnvip
· 07-16 05:55
Este bug pode fazer-me perder a precisão.
Ver originalResponder0
NotSatoshivip
· 07-16 05:55
Nos contratos inteligentes, brincar com ponto flutuante pode fazer a cabeça explodir.
Ver originalResponder0
SandwichVictimvip
· 07-16 05:40
O problema da precisão de Schrödinger
Ver originalResponder0
  • Pino
Negocie cripto em qualquer lugar e a qualquer hora
qrCode
Digitalizar para transferir a aplicação Gate
Novidades
Português (Portugal)
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)