تدعم لغة Rust العمليات الحسابية للأرقام العائمة بشكل أصلي، ولكن توجد مشاكل دقة حسابية لا مفر منها في العمليات الحسابية للأرقام العائمة. عند كتابة العقود الذكية، لا يُوصى باستخدام العمليات الحسابية للأرقام العائمة، خاصة عند التعامل مع النسب أو معدلات الفائدة التي تنطوي على قرارات اقتصادية/مالية مهمة.
تتبع الأعداد العشرية في لغة Rust معيار IEEE 754. على سبيل المثال، نوع النقطة العائمة المزدوجة f64، تمثيله الثنائي الداخلي كما يلي:
!
تُعبر الأعداد العشرية باستخدام صيغة الأعداد العلمية ذات الأساس 2. على سبيل المثال، يمكن تمثيل 0.8125 باستخدام عدد ثنائي محدود الأرقام 0.1101:
تشغيل اختبار واحد
قيمة المبلغ: 0.69999999999999995559
سلسلة "tests::precision_test_float" انهارت بسبب 'فشل التأكيد: (left == right)
إلى اليسار: 0.06999999999999999999 ، يمين: 0.07: '، src / lib.rs: 185: 9
من الواضح أن قيمة amount ليست بالضبط 0.7، بل هي قيمة تقريبية 0.69999999999999995559. كما أن نتيجة عملية القسمة اللاحقة أصبحت غير دقيقة أيضًا 0.06999999999999999.
لحل هذه المشكلة، يمكن النظر في استخدام الأعداد الصحيحة. في بروتوكول NEAR، يتم عادة استخدام طريقة تمثيل 10^24 من yoctoNEAR يعادل 1 من رمز NEAR.
تشغيل اختبار واحد
اختبار الاختبارات::اختبار_الدقة_صحيح ... حسنًا
نتيجة الاختبار: جيد. 1 ناجح؛ 0 فاشل؛ 0 متجاهل؛ 0 مقيس؛ 8 تم تصفيتهم؛ انتهى في 0.00 ثانية
2. مشكلة دقة حسابات الأعداد الصحيحة في Rust
2.1 ترتيب العمليات
قد تؤثر تغييرات ترتيب الضرب والقسمة ذات أولوية الحساب نفسها بشكل مباشر على نتيجة الحساب، مما يؤدي إلى مشكلة دقة الحسابات الصحيحة.
تشغيل اختبار واحد
الخيط 'tests::precision_test_0' انهار عند 'فشل التحقق: (left == right)
اليسار: 2 ، اليمين: 0: '، src / lib.rs: 175: 9
يمكن ملاحظة أن result_0 = a * c / b و result_1 = (a / b) * c على الرغم من أن الصيغ الحسابية متشابهة، إلا أن النتائج مختلفة. السبب هو أن القسمة الصحيحة تتجاهل الدقة التي تقل عن المقام. عند حساب result_1، يؤدي حساب (a / b) أولاً إلى فقدان الدقة وتحويلها إلى 0؛ بينما عند حساب result_0، يتم حساب a * c = 20_0000 أولاً، وهو أكبر من المقام b، مما يتجنب فقدان الدقة.
اختبارات الاختبار::record_offset_test ... ok
نتيجة الاختبار: جيدة. 1 ناجح; 0 فشل; 0 تم تجاهله; 0 تم قياسه; 9 تم تصفيته; انتهى في 0.00 ثانية
3.4 استخدام مكتبة Rust Crate rust-decimal
تتناسب هذه المكتبة مع الحسابات المالية العشرية التي تتطلب دقة حساب فعالة ولا تحتوي على أخطاء في التقريب.
3.5 اعتبر آلية التقريب
عند تصميم العقود الذكية، تُستخدم عادةً مشكلة التقريب وفقًا لمبدأ "أريد الاستفادة، ولا ينبغي للآخرين استغلالي". وفقًا لهذا المبدأ، إذا كان التقريب لأسفل مفيدًا لي، فسأقوم بالتقريب لأسفل؛ وإذا كان التقريب لأعلى مفيدًا لي، فسأقوم بالتقريب لأعلى؛ حيث أن التقريب لأقرب عدد لا يمكن تحديد من هو المستفيد منه، لذلك نادراً ما يتم استخدامه.
قد تحتوي هذه الصفحة على محتوى من جهات خارجية، يتم تقديمه لأغراض إعلامية فقط (وليس كإقرارات/ضمانات)، ولا ينبغي اعتباره موافقة على آرائه من قبل Gate، ولا بمثابة نصيحة مالية أو مهنية. انظر إلى إخلاء المسؤولية للحصول على التفاصيل.
تسجيلات الإعجاب 15
أعجبني
15
5
مشاركة
تعليق
0/400
LiquidationKing
· منذ 10 س
ما تقوله عن 0.7 ليس شيئًا كبيرًا، انهيار كبير سيكون مثيرًا.
شاهد النسخة الأصليةرد0
BankruptcyArtist
· منذ 10 س
آه، لقد تعرضت حقًا لمشاكل بسبب الأرقام العشرية عندما كتبت العقود الذكية.
تحسين دقة الأعداد الصحيحة والفخاخ العائمة في العقود الذكية بلغة Rust
Rustالعقود الذكية养成日记(7)数值精算
1. مشكلة دقة العمليات العشرية
تدعم لغة Rust العمليات الحسابية للأرقام العائمة بشكل أصلي، ولكن توجد مشاكل دقة حسابية لا مفر منها في العمليات الحسابية للأرقام العائمة. عند كتابة العقود الذكية، لا يُوصى باستخدام العمليات الحسابية للأرقام العائمة، خاصة عند التعامل مع النسب أو معدلات الفائدة التي تنطوي على قرارات اقتصادية/مالية مهمة.
تتبع الأعداد العشرية في لغة Rust معيار IEEE 754. على سبيل المثال، نوع النقطة العائمة المزدوجة f64، تمثيله الثنائي الداخلي كما يلي:
!
تُعبر الأعداد العشرية باستخدام صيغة الأعداد العلمية ذات الأساس 2. على سبيل المثال، يمكن تمثيل 0.8125 باستخدام عدد ثنائي محدود الأرقام 0.1101:
0.8125 = 0.5 * 1 + 0.25 * 1 + 0.125 * 0 + 0.0625 * 1
ومع ذلك، بالنسبة للأعداد العشرية الصغيرة مثل 0.7، قد تظهر حالات تكرارية لا نهائية:
0.7 = 0.1011001100110011...
هذا يؤدي إلى عدم القدرة على تمثيل الأعداد العشرية بدقة باستخدام أرقام عائمة ذات طول محدود، مما يؤدي إلى ظاهرة "التقريب".
باستخدام توزيع 0.7 من رموز NEAR على عشرة مستخدمين على سلسلة NEAR العامة كمثال:
صدأ #[test] الجبهة precision_test_float() { دع المبلغ: F64 = 0.7 ؛
دع المقسوم عليه: F64 = 10.0 ؛
دع result_0 = الكمية / القاسم ؛
println!("قيمة المبلغ: {:.20}", amount); assert_eq!(result_0 ، 0.07 ، "") ؛ }
نتيجة التنفيذ:
تشغيل اختبار واحد قيمة المبلغ: 0.69999999999999995559 سلسلة "tests::precision_test_float" انهارت بسبب 'فشل التأكيد: (left == right) إلى اليسار: 0.06999999999999999999 ، يمين: 0.07: '، src / lib.rs: 185: 9
من الواضح أن قيمة amount ليست بالضبط 0.7، بل هي قيمة تقريبية 0.69999999999999995559. كما أن نتيجة عملية القسمة اللاحقة أصبحت غير دقيقة أيضًا 0.06999999999999999.
لحل هذه المشكلة، يمكن النظر في استخدام الأعداد الصحيحة. في بروتوكول NEAR، يتم عادة استخدام طريقة تمثيل 10^24 من yoctoNEAR يعادل 1 من رمز NEAR.
كود الاختبار المعدل:
صدأ #[test] الجبهة الوطنية precision_test_integer() { دع N: u128 = 1_000_000_000_000_000_000_000_000_000 ؛
المبلغ اليدخ: U128 = 700_000_000_000_000_000_000_000 ؛ دع المقسوم: U128 = 10 ؛
دع result_0 = الكمية / القاسم ؛ assert_eq!(result_0, 70_000_000_000_000_000_000_000_000, ""); }
نتيجة التنفيذ:
تشغيل اختبار واحد اختبار الاختبارات::اختبار_الدقة_صحيح ... حسنًا نتيجة الاختبار: جيد. 1 ناجح؛ 0 فاشل؛ 0 متجاهل؛ 0 مقيس؛ 8 تم تصفيتهم؛ انتهى في 0.00 ثانية
2. مشكلة دقة حسابات الأعداد الصحيحة في Rust
2.1 ترتيب العمليات
قد تؤثر تغييرات ترتيب الضرب والقسمة ذات أولوية الحساب نفسها بشكل مباشر على نتيجة الحساب، مما يؤدي إلى مشكلة دقة الحسابات الصحيحة.
صدأ #[test] الجبهة precision_test_div_before_mul() { دع أ: U128 = 1_0000 ؛ دع ب: U128 = 10_0000 ؛ دع C: U128 = 20 ؛
}
نتيجة التنفيذ:
تشغيل اختبار واحد الخيط 'tests::precision_test_0' انهار عند 'فشل التحقق: (left == right) اليسار: 2 ، اليمين: 0: '، src / lib.rs: 175: 9
يمكن ملاحظة أن result_0 = a * c / b و result_1 = (a / b) * c على الرغم من أن الصيغ الحسابية متشابهة، إلا أن النتائج مختلفة. السبب هو أن القسمة الصحيحة تتجاهل الدقة التي تقل عن المقام. عند حساب result_1، يؤدي حساب (a / b) أولاً إلى فقدان الدقة وتحويلها إلى 0؛ بينما عند حساب result_0، يتم حساب a * c = 20_0000 أولاً، وهو أكبر من المقام b، مما يتجنب فقدان الدقة.
2.2 حجم صغير جداً
صدأ #[test] الجبهة precision_test_decimals() { دع أ: u128 = 10 ؛ دع ب: u128 = 3 ؛ دع C: U128 = 4 ؛ دع الرقم العشري: U128 = 100_0000 ؛
}
نتيجة التنفيذ:
تشغيل اختبار واحد 12:13 الخيط 'tests::precision_test_decimals' انهار بسبب 'فشل التأكيد: (left == right) اليسار: 12 ، اليمين: 13: '، src / lib.rs: 214: 9
يمكن رؤية أن ناتجي result_0 و result_1 المتكافئين في عملية الحساب مختلفان، و result_1 = 13 أقرب إلى التوقع الفعلي 13.3333....
3. كيفية كتابة العقود الذكية في Rust لتقييم القيم
3.1 تعديل ترتيب عمليات الحساب
3.2 زيادة مرتبة الأعداد الصحيحة
3.3 فقدان دقة العمليات التراكمية
بالنسبة لمشكلة دقة الحسابات العددية الصحيحة التي لا يمكن تجنبها، يمكن النظر في تسجيل فقدان دقة العمليات التراكمية.
صدأ كونست USER_NUM: U128 = 3 ؛
الجبهة distribute(amount: U128 ، الإزاحة: u128) -> U128 { دع token_to_distribute = الإزاحة + المبلغ ؛ دع per_user_share = token_to_distribute / USER_NUM ؛ println!("per_user_share {}", per_user_share); دع recorded_offset = token_to_distribute - per_user_share * USER_NUM ؛ recorded_offset }
#[test] الجبهة الوطنية record_offset_test() { دع إزاحة MUT: U128 = 0 ؛ من أجل i في 1..7 { println!("Round {}", i); الإزاحة = distribute(10_000_000_000_000_000_000_000_000 ، offset) ؛ println!("إزاحة {}\n", offset); } }
نتيجة التنفيذ:
تشغيل اختبار واحد الجولة 1 per_user_share 3333333333333333333333333 الإزاحة 1
الجولة 2 per_user_share 3333333333333333333333333 الإزاحة 2
الجولة 3 per_user_share 4000000000000000000000000 الإزاحة 0
الجولة 4 per_user_share 3333333333333333333333333 الإزاحة 1
الجولة 5 per_user_share 3333333333333333333333333 تعويض 2
الجولة 6 per_user_share 4000000000000000000000000 إزاحة 0
اختبارات الاختبار::record_offset_test ... ok نتيجة الاختبار: جيدة. 1 ناجح; 0 فشل; 0 تم تجاهله; 0 تم قياسه; 9 تم تصفيته; انتهى في 0.00 ثانية
3.4 استخدام مكتبة Rust Crate rust-decimal
تتناسب هذه المكتبة مع الحسابات المالية العشرية التي تتطلب دقة حساب فعالة ولا تحتوي على أخطاء في التقريب.
3.5 اعتبر آلية التقريب
عند تصميم العقود الذكية، تُستخدم عادةً مشكلة التقريب وفقًا لمبدأ "أريد الاستفادة، ولا ينبغي للآخرين استغلالي". وفقًا لهذا المبدأ، إذا كان التقريب لأسفل مفيدًا لي، فسأقوم بالتقريب لأسفل؛ وإذا كان التقريب لأعلى مفيدًا لي، فسأقوم بالتقريب لأعلى؛ حيث أن التقريب لأقرب عدد لا يمكن تحديد من هو المستفيد منه، لذلك نادراً ما يتم استخدامه.
!
!