Nhật ký phát triển hợp đồng thông minh Rust (7): Tính toán số lượng
Bài viết này sẽ khám phá vấn đề tính toán số học trong lập trình hợp đồng thông minh Rust, chủ yếu bao gồm các vấn đề về độ chính xác của phép toán số thực, độ chính xác của phép toán số nguyên, cũng như cách viết hợp đồng thông minh Rust cho tính toán số học.
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 một cách tự nhiên, nhưng việc tính toán số thực gặp phải 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 phép toán số thực, đặc biệt là khi xử lý các tỷ lệ hoặc lãi suất liên quan đến 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 hệ số khoa học với cơ số 2 để biểu diễn. Tuy nhiên, một số số thập phân như ( chẳng hạn như 0.7) không thể được biểu diễn chính xác bằng số thực có độ dài hữu hạn, sẽ có hiện tượng "làm tròn".
Lấy ví dụ phân phối 0.7 NEAR token cho mười người dùng trên chuỗi công khai NEAR, kết quả tính toán thực tế sẽ xuất hiện tình huống không chính xác:
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ố tiền: {:.20}", amount);
assert_eq!(result_0, 0.07, "");
}
Kết quả thực thi cho thấy giá trị của amount không phải là 0.7 chính xác, mà là một giá trị rất gần gũi là 0.69999999999999995559. Kết quả của phép chia tiếp theo cũng trở nên không chính xác là 0.06999999999999999, chứ không phải là 0.07 như mong đợi.
Để giải quyết vấn đề này, có thể xem xét việc sử dụng số cố định. Trong NEAR Protocol, thường sử dụng 10^24 làm mẫu số, tức là 10^24 yoctoNEAR tương đương với 1 token NEAR.
Mã kiểm tra đã được sửa đổi như sau:
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
Mặc dù 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 của số thực trong một số kịch bản, nhưng kết quả tính toán số nguyên cũng không hoàn toàn chính xác và đáng tin cậy. Một số lý do ảnh hưởng đến độ chính xác của phép toán số nguyên bao gồm:
2.1 Thứ tự thực hiện
Thứ tự trước và sau của phép nhân và phép chia cùng một độ ưu tiên toán học có thể ảnh hưởng trực tiếp đến kết quả tính toá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ả kiểm tra cho thấy, kết quả tính toán của result_0 và result_1 khác nhau. Điều này là do đối với phép chia số nguyên, độ chính xác nhỏ hơn mẫu số sẽ bị loại bỏ. Khi tính toán result_1, (a / b) sẽ mất độ chính xác tính toán trước tiên, trở thành 0; trong khi khi tính toán result_0, việc tính a * c trước có thể tránh mất độ chính xác.
2.2 quy mô quá nhỏ
Khi liên quan đến các phép toán ở quy mô nhỏ hơn, cũng có thể xuất hiện vấn đề về độ chính xác:
gỉ
#[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ả kiểm tra cho thấy, kết quả của result_0 và result_1 khác nhau, và result_1 = 13 gần hơn với giá trị tính toán thực tế dự kiến là 13.3333....
3. Cách viết hợp đồng thông minh Rust cho tính toán số
Để nâng cao độ chính xác, có thể thực hiện các biện pháp bảo vệ sau:
3.1 Điều chỉnh thứ tự thao tác tính toán
Đặt phép nhân của số nguyên ưu tiên hơn phép chia của số nguyên.
3.2 Tăng cường độ lớn của số nguyên
Sử dụng đại lượng lớn hơn, tạo ra phân số lớn hơn. Ví dụ, có thể biểu thị 5.123 NEAR là 5.123 * 10^10 = 51_230_000_000.
3.3 Tổn thất độ chính xác của phép toán tích lũy
Đối với vấn đề độ chính xác của phép toán số nguyên không thể tránh khỏi, có thể xem xét ghi lại tổn thất độ chính xác tích lũy của phép toán. 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 và phân phối lại các token chưa được phân phát do mất độ chính xác.
( 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ố làm tròn.
) 3.5 Xem xét cơ chế làm tròn
Khi thiết kế hợp đồng thông minh, vấn đề làm tròn thường tuân theo nguyên tắc "Tôi muốn hưởng lợi, người khác không được lợi dụng tôi". Theo nguyên tắc này, nếu việc làm tròn xuống có lợi cho hợp đồng thì sẽ làm tròn xuống; nếu việc làm tròn lên có lợi cho hợp đồng thì sẽ làm tròn lên; làm tròn theo quy tắc chung ít khi được áp dụng vì không xác định được ai sẽ được lợi.
Bằng cách áp dụng những phương pháp này, có thể thực hiện tính toán số học chính xác hơn trong hợp đồng thông minh Rust, nâng cao độ tin cậy và tính công bằng của hợp đồng.
Trang này có thể chứa nội dung của bên thứ ba, được cung cấp chỉ nhằm mục đích thông tin (không phải là tuyên bố/bảo đảm) và không được coi là sự chứng thực cho quan điểm của Gate hoặc là lời khuyên về tài chính hoặc chuyên môn. Xem Tuyên bố từ chối trách nhiệm để biết chi tiết.
14 thích
Phần thưởng
14
8
Chia sẻ
Bình luận
0/400
ZeroRushCaptain
· 07-17 09:17
À lại bắt đầu lăn lộn với cái này rồi, lần trước chính là vấn đề độ chính xác bị Vị thế bị khóa hợp đồng phản giết, lỗ đến mức ăn đất.
Xem bản gốcTrả lời0
BlockchainFries
· 07-16 10:52
Cái này thật sự quá tệ, ai dùng thì người đó xui xẻo.
Xem bản gốcTrả lời0
MoonBoi42
· 07-15 08:48
Độ chính xác của số dấu phẩy động không bằng số cố định thoải mái~
Xem bản gốcTrả lời0
OnchainDetective
· 07-14 09:54
Rõ ràng, sai số độ chính xác chính là điểm yếu bị một số cầu nối Cross-chain khai thác bởi Hacker, thật đáng sợ.
Xem bản gốcTrả lời0
HashRateHermit
· 07-14 09:54
Vấn đề độ chính xác thật sự làm tôi chết khiếp.
Xem bản gốcTrả lời0
GateUser-e87b21ee
· 07-14 09:47
Lại làm chuyện về dấu phẩy động, đau đầu quá.
Xem bản gốcTrả lời0
BearMarketSurvivor
· 07-14 09:45
Độ chính xác cái hố này ai cũng không thoát khỏi, người này hiểu.
Xem bản gốcTrả lời0
AirdropLicker
· 07-14 09:35
Việc làm tròn số thực thì thật là sâu sắc, tôi đã từng bị rơi vào đó.
Kỹ thuật và lưu ý về tính toán số trong hợp đồng thông minh Rust
Nhật ký phát triển hợp đồng thông minh Rust (7): Tính toán số lượng
Bài viết này sẽ khám phá vấn đề tính toán số học trong lập trình hợp đồng thông minh Rust, chủ yếu bao gồm các vấn đề về độ chính xác của phép toán số thực, độ chính xác của phép toán số nguyên, cũng như cách viết hợp đồng thông minh Rust cho tính toán số học.
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 một cách tự nhiên, nhưng việc tính toán số thực gặp phải 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 phép toán số thực, đặc biệt là khi xử lý các tỷ lệ hoặc lãi suất liên quan đến 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 hệ số khoa học với cơ số 2 để biểu diễn. Tuy nhiên, một số số thập phân như ( chẳng hạn như 0.7) không thể được biểu diễn chính xác bằng số thực có độ dài hữu hạn, sẽ có hiện tượng "làm tròn".
Lấy ví dụ phân phối 0.7 NEAR token cho mười người dùng trên chuỗi công khai NEAR, kết quả tính toán thực tế sẽ xuất hiện tình huống không chính xác:
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ố tiền: {:.20}", amount); assert_eq!(result_0, 0.07, ""); }
Kết quả thực thi cho thấy giá trị của amount không phải là 0.7 chính xác, mà là một giá trị rất gần gũi là 0.69999999999999995559. Kết quả của phép chia tiếp theo cũng trở nên không chính xác là 0.06999999999999999, chứ không phải là 0.07 như mong đợi.
Để giải quyết vấn đề này, có thể xem xét việc sử dụng số cố định. Trong NEAR Protocol, thường sử dụng 10^24 làm mẫu số, tức là 10^24 yoctoNEAR tương đương với 1 token NEAR.
Mã kiểm tra đã được sửa đổi như sau:
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
Mặc dù 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 của số thực trong một số kịch bản, nhưng kết quả tính toán số nguyên cũng không hoàn toàn chính xác và đáng tin cậy. Một số lý do ảnh hưởng đến độ chính xác của phép toán số nguyên bao gồm:
2.1 Thứ tự thực hiện
Thứ tự trước và sau của phép nhân và phép chia cùng một độ ưu tiên toán học có thể ảnh hưởng trực tiếp đến kết quả tính toá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ả kiểm tra cho thấy, kết quả tính toán của result_0 và result_1 khác nhau. Điều này là do đối với phép chia số nguyên, độ chính xác nhỏ hơn mẫu số sẽ bị loại bỏ. Khi tính toán result_1, (a / b) sẽ mất độ chính xác tính toán trước tiên, trở thành 0; trong khi khi tính toán result_0, việc tính a * c trước có thể tránh mất độ chính xác.
2.2 quy mô quá nhỏ
Khi liên quan đến các phép toán ở quy mô nhỏ hơn, cũng có thể xuất hiện vấn đề về độ chính xác:
gỉ #[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ả kiểm tra cho thấy, kết quả của result_0 và result_1 khác nhau, và result_1 = 13 gần hơn với giá trị tính toán thực tế dự kiến là 13.3333....
3. Cách viết hợp đồng thông minh Rust cho tính toán số
Để nâng cao độ chính xác, có thể thực hiện các biện pháp bảo vệ sau:
3.1 Điều chỉnh thứ tự thao tác tính toán
Đặt phép nhân của số nguyên ưu tiên hơn phép chia của số nguyên.
3.2 Tăng cường độ lớn của số nguyên
Sử dụng đại lượng lớn hơn, tạo ra phân số lớn hơn. Ví dụ, có thể biểu thị 5.123 NEAR là 5.123 * 10^10 = 51_230_000_000.
3.3 Tổn thất độ chính xác của phép toán tích lũy
Đối với vấn đề độ chính xác của phép toán số nguyên không thể tránh khỏi, có thể xem xét ghi lại tổn thất độ chính xác tích lũy của phép toán. 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 và phân phối lại các token chưa được phân phát do mất độ chính xác.
( 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ố làm tròn.
) 3.5 Xem xét cơ chế làm tròn
Khi thiết kế hợp đồng thông minh, vấn đề làm tròn thường tuân theo nguyên tắc "Tôi muốn hưởng lợi, người khác không được lợi dụng tôi". Theo nguyên tắc này, nếu việc làm tròn xuống có lợi cho hợp đồng thì sẽ làm tròn xuống; nếu việc làm tròn lên có lợi cho hợp đồng thì sẽ làm tròn lên; làm tròn theo quy tắc chung ít khi được áp dụng vì không xác định được ai sẽ được lợi.
Bằng cách áp dụng những phương pháp này, có thể thực hiện tính toán số học chính xác hơn trong hợp đồng thông minh Rust, nâng cao độ tin cậy và tính công bằng của hợp đồng.
![]###https://img-cdn.gateio.im/webp-social/moments-6e8b4081214a69423fc7ae022d05c728.webp###