Rust смартконтракти养成日记(7):цілісні обчислення точності проблеми
Огляд минулих подій:
Rust смартконтракти養成日記(1) статус даних контракту визначення та реалізація методів
Щоденник розвитку смартконтрактів Rust (2) Написання модульних тестів смартконтрактів Rust
Щоденник розвитку Rust смартконтрактів (3) Розгортання Rust смартконтрактів, виклик функцій та використання Explorer
Rust смартконтракти养成日记(4)Rust смартконтракти整数溢出
Rust смартконтракти养成日记(5)重入攻击
Rust смартконтракти виховання щоденник (6) відмова в обслуговуванні атака
1. Проблема точності обчислень з плаваючою комою
На відміну від Solidity, Rust має вбудовану підтримку операцій з плаваючою комою. Але операції з плаваючою комою мають невідворотні проблеми з точністю, тому не рекомендується використовувати їх у смартконтрактах, особливо при обробці важливих економічних/фінансових рішень, таких як ставки або відсотки.
Rust дотримується стандарту IEEE 754 для представлення чисел з плаваючою комою. Тип f64 з подвійною точністю представляється в комп'ютері у вигляді двійкового наукового запису.
Деякі дроби можуть бути точно представлені у вигляді обмеженої кількості двійкових розрядів, наприклад, 0.8125 може бути представлене як 0.1101. Але дроби, такі як 0.7, створюють безкінечне циклічне двійкове представлення, яке не може бути точно представлене обмеженою кількістю плаваючої коми, що призводить до проблеми "округлення".
У прикладі розподілу 0.7 токена NEAR 10 користувачам на блокчейні NEAR:
Результати виконання показують, що фактичне значення amount становить 0.69999999999999995559, result_0 становить 0.06999999999999999, що не дорівнює очікуваному 0.07.
Щоб вирішити цю проблему, можна використовувати фіксовану точку. У NEAR зазвичай 1 токен NEAR представляється 10^24 yoctoNEAR. Змінений код:
Таким чином, можна отримати точний результат: 0.7 NEAR / 10 = 0.07 NEAR.
2. Проблема точності обчислення цілих чисел у Rust
Хоча цілісні обчислення можуть вирішити проблеми з точністю чисел з плаваючою комою в деяких випадках, але цілісні обчислення також мають проблеми з точністю.
2.1 порядок виконання
Операції множення та ділення одного рівня, зміна порядку може вплинути на результат:
let result_0 = a.checked_mul(c).unwrap().checked_div(b). unwrap();
let result_1 = a.checked_div(b).unwrap().checked_mul(c). unwrap();
assert_eq!(result_0,result_1);
}
Результати показують result_0 = 2, result_1 = 0.
Причина в тому, що цілочисельне ділення відкидає точність, меншу за дільник. При обчисленні result_1, (a / b) спочатку втрачає точність і стає 0; тоді як result_0 спочатку обчислює (a * c), що уникнуло втрати точності.
#[test]
fn record_offset_test() {
нехай mut зміщення: u128 = 0;
для i в 1..7 {
зсув = distribute(10_000_000_000_000_000_000_000_000, offset);
}
}
!
3.4 Використання бібліотеки Rust Crate rust-decimal
Ця бібліотека підходить для фінансових обчислень з малими числами, які потребують високої точності та без округлювальних помилок.
3.5 Розглянути механізм округлення
У проектуванні смартконтрактів округлення, як правило, слідує принципу "на мою користь": якщо округлення вниз є вигідним, то округлюємо вниз, якщо округлення вгору є вигідним, то округлюємо вгору, рідко застосовують округлення до найближчого цілого.
Ця сторінка може містити контент третіх осіб, який надається виключно в інформаційних цілях (не в якості запевнень/гарантій) і не повинен розглядатися як схвалення його поглядів компанією Gate, а також як фінансова або професійна консультація. Див. Застереження для отримання детальної інформації.
17 лайків
Нагородити
17
8
Поділіться
Прокоментувати
0/400
LostBetweenChains
· 07-25 17:53
Цілісні обчислення не можуть бути вирішені, що за контракт.
Переглянути оригіналвідповісти на0
WagmiWarrior
· 07-25 11:45
Бачу сьому статтю, тонка, тонка.
Переглянути оригіналвідповісти на0
MetaMisfit
· 07-25 01:13
Rust ці смартконтракти ям так багато, правда?
Переглянути оригіналвідповісти на0
DuckFluff
· 07-22 22:44
Ого, знову з плаваючими числами трапилася якась справа~
Переглянути оригіналвідповісти на0
MaticHoleFiller
· 07-22 22:44
Ті, хто потрапив у пастку точності, приходять поділитися досвідом!
Переглянути оригіналвідповісти на0
MetadataExplorer
· 07-22 22:44
Ця частина часто ігнорується, але вона досить важлива.
Переглянути оригіналвідповісти на0
MEVHunter
· 07-22 22:29
точність є MEV honeypot... тримайте свої флоати щільно, або вас знищать, сер
Проблема точності обчислень цілих чисел у смартконтрактах Rust та рішення
Rust смартконтракти养成日记(7):цілісні обчислення точності проблеми
Огляд минулих подій:
1. Проблема точності обчислень з плаваючою комою
На відміну від Solidity, Rust має вбудовану підтримку операцій з плаваючою комою. Але операції з плаваючою комою мають невідворотні проблеми з точністю, тому не рекомендується використовувати їх у смартконтрактах, особливо при обробці важливих економічних/фінансових рішень, таких як ставки або відсотки.
Rust дотримується стандарту IEEE 754 для представлення чисел з плаваючою комою. Тип f64 з подвійною точністю представляється в комп'ютері у вигляді двійкового наукового запису.
Деякі дроби можуть бути точно представлені у вигляді обмеженої кількості двійкових розрядів, наприклад, 0.8125 може бути представлене як 0.1101. Але дроби, такі як 0.7, створюють безкінечне циклічне двійкове представлення, яке не може бути точно представлене обмеженою кількістю плаваючої коми, що призводить до проблеми "округлення".
У прикладі розподілу 0.7 токена NEAR 10 користувачам на блокчейні 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.69999999999999995559, result_0 становить 0.06999999999999999, що не дорівнює очікуваному 0.07.
Щоб вирішити цю проблему, можна використовувати фіксовану точку. У NEAR зазвичай 1 токен NEAR представляється 10^24 yoctoNEAR. Змінений код:
іржа
#[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;
}
Результати показують result_0 = 2, result_1 = 0.
Причина в тому, що цілочисельне ділення відкидає точність, меншу за дільник. При обчисленні result_1, (a / b) спочатку втрачає точність і стає 0; тоді як result_0 спочатку обчислює (a * c), що уникнуло втрати точності.
2.2 занадто малий масштаб
іржа #[test] fn precision_test_decimals() { Нехай А: U128 = 10; нехай b: u128 = 3; Нехай c: u128 = 4; Нехай десятковий дріб: u128 = 100_0000;
}
Результати показують, що result_0 = 12, result_1 = 13, останнє більш близьке до фактичного значення 13.3333.
!
3. Як написати Rust смартконтракти для числового актуарію
Для підвищення точності можна вжити такі заходи:
3.1 Зміна порядку виконання операцій
Нехай множення цілих чисел має пріоритет над діленням.
3.2 збільшити порядок цілого числа
Використовуйте більший порядок величини, щоб створити більші молекули. Наприклад, визначте 1 NEAR = 10^24 yoctoNEAR.
3.3 Втрата точності накопичувальних обчислень
Записувати та накопичувати втрати точності, компенсувати в подальших обчисленнях:
іржа const USER_NUM: u128 = 3;
FN distribute(amount: U128, зміщення: u128) -> u128 { Нехай token_to_distribute = зсув + сума; Нехай per_user_share = token_to_distribute / USER_NUM; нехай recorded_offset = token_to_distribute - per_user_share * USER_NUM; записаний_зсув }
#[test] fn record_offset_test() { нехай mut зміщення: u128 = 0; для i в 1..7 { зсув = distribute(10_000_000_000_000_000_000_000_000, offset); } }
!
3.4 Використання бібліотеки Rust Crate rust-decimal
Ця бібліотека підходить для фінансових обчислень з малими числами, які потребують високої точності та без округлювальних помилок.
3.5 Розглянути механізм округлення
У проектуванні смартконтрактів округлення, як правило, слідує принципу "на мою користь": якщо округлення вниз є вигідним, то округлюємо вниз, якщо округлення вгору є вигідним, то округлюємо вгору, рідко застосовують округлення до найближчого цілого.
!