Rust smart contract pengembangan jurnal (7) perhitungan nilai
1. Masalah presisi dalam perhitungan angka pecahan
Bahasa Rust secara native mendukung operasi bilangan pecahan, tetapi operasi bilangan pecahan memiliki masalah presisi perhitungan yang tidak dapat dihindari. Saat menulis smart contract, tidak disarankan untuk menggunakan operasi bilangan pecahan, terutama saat menangani rasio atau suku bunga yang melibatkan keputusan ekonomi/keuangan yang penting.
Dalam bahasa Rust, angka desimal mengikuti standar IEEE 754. Sebagai contoh, tipe float ganda f64, representasi biner internalnya adalah sebagai berikut:
Bilangan pecahan dinyatakan dalam notasi ilmiah dengan basis 2. Misalnya, 0.8125 dapat dinyatakan dengan bilangan biner terbatas 0.1101:
Dapat dilihat bahwa nilai amount tidak tepat 0.7, melainkan nilai mendekati 0.69999999999999995559. Hasil dari operasi pembagian lebih lanjut juga menjadi tidak akurat 0.06999999999999999.
Untuk menyelesaikan masalah ini, dapat dipertimbangkan untuk menggunakan bilangan tetap. Di NEAR Protocol, biasanya digunakan metode representasi 10^24 yoctoNEAR setara dengan 1 token NEAR.
Kode pengujian yang telah diperbarui:
karat
#[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, "");
}
Hasil eksekusi:
menjalankan 1 tes
uji uji::tes_presisi_integer ... ok
hasil uji: ok. 1 lulus; 0 gagal; 0 diabaikan; 0 diukur; 8 disaring; selesai dalam 0.00s
2. Masalah Presisi Perhitungan Bilangan Bulat Rust
2.1 Urutan Operasi
Perubahan urutan antara perkalian dan pembagian dengan prioritas aritmatika yang sama dapat secara langsung mempengaruhi hasil perhitungan, menyebabkan masalah presisi perhitungan bilangan bulat.
karat
#[test]
fn precision_test_div_before_mul() {
let a: u128 = 1_0000;
let b: u128 = 10_0000;
let c: u128 = 20;
Dapat ditemukan bahwa result_0 = a * c / b dan result_1 = (a / b) * c meskipun rumus perhitungannya sama, hasilnya berbeda. Alasannya adalah pembagian bilangan bulat akan mengabaikan presisi yang lebih kecil dari pembagi. Dalam perhitungan result_1, menghitung (a / b) terlebih dahulu akan mengakibatkan kehilangan presisi menjadi 0; sedangkan dalam perhitungan result_0, terlebih dahulu dihitung a * c = 20_0000 yang lebih besar dari pembagi b, sehingga menghindari kehilangan presisi.
2.2 skala yang terlalu kecil
karat
#[test]
fn precision_test_decimals() {
let a: u128 = 10;
let b: u128 = 3;
let c: u128 = 4;
let decimal: u128 = 100_0000;
Dapat dilihat bahwa hasil result_0 dan result_1 yang ekuivalen dalam proses perhitungan berbeda, dan result_1 = 13 lebih mendekati ekspektasi nyata yaitu 13.3333....
3. Cara Menulis Kontrak Pintar Rust untuk Actuari Numerik
3.1 Menyesuaikan urutan operasi perhitungan
Mengutamakan perkalian integer dibandingkan dengan pembagian integer.
3.2 meningkatkan jumlah bilangan bulat
Bilangan bulat menggunakan lebih banyak urutan, menciptakan pecahan yang lebih besar.
3.3 Kerugian presisi akumulasi operasi
Untuk masalah presisi perhitungan integer yang tidak dapat dihindari, Anda dapat mempertimbangkan untuk mencatat akumulasi kehilangan presisi perhitungan.
karat
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] {
biarkan mutasi offset: u128 = 0;
untuk i dalam 1..7 {
println!("Round {}", i);
offset = distribute(10_000_000_000_000_000_000_000_000, offset);
println!("Offset {}\n", offset);
}
}
test tests::record_offset_test ... ok
hasil tes: ok. 1 lulus; 0 gagal; 0 diabaikan; 0 diukur; 9 difilter; selesai dalam 0.00s
( 3.4 Menggunakan pustaka Rust Crate rust-decimal
Perpustakaan ini cocok untuk perhitungan keuangan desimal yang memerlukan perhitungan presisi yang efektif dan tanpa kesalahan pembulatan.
) 3.5 Pertimbangkan mekanisme pembulatan
Dalam merancang smart contract, masalah pembulatan biasanya mengikuti prinsip "saya ingin diuntungkan, orang lain tidak boleh mengambil keuntungan dari saya". Berdasarkan prinsip ini, jika pembulatan ke bawah menguntungkan saya, maka dibulatkan ke bawah; jika pembulatan ke atas menguntungkan saya, maka dibulatkan ke atas; pembulatan standar tidak dapat menentukan siapa yang diuntungkan, sehingga jarang digunakan.
Halaman ini mungkin berisi konten pihak ketiga, yang disediakan untuk tujuan informasi saja (bukan pernyataan/jaminan) dan tidak boleh dianggap sebagai dukungan terhadap pandangannya oleh Gate, atau sebagai nasihat keuangan atau profesional. Lihat Penafian untuk detailnya.
17 Suka
Hadiah
17
5
Bagikan
Komentar
0/400
LiquidationKing
· 07-16 06:01
Apa yang kamu katakan tentang 0.7 bukanlah hal besar, butuh kejatuhan besar agar menarik.
Lihat AsliBalas0
BankruptcyArtist
· 07-16 05:57
Aduh, menulis smart contract memang pernah sangat merugikan karena angka desimal.
Lihat AsliBalas0
GmGmNoGn
· 07-16 05:55
Bug ini bisa membuat akurasi saya hilang.
Lihat AsliBalas0
NotSatoshi
· 07-16 05:55
Dalam smart contract, bermain dengan floating point, kepala bisa meledak.
Perhitungan nilai kontrak pintar Rust: jerat floating point dan optimasi presisi integer
Rust smart contract pengembangan jurnal (7) perhitungan nilai
1. Masalah presisi dalam perhitungan angka pecahan
Bahasa Rust secara native mendukung operasi bilangan pecahan, tetapi operasi bilangan pecahan memiliki masalah presisi perhitungan yang tidak dapat dihindari. Saat menulis smart contract, tidak disarankan untuk menggunakan operasi bilangan pecahan, terutama saat menangani rasio atau suku bunga yang melibatkan keputusan ekonomi/keuangan yang penting.
Dalam bahasa Rust, angka desimal mengikuti standar IEEE 754. Sebagai contoh, tipe float ganda f64, representasi biner internalnya adalah sebagai berikut:
Bilangan pecahan dinyatakan dalam notasi ilmiah dengan basis 2. Misalnya, 0.8125 dapat dinyatakan dengan bilangan biner terbatas 0.1101:
0.8125 = 0.5 * 1 + 0.25 * 1 + 0.125 * 0 + 0.0625 * 1
Namun untuk desimal kecil seperti 0.7, akan muncul situasi siklus tak terbatas:
0.7 = 0.1011001100110011...
Hal ini menyebabkan angka pecahan dengan panjang terbatas tidak dapat diwakili secara akurat, dan ada fenomena "pembulatan".
Sebagai contoh, mendistribusikan 0,7 token NEAR kepada sepuluh pengguna di blockchain NEAR:
karat #[test] fn precision_test_float() { let amount: f64 = 0.7;
let divisor: f64 = 10.0;
let result_0 = amount / divisor;
println!("Nilai dari jumlah: {:.20}", amount); assert_eq!(result_0, 0.07, ""); }
Hasil eksekusi:
menjalankan 1 tes Nilai jumlah: 0.69999999999999995559 benang 'tests::precision_test_float' panik di 'kegagalan pernyataan: (left == right) kiri: 0.06999999999999999, kanan: 0.07: ', src/lib.rs:185:9
Dapat dilihat bahwa nilai amount tidak tepat 0.7, melainkan nilai mendekati 0.69999999999999995559. Hasil dari operasi pembagian lebih lanjut juga menjadi tidak akurat 0.06999999999999999.
Untuk menyelesaikan masalah ini, dapat dipertimbangkan untuk menggunakan bilangan tetap. Di NEAR Protocol, biasanya digunakan metode representasi 10^24 yoctoNEAR setara dengan 1 token NEAR.
Kode pengujian yang telah diperbarui:
karat #[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, ""); }
Hasil eksekusi:
menjalankan 1 tes uji uji::tes_presisi_integer ... ok hasil uji: ok. 1 lulus; 0 gagal; 0 diabaikan; 0 diukur; 8 disaring; selesai dalam 0.00s
2. Masalah Presisi Perhitungan Bilangan Bulat Rust
2.1 Urutan Operasi
Perubahan urutan antara perkalian dan pembagian dengan prioritas aritmatika yang sama dapat secara langsung mempengaruhi hasil perhitungan, menyebabkan masalah presisi perhitungan bilangan bulat.
karat #[test] fn precision_test_div_before_mul() { let a: u128 = 1_0000; let b: u128 = 10_0000; let c: u128 = 20;
.checked_mul(c) .expect("ERR_MUL") .checked_div(b) .expect("ERR_DIV");
}
Hasil eksekusi:
menjalankan 1 tes benang 'tests::precision_test_0' panik di 'penegasan gagal: (left == right) left: 2, right: 0: ', src/lib.rs:175:9
Dapat ditemukan bahwa result_0 = a * c / b dan result_1 = (a / b) * c meskipun rumus perhitungannya sama, hasilnya berbeda. Alasannya adalah pembagian bilangan bulat akan mengabaikan presisi yang lebih kecil dari pembagi. Dalam perhitungan result_1, menghitung (a / b) terlebih dahulu akan mengakibatkan kehilangan presisi menjadi 0; sedangkan dalam perhitungan result_0, terlebih dahulu dihitung a * c = 20_0000 yang lebih besar dari pembagi b, sehingga menghindari kehilangan presisi.
2.2 skala yang terlalu kecil
karat #[test] fn precision_test_decimals() { let a: u128 = 10; let b: u128 = 3; let c: u128 = 4; let decimal: u128 = 100_0000;
.checked_div(b) .expect("ERR_DIV") .checked_mul(c) .expect("ERR_MUL");
}
Hasil eksekusi:
menjalankan 1 tes 12:13 benang 'tes::precision_test_decimals' panik di 'penegasan gagal: (left == right) kiri: 12, kanan: 13: ', src/lib.rs:214:9
Dapat dilihat bahwa hasil result_0 dan result_1 yang ekuivalen dalam proses perhitungan berbeda, dan result_1 = 13 lebih mendekati ekspektasi nyata yaitu 13.3333....
3. Cara Menulis Kontrak Pintar Rust untuk Actuari Numerik
3.1 Menyesuaikan urutan operasi perhitungan
3.2 meningkatkan jumlah bilangan bulat
3.3 Kerugian presisi akumulasi operasi
Untuk masalah presisi perhitungan integer yang tidak dapat dihindari, Anda dapat mempertimbangkan untuk mencatat akumulasi kehilangan presisi perhitungan.
karat 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] { biarkan mutasi offset: u128 = 0; untuk i dalam 1..7 { println!("Round {}", i); offset = distribute(10_000_000_000_000_000_000_000_000, offset); println!("Offset {}\n", offset); } }
Hasil eksekusi:
menjalankan 1 tes Putaran 1 per_user_share 3333333333333333333333333 Offset 1
Putaran 2 per_user_share 3333333333333333333333333 Offset 2
Putaran 3 per_user_share 4000000000000000000000000 Offset 0
Putaran 4 per_user_share 3333333333333333333333333 Offset 1
Putaran 5 per_user_share 3333333333333333333333333 Offset 2
Putaran 6 per_user_share 4000000000000000000000000 Offset 0
test tests::record_offset_test ... ok hasil tes: ok. 1 lulus; 0 gagal; 0 diabaikan; 0 diukur; 9 difilter; selesai dalam 0.00s
( 3.4 Menggunakan pustaka Rust Crate rust-decimal
Perpustakaan ini cocok untuk perhitungan keuangan desimal yang memerlukan perhitungan presisi yang efektif dan tanpa kesalahan pembulatan.
) 3.5 Pertimbangkan mekanisme pembulatan
Dalam merancang smart contract, masalah pembulatan biasanya mengikuti prinsip "saya ingin diuntungkan, orang lain tidak boleh mengambil keuntungan dari saya". Berdasarkan prinsip ini, jika pembulatan ke bawah menguntungkan saya, maka dibulatkan ke bawah; jika pembulatan ke atas menguntungkan saya, maka dibulatkan ke atas; pembulatan standar tidak dapat menentukan siapa yang diuntungkan, sehingga jarang digunakan.
![]###https://img-cdn.gateio.im/webp-social/moments-1933a4a2dd723a847f0059d31d1780d1.webp###