Fixed-Point Math in Sui Move

Pisuth Daengthongdee
Legato
Published in
5 min readMay 1, 2024

--

If you’re building a project on Sui with the Move language, you might sometimes wonder how to handle fractions, starting from basic operations like 1.5+2.4 to more complex ones like exponentiation, such as 100^0.5

Unlike Solidity, where you can find many Math libraries that allow you to easily use and manipulate fractional numbers, it seems that there aren’t as many available for Sui’s Move language.

At Legato, we’re working on our new AMM with custom weights that require dealing with fractional numbers as in this formula:

here we borrowed from Balancer V2 Lite

Since we’ve made significant progress on this, we now want to share how we can handle fractions on Sui’s Move for those who need it especially for Sui Overflow hackathon participants.

What is Fixed-Point?

Most smart contract languages, such as Solidity or Move, do not natively support a float type. However, when used with a library, fixed-point arithmetic offers lower computational costs compared to floating-point arithmetic. Thus, many developers opt for it.

use std::fixed_point32;

Let’s take an example by converting the fractional number 134/24or 5.5 to fixed-point32 with 32 bits allocated for the integer part and 32 bits for the fractional part and then subtract it by 0.5

fixed_point32::create_from_rational(134,24); // represents 5.5
  • The numerator is left-shifted by 64 bits and the denominator by 32 bits, as seen in the source code using << 64 and << 32.
  • Now, calculating (132 × 2⁶⁴) ÷ (24 × 2³²) = 23622320128, which is the raw fixed-point value.
  • We subtracted 0.5 which is 2147483648 in raw value become 23622320128–2147483648 =21474836480.
  • Then we perform right shifting of 21474836480 >> 32 which divide it by 2³². The final result becomes 5.

Normally, you don’t need to deal with raw values directly. The raw value is wrapped by a struct, and the fixed-point library is supposed to provide functions for math operations.

However, Sui still lacks these functionalities. We will show you the solution in the next section.

Aptos Fixed-Point Math Library

Aptos is another Move-based L1 blockchain that has an extension library on fixed-point. We can easily import it into Sui’s project with minimal adjustments.

From the Aptos repo, we’ve listed all files relating to fixed-point arithmetic that you may copy and paste into your project as follows:

// https://github.com/aptos-labs/aptos-core
/aptos-move/framework/move-stdlib/sources/fixed_point32.move
/aptos-move/framework/aptos-stdlib/sources/fixed_point64.move
/aptos-move/framework/aptos-stdlib/sources/math128.move
/aptos-move/framework/aptos-stdlib/sources/math64.move
/aptos-move/framework/aptos-stdlib/sources/math_fixed.move
/aptos-move/framework/aptos-stdlib/sources/math_fixed64.move

You can put them all in your project in the exact folder like /math, and then you need to do two things:

  • Search for inline syntax and remove them all.
public inline fun gcd(a: u128, b: u128) // original Aptos source code

public fun gcd(a: u128, b: u128) // Sui's Move doesn't support inline
  • Rename Aptos’s module to your project’s module.
use std::fixed_point32; // original Aptos source code
use aptos_std::fixed_point64; // original Aptos source code

// into

use your_project::fixed_point32;
use your_project::fixed_point64;
  • In case you are using the latest version, spec has been deprecated. You may remove any references or implementations related to as well.

Then you can try the test command to see if it works or not.

sui move test

Basic Math Operations

Let’s start by trying basic math operations like add and subtract.

use your_project::fixed_point64;

// 2.34+1.5
let num_a = fixed_point64::create_from_rational(234, 100);
let num_b = fixed_point64::create_from_rational(15, 10);
let output = fixed_point64::add(num_a, num_b);

// 1000-77.3
let num_a = fixed_point64::create_from_u128(1000);
let num_b = fixed_point64::create_from_rational(773, 10);
let output = fixed_point64::sub(num_a, num_b);

You may notice that numbers are instantiated using create_from_rational, which requires providing the numerator and denominator to represent the desired value.

You can then compare fixed-point numbers using the equal, greater and less functions or convert the number back to an integer.

// a = b
fixed_point64::equal(num_a, num_b);

// a = ~b
fixed_point64::almost_equal( num_a, num_b, precision); // ~0.1

// a < b
fixed_point64::less(num_a, num_b);

// a > b
fixed_point64::greater(num_a, num_b);

// 1003.75 -> 1004
fixed_point64::round(num); // convert into u128

When writing unit tests, the almost_equal function may be suitable for verifying the value returned from your function.

For multiply and divide operations, you have 2 options depending on the desired returned value.

use your_project::fixed_point64;
use your_project::math_fixed64;

// When you want the output as u128
fixed_point64::multiply_u128(num_a, num_b); // num_a must be u128

// When you want the output as fixed-point
math_fixed64::mul_div(num_a, num_b, fixed_point64::create_from_u128(1));

Specialized Math Functions

Functions like square root, exponential and power (only with integers) are also covered by Aptos’s fixed-point math library specifically in a module math_fixed64.move

use your_project::fixed_point64;
use your_project::math_fixed64;

// sqrt(2) = 1.414213562
let output = math_fixed64::sqrt( fixed_point64::create_from_u128(2) );
// this is how you can assert
let ref_num = fixed_point64::create_from_rational( 1414, 1000); // 1.414
let precision = fixed_point64::create_from_rational( 1, 10); // 0.1
assert!( fixed_point64::almost_equal( output, ref_num, precision) , 0);

// exp(3) = 20.085536923
let output = math_fixed64::exp( fixed_point64::create_from_u128(3) );
let ref_num = fixed_point64::create_from_rational( 2008, 10); // 20.08
assert!( fixed_point64::almost_equal( output, ref_num, precision) , 0);

The logarithm base 2 is a bit tricky but also supported, you may need to subtract with a fixed number of 64 afterward.

// log2(1000) = 9.966
let num = fixed_point64::create_from_u128(1000);
let output = math_fixed64::log2_plus_64(num);
output = fixed_point64::sub( output, fixed_point64::create_from_u128(64));

let precision = fixed_point64::create_from_rational( 1, 10); // 0.1
let ref_num = fixed_point64::create_from_rational( 9966, 100); // 9.966
assert!( fixed_point64::almost_equal( output, ref_num, precision) , 0);

// 10.5^2 = 110.25
let num = fixed_point64::create_from_rational( 105, 10); // 10.5
let output = math_fixed64::pow( num, 2);

let ref_num = fixed_point64::create_from_rational( 11025, 100); // 110.25
assert!( fixed_point64::almost_equal( output, ref_num, precision) , 0);

The last function we showed is power, which has a limitation that it only allows the exponent to be an integer.

I believe we’ve covered most math operations in this article, which may help speed up development with fixed-point math on Sui.

Next article, we’ll explore advanced functions like exponentiation with fixed-point and the natural logarithm which require custom code.

Keep up with our channels for the latest article releases and product updates.

Website: https://legato.finance

Twitter/X: https://twitter.com/StakeLegato

--

--