Diário de Desenvolvimento de Contratos Inteligentes Rust (7): Cálculo Numérico
1. Problemas de precisão em operações com números de ponto flutuante
A linguagem Rust suporta operações com números de ponto flutuante de forma nativa, mas essas operações apresentam problemas de precisão de cálculo que não podem ser evitados. Ao escrever contratos inteligentes, não é recomendado usar operações com números de ponto flutuante, especialmente ao lidar com taxas ou juros que envolvam decisões econômicas/financeiras importantes.
O tipo de ponto flutuante de dupla precisão f64 na linguagem Rust segue o padrão IEEE 754, utilizando a notação científica com base 2. Certos números decimais, como 0.7, não podem ser representados com precisão por números de ponto flutuante de comprimento finito, existindo o fenômeno de "arredondamento".
No teste de distribuição de 0,7 tokens NEAR para dez usuários na blockchain NEAR, o resultado da operação de ponto flutuante não é preciso:
ferrugem
let amount: f64 = 0.7;
let divisor: f64 = 10.0;
let result_0 = amount / divisor;
o valor de amount é 0.69999999999999995559, o valor de result_0 é 0.06999999999999999, em vez do esperado 0.07.
Para resolver este problema, pode-se usar números de ponto fixo. No NEAR Protocol, normalmente utiliza-se a representação 1 NEAR = 10^24 yoctoNEAR:
ferrugem
deixe 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;
Desta forma, pode-se obter resultados de cálculo precisos: 0,7 NEAR / 10 = 0,07 NEAR.
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. Por exemplo:
ferrugem
let a: u128 = 1_0000;
let b: u128 = 10_0000;
let c: u128 = 20;
// result_0 = a * c / b
let result_0 = a.checked_mul(c).expect("ERR_MUL").checked_div(b).expect("ERR_DIV");
// result_1 = a / b * c
let result_1 = a.checked_div(b).expect("ERR_DIV").checked_mul(c).expect("ERR_MUL");
o resultado de result_0 e result_1 é diferente, porque para a divisão inteira, a precisão abaixo do divisor é descartada.
2.2 quantidade muito pequena
Quando se trata de valores decimais, a operação inteira pode resultar em perda de precisão:
ferrugem
let a: u128 = 10;
let b: u128 = 3;
let c: u128 = 4;
let decimal: u128 = 100_0000;
// result_0 = (a / b) * c
let result_0 = a.checked_div(b).expect("ERR_DIV").checked_mul(c).expect("ERR_MUL");
// result_1 = (a * decimal / b) * c / decimal;
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");
o resultado da operação entre result_0 e result_1 é diferente, result_1 está mais próximo do valor esperado real.
3. Como escrever contratos inteligentes Rust para cálculos numéricos
3.1 Ajustar a ordem das operações
Fazer com que a multiplicação de inteiros tenha prioridade sobre a divisão de inteiros.
3.2 aumentar a ordem de grandeza dos inteiros
Utilize uma escala maior para criar moléculas maiores. Por exemplo, expresse 5.123 NEAR como 51_230_000_000 yoctoNEAR.
3.3 Perda de precisão dos cálculos acumulados
Registar a perda de precisão acumulada nos cálculos e compensá-la em cálculos subsequentes. Por exemplo:
ferrugem
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
}
( 3.4 Usando a biblioteca Rust Crate rust-decimal
Esta biblioteca é adequada para cálculos financeiros em decimais que requerem precisão eficaz e não têm erro de arredondamento.
) 3.5 Considerar o mecanismo de arredondamento
No design de contratos inteligentes, o problema de arredondamento geralmente adota o princípio "quero lucrar, os outros não devem tirar vantagem de mim". Escolha arredondar para baixo, arredondar para cima ou arredondar para o mais próximo, conforme a situação.
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.
7 gostos
Recompensa
7
3
Partilhar
Comentar
0/400
BearMarketBuilder
· 08-05 07:58
Os números de ponto flutuante são um buraco tão profundo como o mar.
Ver originalResponder0
SelfSovereignSteve
· 08-05 07:53
Os números de ponto flutuante são muito problemáticos. Os contratos inteligentes vão falhar. Não me atrevo a usá-los.
Ver originalResponder0
DegenGambler
· 08-05 07:49
Atenção irmãos que jogam Rust, a precisão é uma armadilha considerável.
Desenvolvimento de contratos inteligentes em Rust: técnicas de cálculo numérico e controle de precisão
Diário de Desenvolvimento de Contratos Inteligentes Rust (7): Cálculo Numérico
1. Problemas de precisão em operações com números de ponto flutuante
A linguagem Rust suporta operações com números de ponto flutuante de forma nativa, mas essas operações apresentam problemas de precisão de cálculo que não podem ser evitados. Ao escrever contratos inteligentes, não é recomendado usar operações com números de ponto flutuante, especialmente ao lidar com taxas ou juros que envolvam decisões econômicas/financeiras importantes.
O tipo de ponto flutuante de dupla precisão f64 na linguagem Rust segue o padrão IEEE 754, utilizando a notação científica com base 2. Certos números decimais, como 0.7, não podem ser representados com precisão por números de ponto flutuante de comprimento finito, existindo o fenômeno de "arredondamento".
No teste de distribuição de 0,7 tokens NEAR para dez usuários na blockchain NEAR, o resultado da operação de ponto flutuante não é preciso:
ferrugem let amount: f64 = 0.7;
let divisor: f64 = 10.0; let result_0 = amount / divisor;
o valor de amount é 0.69999999999999995559, o valor de result_0 é 0.06999999999999999, em vez do esperado 0.07.
Para resolver este problema, pode-se usar números de ponto fixo. No NEAR Protocol, normalmente utiliza-se a representação 1 NEAR = 10^24 yoctoNEAR:
ferrugem deixe 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;
Desta forma, pode-se obter resultados de cálculo precisos: 0,7 NEAR / 10 = 0,07 NEAR.
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. Por exemplo:
ferrugem let a: u128 = 1_0000; let b: u128 = 10_0000; let c: u128 = 20;
// result_0 = a * c / b let result_0 = a.checked_mul(c).expect("ERR_MUL").checked_div(b).expect("ERR_DIV");
// result_1 = a / b * c
let result_1 = a.checked_div(b).expect("ERR_DIV").checked_mul(c).expect("ERR_MUL");
o resultado de result_0 e result_1 é diferente, porque para a divisão inteira, a precisão abaixo do divisor é descartada.
2.2 quantidade muito pequena
Quando se trata de valores decimais, a operação inteira pode resultar em perda de precisão:
ferrugem let a: u128 = 10; let b: u128 = 3; let c: u128 = 4; let decimal: u128 = 100_0000;
// result_0 = (a / b) * c let result_0 = a.checked_div(b).expect("ERR_DIV").checked_mul(c).expect("ERR_MUL");
// result_1 = (a * decimal / b) * c / decimal;
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");
o resultado da operação entre result_0 e result_1 é diferente, result_1 está mais próximo do valor esperado real.
3. Como escrever contratos inteligentes Rust para cálculos numéricos
3.1 Ajustar a ordem das operações
Fazer com que a multiplicação de inteiros tenha prioridade sobre a divisão de inteiros.
3.2 aumentar a ordem de grandeza dos inteiros
Utilize uma escala maior para criar moléculas maiores. Por exemplo, expresse 5.123 NEAR como 51_230_000_000 yoctoNEAR.
3.3 Perda de precisão dos cálculos acumulados
Registar a perda de precisão acumulada nos cálculos e compensá-la em cálculos subsequentes. Por exemplo:
ferrugem 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 }
( 3.4 Usando a biblioteca Rust Crate rust-decimal
Esta biblioteca é adequada para cálculos financeiros em decimais que requerem precisão eficaz e não têm erro de arredondamento.
) 3.5 Considerar o mecanismo de arredondamento
No design de contratos inteligentes, o problema de arredondamento geralmente adota o princípio "quero lucrar, os outros não devem tirar vantagem de mim". Escolha arredondar para baixo, arredondar para cima ou arredondar para o mais próximo, conforme a situação.
![]###https://img-cdn.gateio.im/webp-social/moments-6e8b4081214a69423fc7ae022d05c728.webp###