Nhật ký phát triển hợp đồng thông minh Rust (7): Vấn đề độ chính xác trong tính toán số nguyên
Điểm lại các kỳ trước:
Nhật ký phát triển hợp đồng thông minh Rust (1) Định nghĩa dữ liệu trạng thái hợp đồng và thực hiện phương thức
Nhật ký phát triển hợp đồng thông minh Rust (2) Viết thử nghiệm đơn vị hợp đồng thông minh Rust
Nhật ký phát triển hợp đồng thông minh Rust (3) Triển khai hợp đồng thông minh Rust, gọi hàm và sử dụng Explorer
Nhật ký phát triển hợp đồng thông minh Rust (4) Tràn số nguyên hợp đồng thông minh Rust
Nhật ký phát triển hợp đồng thông minh Rust (5) tấn công tái nhập
Nhật ký phát triển hợp đồng thông minh Rust (6) tấn công từ chối dịch vụ
1. Vấn đề độ chính xác trong tính toán số thực
Khác với Solidity, Rust hỗ trợ tính toán số thực một cách tự nhiên. Tuy nhiên, tính toán số thực tồn tại các vấn đề về độ chính xác không thể tránh khỏi, vì vậy không được khuyến nghị sử dụng trong hợp đồng thông minh, đặ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.
Rust tuân theo tiêu chuẩn IEEE 754 để biểu diễn số thực. Kiểu số thực gấp đôi f64 được biểu diễn trong máy tính bằng phương pháp ký hiệu khoa học nhị phân.
Một số số thập phân có thể được biểu diễn chính xác bằng số nhị phân với một số lượng hữu hạn, ví dụ như 0.8125 có thể được biểu diễn là 0.1101. Nhưng số thập phân như 0.7 sẽ tạo ra biểu diễn nhị phân tuần hoàn vô hạn, không thể được biểu diễn chính xác bằng số thực với số lượng hữu hạn, dẫn đến vấn đề "làm tròn".
Trong ví dụ 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ố tiền: {:.20}", amount);
assert_eq!(result_0, 0.07);
}
Kết quả thực hiện cho thấy giá trị thực của amount là 0.69999999999999995559, result_0 là 0.06999999999999999, không bằng giá trị mong đợi là 0.07.
Để giải quyết vấn đề này, có thể sử dụng số cố định. Trong NEAR, thường dùng 10^24 yoctoNEAR để biểu thị 1 token NEAR. Mã đã đượ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);
}
Vậy có thể đạt được kết quả 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ù phép toán số nguyên có thể giải quyết một số vấn đề về độ chính xác của số thực trong một số tình huống, nhưng phép toán số nguyên cũng tồn tại vấn đề về độ chính xác.
2.1 Thứ tự thao tác
Phép nhân và phép chia cùng cấp, thay đổi thứ tự có thể ảnh hưởng đến kết quả:
rỉ sét
#[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).unwrap().checked_div(b).unwrap();
let result_1 = a.checked_div(b).unwrap().checked_mul(c).unwrap();
assert_eq!(result_0,result_1);
}
Kết quả cho thấy result_0 = 2, result_1 = 0.
Lý do 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 sẽ mất độ chính xác trở thành 0; trong khi result_0 trước tiên tính toán (a * c) để tránh mất độ chính xác.
2.2 độ lớn quá nhỏ
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).unwrap().checked_mul(c).unwrap();
let result_1 = a.checked_mul(decimal).unwrap()
.checked_div(b).unwrap()
.checked_mul(c).unwrap()
.checked_div(decimal).unwrap();
println!("{}:{}", result_0, result_1);
assert_eq!(result_0, result_1);
}
Kết quả cho thấy result_0 = 12, result_1 = 13, cái sau gần với giá trị thực 13.3333 hơn.
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 sau:
3.1 Điều chỉnh thứ tự thao tác tính toán
Để phép nhân số nguyên ưu tiên hơn phép chia.
3.2 Tăng bậc số nguyên
Sử dụng quy mô lớn hơn, tạo ra phân tử lớn hơn. Như định nghĩa 1 NEAR = 10^24 yoctoNEAR.
3.3 Tích lũy tổn thất độ chính xác của phép toán
Ghi lại và tích lũy tổn thất độ chính xác, bù đắp trong các phép toán tiếp theo:
gỉ
const USER_NUM: u128 = 3;
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
}
#(
fn record_offset_test)[test] {
let mut offset: u128 = 0;
cho i trong 1..7 {
offset = distribute(10_000_000_000_000_000_000_000_000, offset);
}
}
( 3.4 Sử dụng thư viện Rust Crate rust-decimal
Thư viện này phù hợp cho các tính toán tài chính với số thập phân yêu cầu độ chính xác cao và không có lỗi 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, việc làm tròn thường tuân theo nguyên tắc "có lợi cho tôi": nếu làm tròn xuống có lợi cho tôi thì làm tròn xuống, nếu làm tròn lên có lợi cho tôi thì làm tròn lên, hiếm khi áp dụng quy tắc làm tròn thông thườ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.
17 thích
Phần thưởng
17
8
Chia sẻ
Bình luận
0/400
LostBetweenChains
· 07-25 17:53
Tính toán số nguyên còn không xong, thì chơi hợp đồng gì?
Xem bản gốcTrả lời0
WagmiWarrior
· 07-25 11:45
Thấy bài thứ 7 rồi, mỏng quá mỏng quá.
Xem bản gốcTrả lời0
MetaMisfit
· 07-25 01:13
Rust này hợp đồng thông minh坑 cũng nhiều quá nhỉ
Xem bản gốcTrả lời0
DuckFluff
· 07-22 22:44
Ôi trời, số thực lại gây rắc rối rồi~
Xem bản gốcTrả lời0
MaticHoleFiller
· 07-22 22:44
Những người đã gặp phải vấn đề về độ chính xác hãy đến chia sẻ kinh nghiệm!
Xem bản gốcTrả lời0
MetadataExplorer
· 07-22 22:44
Phần số nguyên thường bị bỏ qua, khá quan trọng.
Xem bản gốcTrả lời0
MEVHunter
· 07-22 22:29
độ chính xác là một hũ mật ong mev... giữ cho các float của bạn chặt chẽ hoặc sẽ bị rekt ser
Xem bản gốcTrả lời0
TrustlessMaximalist
· 07-22 22:16
Cái bẫy của phép toán dấu phẩy động này phải tránh xa nhé
Vấn đề độ chính xác trong tính toán số nguyên của hợp đồng thông minh Rust và giải pháp
Nhật ký phát triển hợp đồng thông minh Rust (7): Vấn đề độ chính xác trong tính toán số nguyên
Điểm lại các kỳ trước:
1. Vấn đề độ chính xác trong tính toán số thực
Khác với Solidity, Rust hỗ trợ tính toán số thực một cách tự nhiên. Tuy nhiên, tính toán số thực tồn tại các vấn đề về độ chính xác không thể tránh khỏi, vì vậy không được khuyến nghị sử dụng trong hợp đồng thông minh, đặ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.
Rust tuân theo tiêu chuẩn IEEE 754 để biểu diễn số thực. Kiểu số thực gấp đôi f64 được biểu diễn trong máy tính bằng phương pháp ký hiệu khoa học nhị phân.
Một số số thập phân có thể được biểu diễn chính xác bằng số nhị phân với một số lượng hữu hạn, ví dụ như 0.8125 có thể được biểu diễn là 0.1101. Nhưng số thập phân như 0.7 sẽ tạo ra biểu diễn nhị phân tuần hoàn vô hạn, không thể được biểu diễn chính xác bằng số thực với số lượng hữu hạn, dẫn đến vấn đề "làm tròn".
Trong ví dụ 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ố tiền: {:.20}", amount); assert_eq!(result_0, 0.07); }
Kết quả thực hiện cho thấy giá trị thực của amount là 0.69999999999999995559, result_0 là 0.06999999999999999, không bằng giá trị mong đợi là 0.07.
Để giải quyết vấn đề này, có thể sử dụng số cố định. Trong NEAR, thường dùng 10^24 yoctoNEAR để biểu thị 1 token NEAR. Mã đã đượ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); }
Vậy có thể đạt được kết quả 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ù phép toán số nguyên có thể giải quyết một số vấn đề về độ chính xác của số thực trong một số tình huống, nhưng phép toán số nguyên cũng tồn tại vấn đề về độ chính xác.
2.1 Thứ tự thao tác
Phép nhân và phép chia cùng cấp, thay đổi thứ tự có thể ảnh hưởng đến kết quả:
rỉ sét #[test] fn precision_test_div_before_mul() { let a: u128 = 1_0000; let b: u128 = 10_0000; let c: u128 = 20;
}
Kết quả cho thấy result_0 = 2, result_1 = 0.
Lý do 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 sẽ mất độ chính xác trở thành 0; trong khi result_0 trước tiên tính toán (a * c) để tránh mất độ chính xác.
2.2 độ lớn quá nhỏ
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ả cho thấy result_0 = 12, result_1 = 13, cái sau gần với giá trị thực 13.3333 hơn.
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 sau:
3.1 Điều chỉnh thứ tự thao tác tính toán
Để phép nhân số nguyên ưu tiên hơn phép chia.
3.2 Tăng bậc số nguyên
Sử dụng quy mô lớn hơn, tạo ra phân tử lớn hơn. Như định nghĩa 1 NEAR = 10^24 yoctoNEAR.
3.3 Tích lũy tổn thất độ chính xác của phép toán
Ghi lại và tích lũy tổn thất độ chính xác, bù đắp trong các phép toán tiếp theo:
gỉ const USER_NUM: u128 = 3;
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 }
#( fn record_offset_test)[test] { let mut offset: u128 = 0; cho i trong 1..7 { offset = distribute(10_000_000_000_000_000_000_000_000, offset); } }
( 3.4 Sử dụng thư viện Rust Crate rust-decimal
Thư viện này phù hợp cho các tính toán tài chính với số thập phân yêu cầu độ chính xác cao và không có lỗi 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, việc làm tròn thường tuân theo nguyên tắc "có lợi cho tôi": nếu làm tròn xuống có lợi cho tôi thì làm tròn xuống, nếu làm tròn lên có lợi cho tôi thì làm tròn lên, hiếm khi áp dụng quy tắc làm tròn thông thường.
![]###https://img-cdn.gateio.im/webp-social/moments-6e8b4081214a69423fc7ae022d05c728.webp###