Vấn đề độ chính xác trong tính toán số học trong phát triển hợp đồng thông minh Rust
1. Vấn đề độ chính xác trong phép toán số thực
Ngôn ngữ Rust hỗ trợ tính toán số thực bản địa, nhưng tính toán số thực có vấn đề về độ chính xác không thể tránh khỏi. Khi viết hợp đồng thông minh, không nên sử dụng tính toán số thực, đặc biệt là khi xử lý tỷ lệ hoặc lãi suất trong các quyết định kinh tế/tài chính quan trọng.
Loại số thực độ chính xác gấp đôi f64 trong ngôn ngữ Rust tuân theo tiêu chuẩn IEEE 754, sử dụng biểu diễn khoa học với cơ số là 2. Một số số thập phân không thể được biểu diễn chính xác bằng số thực có độ dài hữu hạn, dẫn đến hiện tượng "làm tròn".
Ví dụ, khi phân phối 0.7 NEAR token cho 10 người dùng trên chuỗi công khai NEAR:
gỉ
#[test]
fn precision_test_float() {
let amount: f64 = 0.7;
let divisor: f64 = 10.0;
let result_0 = amount / divisor;
println!("Giá trị của số lượng: {:.20}", amount);
assert_eq!(result_0, 0.07, "");
}
Kết quả đầu ra cho thấy giá trị của amount là 0.69999999999999995559, không phải là 0.7 chính xác. Kết quả phép chia cũng trở thành 0.06999999999999999 không chính xác, thay vì 0.07 như mong đợi.
Để giải quyết vấn đề này, có thể xem xét sử dụng số cố định. Trong NEAR Protocol, thường sử dụng 10^24 yoctoNEAR để biểu thị 1 mã thông báo NEAR. Mã kiểm tra đã được sửa đổi:
gỉ
#[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, "");
}
Cách này có thể đạt được kết quả tính toán chính xác: 0.7 NEAR / 10 = 0.07 NEAR.
2. Vấn đề độ chính xác trong tính toán số nguyên Rust
Việc sử dụng phép toán số nguyên có thể giải quyết vấn đề mất độ chính xác trong phép toán số thực ở một số trường hợp, nhưng kết quả của phép toán số nguyên cũng không hoàn toàn chính xác và đáng tin cậy.
2.1 Thứ tự toán tử
Thứ tự của phép nhân và phép chia có cùng độ ưu tiên số học có thể ảnh hưởng trực tiếp đến kết quả tính toán, dẫn đến vấn đề độ chính xác của phép tính số nguyên. Ví dụ:
gỉ
#[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,"");
}
Kết quả thực thi cho thấy result_0 và result_1 không bằng nhau. Nguyên nhân là phép chia số nguyên sẽ loại bỏ độ chính xác nhỏ hơn mẫu số. Khi tính toán result_1, (a / b) trước tiên mất độ chính xác và trở thành 0; trong khi khi tính toán result_0, trước tiên tính a * c để tránh mất độ chính xác.
2.2 quy mô quá nhỏ
Khi liên quan đến tính toán số thập phân, phép toán số nguyên có thể dẫn đến mất độ chính xác:
rỉ sét
#[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, "");
}
Kết quả cho thấy result_0=12, result_1=13, trong khi giá trị dự kiến thực tế nên là 13.3333....
3. Làm thế nào để viết hợp đồng thông minh Rust cho việc định giá số
Để nâng cao độ chính xác, có thể áp dụng các biện pháp bảo vệ sau:
3.1 Điều chỉnh thứ tự thực hiện phép toán
Hãy ưu tiên phép nhân số nguyên hơn phép chia số nguyên.
3.2 tăng bậc số nguyên
Sử dụng bậc lớn hơn, tạo ra phân số lớn hơn. Ví dụ, biểu thị 5.123 NEAR có thể sử dụng 5.123 * 10^10 = 51_230_000_000 để tham gia vào các phép toán tiếp theo.
3.3 Tổn thất độ chính xác của phép toán tích lũy
Đối với các vấn đề độ chính xác không thể tránh khỏi, có thể ghi lại tổn thất độ chính xác tích lũy. Ví dụ:
gỉ
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;
cho i trong 1..7 {
println!("Round {}", i);
offset = distribute(to_yocto)"10"(, offset(;
println!)"Offset {}\n", offset);
}
}
Phương pháp này có thể tích lũy số token còn lại từ mỗi lần phân phối, và phát cùng nhau trong lần phân phối tiếp theo, cuối cùng đạt được mục tiêu phát đủ.
( 3.4 Sử dụng thư viện Rust Crate rust-decimal
Thư viện này phù hợp cho các phép toán tài chính với số thập phân cần tính toán chính xác mà không có sai số do làm tròn.
) 3.5 Xem xét cơ chế làm tròn
Trong thiết kế hợp đồng thông minh, vấn đề làm tròn thường áp dụng nguyên tắc "Tôi muốn lợi ích, người khác không được lợi dụng tôi". Tùy thuộc vào tình huống mà chọn làm tròn xuống hoặc làm tròn lên, rất ít khi sử dụng làm tròn thông thường.
This page may contain third-party content, which is provided for information purposes only (not representations/warranties) and should not be considered as an endorsement of its views by Gate, nor as financial or professional advice. See Disclaimer for details.
Phát triển hợp đồng thông minh Rust: Khắc phục vấn đề độ chính xác trong tính toán số
Vấn đề độ chính xác trong tính toán số học trong phát triển hợp đồng thông minh Rust
1. Vấn đề độ chính xác trong phép toán số thực
Ngôn ngữ Rust hỗ trợ tính toán số thực bản địa, nhưng tính toán số thực có vấn đề về độ chính xác không thể tránh khỏi. Khi viết hợp đồng thông minh, không nên sử dụng tính toán số thực, đặc biệt là khi xử lý tỷ lệ hoặc lãi suất trong các quyết định kinh tế/tài chính quan trọng.
Loại số thực độ chính xác gấp đôi f64 trong ngôn ngữ Rust tuân theo tiêu chuẩn IEEE 754, sử dụng biểu diễn khoa học với cơ số là 2. Một số số thập phân không thể được biểu diễn chính xác bằng số thực có độ dài hữu hạn, dẫn đến hiện tượng "làm tròn".
Ví dụ, khi phân phối 0.7 NEAR token cho 10 người dùng trên chuỗi công khai NEAR:
gỉ #[test] fn precision_test_float() { let amount: f64 = 0.7;
let divisor: f64 = 10.0;
let result_0 = amount / divisor;
println!("Giá trị của số lượng: {:.20}", amount); assert_eq!(result_0, 0.07, ""); }
Kết quả đầu ra cho thấy giá trị của amount là 0.69999999999999995559, không phải là 0.7 chính xác. Kết quả phép chia cũng trở thành 0.06999999999999999 không chính xác, thay vì 0.07 như mong đợi.
Để giải quyết vấn đề này, có thể xem xét sử dụng số cố định. Trong NEAR Protocol, thường sử dụng 10^24 yoctoNEAR để biểu thị 1 mã thông báo NEAR. Mã kiểm tra đã được sửa đổi:
gỉ #[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, ""); }
Cách này có thể đạt được kết quả tính toán chính xác: 0.7 NEAR / 10 = 0.07 NEAR.
2. Vấn đề độ chính xác trong tính toán số nguyên Rust
Việc sử dụng phép toán số nguyên có thể giải quyết vấn đề mất độ chính xác trong phép toán số thực ở một số trường hợp, nhưng kết quả của phép toán số nguyên cũng không hoàn toàn chính xác và đáng tin cậy.
2.1 Thứ tự toán tử
Thứ tự của phép nhân và phép chia có cùng độ ưu tiên số học có thể ảnh hưởng trực tiếp đến kết quả tính toán, dẫn đến vấn đề độ chính xác của phép tính số nguyên. Ví dụ:
gỉ #[test] fn precision_test_div_before_mul() { let a: u128 = 1_0000; let b: u128 = 10_0000; let c: u128 = 20;
}
Kết quả thực thi cho thấy result_0 và result_1 không bằng nhau. Nguyên nhân là phép chia số nguyên sẽ loại bỏ độ chính xác nhỏ hơn mẫu số. Khi tính toán result_1, (a / b) trước tiên mất độ chính xác và trở thành 0; trong khi khi tính toán result_0, trước tiên tính a * c để tránh mất độ chính xác.
2.2 quy mô quá nhỏ
Khi liên quan đến tính toán số thập phân, phép toán số nguyên có thể dẫn đến mất độ chính xác:
rỉ sét #[test] fn precision_test_decimals() { let a: u128 = 10; let b: u128 = 3; let c: u128 = 4; let decimal: u128 = 100_0000;
}
Kết quả cho thấy result_0=12, result_1=13, trong khi giá trị dự kiến thực tế nên là 13.3333....
3. Làm thế nào để viết hợp đồng thông minh Rust cho việc định giá số
Để nâng cao độ chính xác, có thể áp dụng các biện pháp bảo vệ sau:
3.1 Điều chỉnh thứ tự thực hiện phép toán
Hãy ưu tiên phép nhân số nguyên hơn phép chia số nguyên.
3.2 tăng bậc số nguyên
Sử dụng bậc lớn hơn, tạo ra phân số lớn hơn. Ví dụ, biểu thị 5.123 NEAR có thể sử dụng 5.123 * 10^10 = 51_230_000_000 để tham gia vào các phép toán tiếp theo.
3.3 Tổn thất độ chính xác của phép toán tích lũy
Đối với các vấn đề độ chính xác không thể tránh khỏi, có thể ghi lại tổn thất độ chính xác tích lũy. Ví dụ:
gỉ 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; cho i trong 1..7 { println!("Round {}", i); offset = distribute(to_yocto)"10"(, offset(; println!)"Offset {}\n", offset); } }
Phương pháp này có thể tích lũy số token còn lại từ mỗi lần phân phối, và phát cùng nhau trong lần phân phối tiếp theo, cuối cùng đạt được mục tiêu phát đủ.
( 3.4 Sử dụng thư viện Rust Crate rust-decimal
Thư viện này phù hợp cho các phép toán tài chính với số thập phân cần tính toán chính xác mà không có sai số do làm tròn.
) 3.5 Xem xét cơ chế làm tròn
Trong thiết kế hợp đồng thông minh, vấn đề làm tròn thường áp dụng nguyên tắc "Tôi muốn lợi ích, người khác không được lợi dụng tôi". Tùy thuộc vào tình huống mà chọn làm tròn xuống hoặc làm tròn lên, rất ít khi sử dụng làm tròn thông thường.
![]###https://img-cdn.gateio.im/webp-social/moments-6e8b4081214a69423fc7ae022d05c728.webp###