Rust akıllı sözleşmeler geliştirmedeki sayısal hesaplama hassasiyeti sorunları
1. Kesirli sayı işlemlerinin hassasiyet sorunu
Rust dilinin yerel olarak ondalık sayı işlemlerini desteklemesine rağmen, ondalık sayı işlemleri kaçınılmaz hesaplama hassasiyeti sorunları taşır. Akıllı sözleşmeler yazarken, özellikle önemli ekonomik/finansal kararların oranları veya faiz oranlarıyla ilgili işlemler yaparken ondalık sayı işlemlerinin kullanılmasını önerilmez.
Rust dilinde çift hassasiyetli kayan nokta türü f64, IEEE 754 standardını takip eder ve 2 tabanlı bilimsel gösterim kullanır. Bazı ondalık sayılar, sonlu uzunlukta kayan sayılarla kesin olarak temsil edilemez ve "yuvarlama" olayı vardır.
Örneğin, NEAR blok zincirinde 10 kullanıcıya 0.7 NEAR token dağıtıldığında:
pas
#[test]
fn precision_test_float() {
let amount: f64 = 0.7;
let divisor: f64 = 10.0;
let result_0 = amount / divisor;
println!("Miktarın değeri: {:.20}", amount);
assert_eq!(result_0, 0.07, "");
}
Çıktı sonucu amount'un değeri 0.69999999999999995559 olarak gösteriliyor, bu da kesin 0.7 değil. Bölme işleminin sonucu da beklenen 0.07 yerine hassas olmayan 0.06999999999999999 oldu.
Bu sorunu çözmek için sabit nokta sayıları kullanmayı düşünebilirsiniz. NEAR Protokolü'nde, 1 NEAR token'ı temsil etmek için genellikle 10^24 yoctoNEAR kullanılır. Değiştirilmiş test kodu:
pas
#[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, "");
}
Bu şekilde kesin hesaplama sonucu elde edilebilir: 0.7 NEAR / 10 = 0.07 NEAR.
2. Rust tam sayı hesaplamalarında hassasiyet sorunu
Tam sayı işlemleri, bazı senaryolarda kayan nokta işlemlerinin hassasiyet kaybı sorununu çözebilir, ancak tam sayı hesaplama sonuçları da tamamen doğru ve güvenilir değildir.
2.1 İşlem Sırası
Aynı aritmetik önceliğe sahip çarpma ve bölme işlemlerinin sırasının değiştirilmesi, hesaplama sonuçlarını doğrudan etkileyebilir ve tam sayı hesaplama doğruluğu sorunlarına yol açabilir. Örneğin:
pas
#[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,"");
}
Yürütme sonucu, result_0 ile result_1'in eşit olmadığını gösteriyor. Bunun nedeni, tam sayı bölmesinin, bölenin altındaki hassasiyeti atmasıdır. result_1'i hesaplarken, (a / b) önce hassasiyeti kaybedip 0'a dönüşüyor; oysa result_0'ı hesaplarken, a * c hesaplanarak hassasiyet kaybı önleniyor.
2.2 çok küçük bir büyüklük
Ondalık hesaplamalar söz konusu olduğunda, tam sayılı işlemler hassasiyet kaybına neden olabilir:
pas
#[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, "");
}
Sonuçlar gösteriyor ki result_0=12, result_1=13, ancak beklenen değer 13.3333....
3. Sayısal Aktüeryalık için Rust akıllı sözleşmeleri nasıl yazılır
Hassasiyeti artırmak için aşağıdaki koruma önlemleri alınabilir:
3.1 İşlem sırasını ayarlama
Tam sayı çarpımının tam sayı bölümüne öncelik vermesini sağla.
3.2 Tam sayının büyüklüğünü artırın
Daha büyük bir ölçek kullanarak, daha büyük moleküller yaratın. Örneğin, 5.123 NEAR'ı göstermek için 5.123 * 10^10 = 51_230_000_000 ile sonraki hesaplamalara katılabilirsiniz.
3.3 Birikim işlemi hassasiyeti kaybı
Kaçınılmaz hassasiyet sorunları için, biriken hesaplama hassasiyet kaybını kaydedebilirsiniz. Örneğin:
pas
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;
kaydedilen_offset
}
#(
fn record_offset_test)[test] {
let mut offset: u128 = 0;
for i in 1..7 {
println!("Round {}", i);
offset = distribute(to_yocto)"10"(, offset(;
println!)"Offset {}\n", offset);
}
}
Bu yöntem, her dağıtımda kalan token'ları biriktirerek sonraki dağıtımda birlikte vermeyi sağlar ve nihayetinde yeterli dağıtım amacına ulaşır.
Bu kütüphane, doğru hassasiyet hesaplaması ve yuvarlama hatası olmayan ondalık finansal hesaplamalar için uygundur.
) 3.5 Yuvarlama mekanizmasını dikkate alma
Akıllı sözleşme tasarımında, yuvarlama sorunları genellikle "Ben kazanç sağlamak istiyorum, diğerleri benim kazancımı alamaz" ilkesine göre ele alınır. Duruma göre aşağıya veya yukarıya yuvarlama seçilir, nadiren en yakın tam sayıya yuvarlama yapılır.
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.
Rust akıllı sözleşmeler geliştirme: Sayısal hesaplama hassasiyeti sorununu aşma
Rust akıllı sözleşmeler geliştirmedeki sayısal hesaplama hassasiyeti sorunları
1. Kesirli sayı işlemlerinin hassasiyet sorunu
Rust dilinin yerel olarak ondalık sayı işlemlerini desteklemesine rağmen, ondalık sayı işlemleri kaçınılmaz hesaplama hassasiyeti sorunları taşır. Akıllı sözleşmeler yazarken, özellikle önemli ekonomik/finansal kararların oranları veya faiz oranlarıyla ilgili işlemler yaparken ondalık sayı işlemlerinin kullanılmasını önerilmez.
Rust dilinde çift hassasiyetli kayan nokta türü f64, IEEE 754 standardını takip eder ve 2 tabanlı bilimsel gösterim kullanır. Bazı ondalık sayılar, sonlu uzunlukta kayan sayılarla kesin olarak temsil edilemez ve "yuvarlama" olayı vardır.
Örneğin, NEAR blok zincirinde 10 kullanıcıya 0.7 NEAR token dağıtıldığında:
pas #[test] fn precision_test_float() { let amount: f64 = 0.7;
let divisor: f64 = 10.0;
let result_0 = amount / divisor;
println!("Miktarın değeri: {:.20}", amount); assert_eq!(result_0, 0.07, ""); }
Çıktı sonucu amount'un değeri 0.69999999999999995559 olarak gösteriliyor, bu da kesin 0.7 değil. Bölme işleminin sonucu da beklenen 0.07 yerine hassas olmayan 0.06999999999999999 oldu.
Bu sorunu çözmek için sabit nokta sayıları kullanmayı düşünebilirsiniz. NEAR Protokolü'nde, 1 NEAR token'ı temsil etmek için genellikle 10^24 yoctoNEAR kullanılır. Değiştirilmiş test kodu:
pas #[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, ""); }
Bu şekilde kesin hesaplama sonucu elde edilebilir: 0.7 NEAR / 10 = 0.07 NEAR.
2. Rust tam sayı hesaplamalarında hassasiyet sorunu
Tam sayı işlemleri, bazı senaryolarda kayan nokta işlemlerinin hassasiyet kaybı sorununu çözebilir, ancak tam sayı hesaplama sonuçları da tamamen doğru ve güvenilir değildir.
2.1 İşlem Sırası
Aynı aritmetik önceliğe sahip çarpma ve bölme işlemlerinin sırasının değiştirilmesi, hesaplama sonuçlarını doğrudan etkileyebilir ve tam sayı hesaplama doğruluğu sorunlarına yol açabilir. Örneğin:
pas #[test] fn precision_test_div_before_mul() { let a: u128 = 1_0000; let b: u128 = 10_0000; let c: u128 = 20;
}
Yürütme sonucu, result_0 ile result_1'in eşit olmadığını gösteriyor. Bunun nedeni, tam sayı bölmesinin, bölenin altındaki hassasiyeti atmasıdır. result_1'i hesaplarken, (a / b) önce hassasiyeti kaybedip 0'a dönüşüyor; oysa result_0'ı hesaplarken, a * c hesaplanarak hassasiyet kaybı önleniyor.
2.2 çok küçük bir büyüklük
Ondalık hesaplamalar söz konusu olduğunda, tam sayılı işlemler hassasiyet kaybına neden olabilir:
pas #[test] fn precision_test_decimals() { let a: u128 = 10; let b: u128 = 3; let c: u128 = 4; let decimal: u128 = 100_0000;
}
Sonuçlar gösteriyor ki result_0=12, result_1=13, ancak beklenen değer 13.3333....
3. Sayısal Aktüeryalık için Rust akıllı sözleşmeleri nasıl yazılır
Hassasiyeti artırmak için aşağıdaki koruma önlemleri alınabilir:
3.1 İşlem sırasını ayarlama
Tam sayı çarpımının tam sayı bölümüne öncelik vermesini sağla.
3.2 Tam sayının büyüklüğünü artırın
Daha büyük bir ölçek kullanarak, daha büyük moleküller yaratın. Örneğin, 5.123 NEAR'ı göstermek için 5.123 * 10^10 = 51_230_000_000 ile sonraki hesaplamalara katılabilirsiniz.
3.3 Birikim işlemi hassasiyeti kaybı
Kaçınılmaz hassasiyet sorunları için, biriken hesaplama hassasiyet kaybını kaydedebilirsiniz. Örneğin:
pas 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; kaydedilen_offset }
#( fn record_offset_test)[test] { let mut offset: u128 = 0; for i in 1..7 { println!("Round {}", i); offset = distribute(to_yocto)"10"(, offset(; println!)"Offset {}\n", offset); } }
Bu yöntem, her dağıtımda kalan token'ları biriktirerek sonraki dağıtımda birlikte vermeyi sağlar ve nihayetinde yeterli dağıtım amacına ulaşır.
( 3.4 Rust Crate rust-decimal kütüphanesini kullanma
Bu kütüphane, doğru hassasiyet hesaplaması ve yuvarlama hatası olmayan ondalık finansal hesaplamalar için uygundur.
) 3.5 Yuvarlama mekanizmasını dikkate alma
Akıllı sözleşme tasarımında, yuvarlama sorunları genellikle "Ben kazanç sağlamak istiyorum, diğerleri benim kazancımı alamaz" ilkesine göre ele alınır. Duruma göre aşağıya veya yukarıya yuvarlama seçilir, nadiren en yakın tam sayıya yuvarlama yapılır.
![]###https://img-cdn.gateio.im/webp-social/moments-6e8b4081214a69423fc7ae022d05c728.webp###