Rust akıllı sözleşmeler sayısal hesaplama: kayan nokta tuzakları ve tam sayı hassasiyeti optimizasyonu

Rust akıllı sözleşmeler yetiştirme günlüğü (7) Sayısal hesaplama

1. Ondalık Sayı Hesaplamalarının Hassasiyet Sorunu

Rust dili yerel olarak ondalık sayı hesaplamalarını destekler, ancak ondalık sayı hesaplamalarında kaçınılmaz hesaplama hassasiyeti sorunları vardır. Akıllı sözleşmeler yazarken, özellikle önemli ekonomik/finansal kararlarla ilgili oranlar veya faiz oranlarıyla uğraşırken ondalık sayı hesaplamalarını kullanmanız önerilmez.

Rust dilinde ondalık sayılar IEEE 754 standardını takip eder. Çift hassasiyetli ondalık türü f64 örneği olarak, içsel ikili temsil aşağıdaki gibidir:

Kayan noktalı sayılar, tabanı 2 olan bilimsel notasyonla ifade edilir. Örneğin, 0.8125, sonlu basamaklı ikili sayı 0.1101 ile temsil edilebilir:

0.8125 = 0.5 * 1 + 0.25 * 1 + 0.125 * 0 + 0.0625 * 1

Ancak 0.7 gibi bir ondalık sayı için sonsuz döngü durumu ortaya çıkabilir:

0.7 = 0.1011001100110011...

Bu, sınırlı uzunluktaki kayan noktalı sayılarla doğru bir şekilde ifade edilememesine ve "yuvarlama" olgusunun ortaya çıkmasına neden olur.

NEAR blok zincirinde on kullanıcıya 0.7 NEAR token dağıtımı örneği olarak:

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, ""); }

İcra sonucu:

1 test çalıştırma Miktarın değeri: 0.69999999999999995559 thread 'tests::precision_test_float' panicked at 'assertion failed: (sol == sağ) left: 0.06999999999999999, right: 0.07: ', src/lib.rs:185:9

Görülebilir ki amount'un değeri tam olarak 0.7 değildir, bunun yerine yaklaşık bir değer olan 0.69999999999999995559'dur. Daha ileri bölme işleminin sonucu da kesin olmayan 0.06999999999999999 haline gelmiştir.

Bu sorunu çözmek için, sabit nokta sayısı kullanmayı düşünebilirsiniz. NEAR Protokolü'nde, genellikle 10^24 yoctoNEAR'ın 1 NEAR token'ına eşdeğer olduğu gösterim yöntemi kullanılır.

Değiştirilen 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, ""); }

İcra sonucu:

1 test çalıştırma test testleri::kesinlik_testi_tamsayı ... tamam test sonucu: tamam. 1 geçti; 0 başarısız; 0 göz ardı edildi; 0 ölçüldü; 8 filtrelendi; 0.00s içinde tamamlandı

2. Rust tam sayı hesaplamalarının hassasiyeti ile ilgili sorun

2.1 İşlem Sırası

Aynı aritmetik önceliğe sahip çarpma ve bölme işlemlerinin sıralarındaki değişiklik, hesaplama sonuçlarını doğrudan etkileyebilir ve tam sayı hesaplama hassasiyeti sorunlarına yol açabilir.

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,"");

}

İşlem sonucu:

1 test çalıştırma thread 'tests::precision_test_0' panicked at 'assertion failed: (left == right) sol: 2, sağ: 0: ', src/lib.rs:175:9

result_0 = a * c / b ve result_1 = (a / b) * c ifadelerinin hesaplama formülleri aynı olmasına rağmen, sonuçlar farklıdır. Bunun nedeni, tamsayı bölmesinin paydanın altındaki hassasiyeti atmasıdır. result_1'i hesaplarken, önce (a / b) hesaplamak, hassasiyet kaybına neden olup sonucu 0'a düşürür; oysa result_0'ı hesaplarken, önce a * c = 20_0000 hesaplanarak paydanın b'den büyük olması sağlanır ve bu da hassasiyet kaybını önler.

2.2 çok küçük bir ölçek

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, "");

}

Uygulama sonucu:

1 test çalıştırma 12:13 thread 'tests::precision_test_decimals' panicked at 'assertion failed: (left == right) sol: 12, sağ: 13: ', src/lib.rs:214:9

Görülebilir hesaplama sürecinin eşdeğer olan result_0 ve result_1 sonuçları farklıdır ve result_1 = 13, gerçek beklenen 13.3333....'e daha yakındır.

3. Sayısal Aktüeryal Rust akıllı sözleşmeler nasıl yazılır

3.1 İşlem sırasını ayarlama

  • Tam sayılarla çarpma, tam sayılarla bölmeden önce gelir.

3.2 Tam sayının büyüklüğünü artırma

  • Tam sayılar daha büyük bir sayı ölçeği kullanarak daha büyük paylar oluşturur.

3.3 Birikim hesaplama hassasiyetinin kaybı

Kaçınılmaz tam sayı hesaplama hassasiyeti sorunları için, birikmiş hesaplama hassasiyeti kaybını kaydetmeyi düşünebilirsiniz.

pas const USER_NUM: u128 = 3;

u128 { let dağıtılacak_token = offset + amount; let per_user_share = token_to_distribute / USER_NUM; println!("kullanıcı_başına_hisse {}", kullanıcı_başına_hisse); let recorded_offset = token_to_distribute - per_user_share * USER_NUM; kayıtlı_offset }

#( fn record_offset_test)[test] { let mut offset: u128 = 0; for i in 1..7 { println!("Round {}", i); offset = distribute(10_000_000_000_000_000_000_000_000, offset); println!("Offset {}\n", offset); } }

İşlem sonucu:

1 test çalıştırma

  1. Tur per_user_share 3333333333333333333333333 Offset 1

  2. Tur per_user_share 3333333333333333333333333 Offset 2

  3. Tur per_user_share 4000000000000000000000000 Offset 0

Dördüncü Tur per_user_share 3333333333333333333333333 Offset 1

  1. Tur per_user_share 3333333333333333333333333 Offset 2

  2. Raunt per_user_share 4000000000000000000000000 Offset 0

test testleri::kaydet_offset_test ... tamam test sonucu: tamam. 1 geçti; 0 başarısız; 0 göz ardı edildi; 0 ölçüldü; 9 filtrelendi; 0.00s içinde tamamlandı

( 3.4 Rust Crate kütüphanesi rust-decimal kullanımı

Bu kütüphane, kesin hesaplama gerektiren ve yuvarlama hatası olmayan ondalık finansal hesaplamalar için uygundur.

) 3.5 Yuvarlama mekanizmasını dikkate alın

Akıllı sözleşmeler tasarlanırken, yuvarlama sorunu genellikle "Ben kârlı çıkmalıyım, başkaları benden yararlanamaz" prensibi ile ele alınır. Bu prensibe göre, eğer aşağı yuvarlama benim için uygunsa, aşağı; yukarı yuvarlama benim için uygunsa, yukarı; dört başı mamur yuvarlama ise kimin yararına olduğunu belirlemek zor olduğundan nadiren kullanılır.

![]###https://img-cdn.gateio.im/webp-social/moments-1933a4a2dd723a847f0059d31d1780d1.webp###

View Original
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.
  • Reward
  • 5
  • Share
Comment
0/400
LiquidationKingvip
· 13h ago
Senin bahsettiğin 0.7 çok da önemli bir şey değil, büyük bir çöküş olursa daha heyecanlı olur.
View OriginalReply0
BankruptcyArtistvip
· 13h ago
Ah, akıllı sözleşmeler yazarken gerçekten ondalık sayılar yüzünden berbat oldum.
View OriginalReply0
GmGmNoGnvip
· 13h ago
Bu hata benim hassasiyetimi tamamen kaybettirdi.
View OriginalReply0
NotSatoshivip
· 13h ago
akıllı sözleşmelerde ondalık sayı oynamak kafayı patlatacak
View OriginalReply0
SandwichVictimvip
· 13h ago
Schrödinger'in hassasiyet sorunu
View OriginalReply0
Trade Crypto Anywhere Anytime
qrCode
Scan to download Gate app
Community
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)