Как обойти ошибку целочисленного деления в смарт-контрактах

Как обойти ошибку целочисленного деления в смарт-контрактах

20 февраля 2023 г.

Мы все слышали о числах с плавающей запятой в школе. В отличие от целых чисел без десятичной точки.

Не нужно быть гением математики, чтобы знать, что число пять является целым числом, а 5,43 — числом с плавающей запятой. Ясно, что мы все знакомы с десятичной точкой, которая является четкой точкой различия между ними.

Конечно, неудивительно, что числа с плавающей запятой используются в языках программирования и даже классифицируются как отдельный примитивный тип данных из-за того, насколько они необходимы для повседневных вычислений.

Использование чисел с плавающей запятой в языках программирования

Когда вы начнете изучать Python или Java, вы будете использовать тип данных float. В частности, когда вы сами выполняете операцию деления между двумя числами с плавающей запятой.

В Python, даже если вам не нужно объявлять число с плавающей запятой, деление двух таких чисел может привести к числу одного и того же типа данных, как показано ниже:

Python program to divide two floating point numbers

Мы можем получить тот же результат и в Java, даже если нам придется явно объявить примитивный тип данных «плавающий» для всех используемых переменных.

Java program to divide two floating point numbers

Как можно заметить, такого рода вычисления необходимы для ряда приложений, и именно поэтому операции с числами с плавающей запятой доступны как функция в обоих этих языках программирования.

С другой стороны, если вы изучали Solidity, вы должны были заметить, что он не включает тип данных числа с плавающей запятой.

Как Solidity выполняет транзакции без использования чисел с плавающей запятой

Вместо этого, если вы написали какой-либо код в Solidity, вы должны использовать числа либо со знаком, либо без знака целочисленного типа.

Итак, если вы хотите выполнить те же вычисления — пять разделить на два — как показано в предыдущем разделе, вот как это будет выглядеть в рамках смарт-контракта:

Integer Division Error - Smart Contract in Solidity

Однако результат, который вы получите, будет другим, так как Solidity не поддерживает тип данных float. По крайней мере, пока.

getResult function

В частности, Solidity округлит результат до нуля. Что в данном случае, как показано выше, приведет к значению два. Думайте об этом как об операции по модулю над обоими числами.

Хотя в обычных обстоятельствах это не должно иметь большого значения, бывают случаи, когда результат может привести к ошибке в вычислениях. Вот почему рекомендуется по возможности избегать или откладывать операцию деления.

Ваш первый взгляд на ошибку целочисленного деления

Даже если школьное правило BODMAS требует, чтобы все учащиеся-математики выполняли вычисления деления перед умножением, это не рекомендуется, если вам нужно выполнить целочисленное деление в Solidity.

Давайте выясним, почему на этом простом примере выполняется умножение и деление трех чисел:

Multiply and divide Three Numbers - Smart Contract in Solidity

Если вы введете числа один, три и пять при развертывании смарт-контракта, вы должны получить одинаковое значение для функций getResult и getResult2, верно?

Если вы используете простой калькулятор, вы должны получить значение с плавающей запятой 1,666, что соответствует единице в Solidity, благодаря отсутствию значений с плавающей запятой.

К сожалению, этого не происходит, когда вы проверяете результаты функций getResult и getResult2, как показано ниже:

getResult & getResult2 results

Если мы сначала выполним деление, мы получим окончательный результат, равный нулю. В отличие от ожидаемого значения единицы, когда вы откладываете эту операцию до конца в функции getResult.

Как вы можете заметить, даже если мы вычислили это значение, предвидя отсутствие значений с плавающей запятой, все равно возникает ошибка, которая может привести к потере точности. Что, в свою очередь, может привести к экономическим потерям, которые можно легко обойти.

Итак, как нам предотвратить ошибку целочисленного деления? Что еще более важно, как мы можем повысить точность наших вычислений? Давайте узнаем три наиболее распространенных способа сделать это.

3 способа предотвратить ошибку целочисленного деления

Учитывая, что существует несколько подходов к обходу этой ошибки, давайте начнем с самого простого исправления и рассмотрим еще несколько, прежде чем на этом закончить.

Способ 1. Используйте множитель

Теперь, наряду с размещением операции деления на последнем месте, одним из способов гарантировать, что вы не получите ошибок или неточных значений, является использование множителя. В приведенном ниже примере мы будем использовать множитель 100 вместе с теми же тремя числами, которые использовались ранее.

Using a multiplier - Smart Contract in Solidity

Теперь, когда вы развернете контракт со следующим кодом и вызовете обе функции, вы получите следующий результат:

Using a multiplier - getResult and getResult2 values

Поскольку желаемый результат — 1,666 или 166/100, мы видим, что значение getResult2 обеспечивает необходимую точность, когда множитель работает в сочетании с тремя числами. Конечно, если вы не используете множитель, как в функции getResult, вы получите 1.

Где 0,666 усекается от результата, как и ожидается по умолчанию при использовании Solidity. Итак, чтобы получить это значение, все, что вам нужно сделать, это разделить результат на множитель.

Как вы, возможно, знаете, Solidity приближается к нулю, когда дело доходит до округления как в случае целых чисел со знаком, так и в случае целых чисел без знака, поэтому это исправление работает и в случае целых чисел со знаком, как только вы развернете и запустите приведенный ниже код с аргументами минус один. , три и пять:

Using a multiplier for signed integers - Smart Contract in Solidity

Что касается значений, сгенерированных функциями для целых чисел со знаком, то вот они:

Using a multiplier for signed integers - Smart Contract in Solidity

Ясно, что мы можем поддерживать точность и для целых чисел со знаком, используя множитель. Хотя существует точный способ округления целых чисел со знаком, который мы рассмотрим далее.

Способ № 2. Используйте деление пола для целых чисел со знаком

Теперь, если посмотреть на числовую строку ниже, результат выполнения целочисленного деления между двумя целыми числами без знака округляется ближе к нулю. Как и в случае получения 1,666, Solidity округляет его до 1, что является меньшим числом.

The number line

Однако, когда речь идет о целых числах со знаком, результат -1,6666 будет округлен до -1, который является большим из двух чисел. Таким образом, здесь должно применяться деление по полу, а не деление с округлением до нуля, которое реализовано в Solidity по умолчанию. Ради точности, конечно.

Performing floor division for signed integers

Если бы был доступен тип данных с плавающей запятой, было бы вычислено значение -1,666. В то время как Solidity округляет это значение до -1, применение деления пола к целым числам со знаком уменьшит его до -2.

Когда вы вызываете функции getResult и getResult2, мы получаем значение, как показано ниже, для аргументов минус один, три & пять:

getResult2 gives the right floor division results

Как вы можете заметить, getResult вычисляет значение, используя метод округления до нуля, в то время как getResult2 округляет его до наименьшего целого числа в силу деления пола.

Способ 3. Используйте библиотеку ABDKMath64x64

Теперь, в качестве последнего метода, мы будем использовать библиотеку ABDKMath64x64, которая преобразует результат операций деления в числа с фиксированной точкой.

Опять же, говорят, что использование этой библиотеки повышает точность по сравнению с методом округления до нуля, который доступен по умолчанию в Solidity. Чтобы понять вывод, давайте сравним результаты добавления с функцией div, как показано ниже:

Using the ABDK library

Когда вы добавляете аргументы 1, 1 и 1 при развертывании смарт-контракта, вы получаете значения, как показано ниже:

Using the ABDK library - Output

Неудивительно, что функция add возвращает целочисленное значение, равное двум, с тремя аргументами в сумме. Что касается функции div, возвращается значение int 128, представляющее 64,64-битное число с фиксированной запятой со знаком, с которым можно выполнять обычные операции с числами.

Другие библиотеки целочисленного деления, которые стоит рассмотреть

Конечно, библиотека ABDKMath64x64 — не единственная, которую можно использовать для повышения точности, помимо предотвращения ошибки целочисленного деления.

Есть несколько других примеров, например Fixidity, DSMath и библиотеки BANKEX, использующие разные числовые форматы. Форматы чисел, которые отличаются от 64,64-битного числового формата с фиксированной запятой, используемого в приведенном выше примере. Таким образом, хотя эти библиотеки могут показаться полезными для изучения, помните, что их числовые форматы не будут работать ни с какими другими доступными библиотеками.


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE