Техніки та застереження щодо числових обчислень у смартконтрактах Rust

Rust смартконтракти养成日记(7):数值精算

Ця стаття розгляне проблеми числового актуарного обчислення в програмуванні смартконтрактів на Rust, зокрема, питання точності операцій з плаваючою комою, питання точності обчислень з цілими числами, а також як написати смартконтракти на Rust для числового актуарного обчислення тощо.

1. Проблема точності обчислень з плаваючою комою

Мова Rust нативно підтримує обчислення з плаваючою крапкою, але обчислення з плаваючою крапкою мають невідворотні проблеми з точністю. Під час написання смартконтрактів не рекомендується використовувати обчислення з плаваючою крапкою, особливо при обробці співвідношень або відсоткових ставок, що стосуються важливих економічних/фінансових рішень.

У мові Rust тип з подвійною точністю f64 відповідає стандарту IEEE 754 і використовує наукову нотацію з основою 2 для вираження. Однак деякі десяткові числа (, такі як 0.7), не можуть бути точно представлені за допомогою обмеженої кількості розрядів плаваючої коми, що призводить до явища "округлення".

Наприклад, розподілити 0,7 NEAR токенів між десятьма користувачами на публічному ланцюгу NEAR може призвести до неточних результатів.

іржа #[test] fn precision_test_float() { Нехай кількість: f64 = 0,7;
Нехай дільник: f64 = 10,0;
нехай result_0 = сума / дільник;
println!("Значення суми: {:.20}", amount); assert_eq!(result_0, 0,07, "); }

Результат виконання показує, що значення amount не є точним 0.7, а є надзвичайно близьким значенням 0.69999999999999995559. Подальший результат ділення також став неточним 0.06999999999999999, а не очікуваним 0.07.

Щоб вирішити цю проблему, можна розглянути використання фіксованих чисел. У NEAR Protocol зазвичай використовують 10^24 як знаменник, тобто 10^24 yoctoNEAR еквівалентно 1 токену NEAR.

Змінений тестовий код виглядає так:

іржа #[test] fn precision_test_integer() { нехай N: u128 = 1_000_000_000_000_000_000_000;
Нехай кількість: U128 = 700_000_000_000_000_000; Нехай дільник: u128 = 10;
нехай result_0 = сума / дільник; assert_eq!(result_0, 70_000_000_000_000_000_000, ""); }

Таким чином можна отримати точний результат обчислення: 0,7 NEAR / 10 = 0,07 NEAR.

!

2. Проблема точності обчислень цілих чисел у Rust

Хоча використання цілочисельних обчислень може вирішити проблему втрати точності з плаваючою комою в певних сценаріях, результати цілочисельних обчислень також не є повністю точними та надійними. Частина причин, які впливають на точність цілочисельних обчислень, включає:

2.1 порядок виконання операцій

Однаковий пріоритет арифметичних операцій множення і ділення, зміна їхнього порядку може безпосередньо вплинути на результат обчислення. Наприклад:

іржа #[test] fn precision_test_div_before_mul() { Нехай a: u128 = 1_0000; нехай b: u128 = 10_0000; Нехай С: 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,");

}

Результати тестування показали, що результат_0 і результат_1 мають різні результати обчислень. Це пов'язано з тим, що при цілочисельному діленні точність, що менша за дільник, буде втрачена. Під час обчислення результату_1, (a / b) спочатку втрачає точність обчислень і стає 0; під час обчислення результату_0 спочатку обчислюється a * c, що дозволяє уникнути втрати точності.

2.2 надто малий порядок величини

Коли йдеться про обчислення менших порядків величини, можуть виникати проблеми з точністю:

іржа #[test] fn precision_test_decimals() { Нехай А: U128 = 10; нехай b: u128 = 3; Нехай c: u128 = 4; Нехай десятковий дріб: 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, ");

}

Результати тестування показують, що результати обчислень result_0 та result_1 різні, і що result_1 = 13 ближче до фактичного очікуваного значення обчислення 13.3333....

!

3. Як написати смартконтракти Rust для числового актуаріїв

Для підвищення точності можна вжити такі заходи захисту:

3.1 Зміна порядку виконання операцій

Зробіть так, щоб множення цілих чисел було пріоритетним над діленням цілих чисел.

3.2 збільшити кількість порядків

Використовуйте більші масштаби, щоб створити більші молекули. Наприклад, 5.123 NEAR можна представити як 5.123 * 10^10 = 51_230_000_000.

3.3 Втрата точності накопичувальних обчислень

Для невідворотних проблем з точністю обчислень цілих чисел можна розглянути можливість фіксації накопичених втрат точності обчислень. Наприклад:

іржа const USER_NUM: u128 = 3;

FN 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; записаний_зсув }

#[test] fn record_offset_test() { нехай mut зміщення: u128 = 0; для i в 1..7 { println!("Round {}", i); зміщення = distribute(to_yocto("10"), offset); println!("Offset {}\n", offset); } }

Цей метод може накопичувати та перерозподіляти токени, які не були розподілені через втрату точності.

3.4 Використання бібліотеки Rust Crate rust-decimal

Ця бібліотека підходить для фінансових розрахунків з десятковими числами, які потребують точної обчислювальної точності та не мають помилок округлення.

3.5 Розгляньте механізм округлення

При проектуванні смартконтрактів, проблема округлення зазвичай слідує принципу "Я хочу отримати вигоду, інші не повинні мене обманювати". Відповідно до цього принципу, якщо округлення вниз вигідне для контракту, то округлюємо вниз; якщо округлення вгору вигідне для контракту, то округлюємо вгору; округлення до найближчого цілого рідко застосовується, оскільки не можна визначити, кому це вигідно.

Завдяки використанню цих методів можна реалізувати більш точні числові обчислення в Rust смартконтрактах, підвищуючи надійність і справедливість контракту.

!

Переглянути оригінал
Ця сторінка може містити контент третіх осіб, який надається виключно в інформаційних цілях (не в якості запевнень/гарантій) і не повинен розглядатися як схвалення його поглядів компанією Gate, а також як фінансова або професійна консультація. Див. Застереження для отримання детальної інформації.
  • Нагородити
  • 8
  • Поділіться
Прокоментувати
0/400
ZeroRushCaptainvip
· 07-17 09:17
А знову почали мучити цю річ. В минулий раз була проблема з точністю, і мене закрила позиція за контрактом. Втратив так, що доводиться їсти землю.
Переглянути оригіналвідповісти на0
BlockchainFriesvip
· 07-16 10:52
Ця плаваюча точка - це справжня пастка, хто використовує, той і нещасний.
Переглянути оригіналвідповісти на0
MoonBoi42vip
· 07-15 08:48
Яка точність плаваючої точки може зрівнятися з фіксованою~
Переглянути оригіналвідповісти на0
OnchainDetectivevip
· 07-14 09:54
Очевидно, що похибка точності є саме тією вразливістю, яку хакери використовують для атаки на деякі кросчейн мости, що викликає жах.
Переглянути оригіналвідповісти на0
HashRateHermitvip
· 07-14 09:54
Проблема з точністю дійсно мене підвела.
Переглянути оригіналвідповісти на0
GateUser-e87b21eevip
· 07-14 09:47
Знову проблема з плаваючою точкою, голова болить.
Переглянути оригіналвідповісти на0
BearMarketSurvivorvip
· 07-14 09:45
Тут ніхто не уникне від цієї пастки, ця особа розуміє.
Переглянути оригіналвідповісти на0
AirdropLickervip
· 07-14 09:35
Округлення дробових чисел – це занадто глибока яма, я вже туди потрапляв.
Переглянути оригіналвідповісти на0
  • Закріпити