Skip to main content

Fixed-Point Numbers and Functions

Fixed-point numbers

🚧 Status

Currently only the 64-bit wide Fix64 and UFix64 types are available. More fixed-point number types will be added in a future release.

Fixed-point numbers are useful for representing fractional values. They have a fixed number of digits after a decimal point.

They are essentially integers which are scaled by a factor. For example, the value 1.23 can be represented as 1230 with a scaling factor of 1/1000. The scaling factor is the same for all values of the same type and stays the same during calculations.

Fixed-point numbers in Cadence have a scaling factor with a power of 10, instead of a power of 2 (i.e., they are decimal, not binary).

Signed fixed-point number types have the prefix Fix, have the following factors, and can represent values in the following ranges:

  • Fix64: Factor 1/100,000,000; -92233720368.54775808 through 92233720368.54775807

Unsigned fixed-point number types have the prefix UFix, have the following factors, and can represent values in the following ranges:

  • UFix64: Factor 1/100,000,000; 0.0 through 184467440737.09551615

Fixed-point number functions

Fixed-Point numbers have multiple built-in functions you can use:


  • _10
    view fun toString(): String

    Returns the string representation of the fixed-point number.


    _10
    let fix = 1.23
    _10
    _10
    fix.toString() // is "1.23000000"


  • _10
    view fun toBigEndianBytes(): [UInt8]

    Returns the byte array representation ([UInt8]) in big-endian order of the fixed-point number.


    _10
    let fix = 1.23
    _10
    _10
    fix.toBigEndianBytes() // is `[0, 0, 0, 0, 7, 84, 212, 192]`

All fixed-point types support the following functions:


  • _10
    view fun T.fromString(_ input: String): T?

    Attempts to parse a fixed-point value from a base-10 encoded string, returning nil if the string is invalid.

    For a given fixed-point numeral n of type T, T.fromString(n.toString()) is equivalent to wrapping n up in an optional.

    Strings are invalid if:

    • they contain non-digit characters.
    • they don't fit in the target type.
    • they're missing a decimal or fractional component. For example, both 0. and .1 are invalid strings, but 0.1 is accepted.

    For signed types like Fix64, the string may optionally begin with a + or - sign prefix.

    For unsigned types like UFix64, sign prefices are not allowed.

    Examples:


    _11
    let nil1: UFix64? = UFix64.fromString("0.") // nil, fractional part is required
    _11
    _11
    let nil2: UFix64? = UFix64.fromString(".1") // nil, decimal part is required
    _11
    _11
    let smol: UFix64? = UFix64.fromString("0.1") // ok
    _11
    _11
    let smolString: String = "-0.1"
    _11
    _11
    let nil3: UFix64? = UFix64.fromString(smolString) // nil, unsigned types don't allow a sign prefix
    _11
    _11
    let smolFix64: Fix64? = Fix64.fromString(smolString) // ok


  • _10
    view fun T.fromBigEndianBytes(_ bytes: [UInt8]): T?

    Attempts to parse an integer value from a byte array representation ([UInt8]) in big-endian order, returning nil if the input bytes are invalid.

    For a given integer n of type T, T.fromBigEndianBytes(n.toBigEndianBytes()) is equivalent to wrapping n up in an optional.

    The bytes are invalid if:

    • the length of the bytes array exceeds the number of bytes needed for the target type.
    • they don't fit in the target type.

    Examples:


    _10
    let fortyTwo: UFix64? = UFix64.fromBigEndianBytes([0, 0, 0, 0, 250, 86, 234, 0]) // ok, 42.0
    _10
    _10
    let nilWord: UFix64? = UFix64.fromBigEndianBytes("[100, 22, 0, 0, 0, 0, 0, 0, 0]") // nil, out of bounds
    _10
    _10
    let nilWord2: Fix64? = Fix64.fromBigEndianBytes("[0, 22, 0, 0, 0, 0, 0, 0, 0]") // // nil, size (9) exceeds number of bytes needed for Fix64 (8)
    _10
    _10
    let negativeNumber: Fix64? = Fix64.fromBigEndianBytes([255, 255, 255, 255, 250, 10, 31, 0]) // ok, -1

Number type casting

Casting between number types (e.g. Int to UInt, Fix64 to Int) using the casting operators (as, as? and as!) is not supported.

To convert between number types, the conversion functions ((e.g. UInt(_))) must be used. These conversion functions have the same name as the desired type.


_10
let value: UInt8 = 1
_10
_10
let intValue: Int? = value as? Int
_10
// intValue is `nil` and has type `Int?`
_10
_10
let validInt: Int = Int(value)
_10
// validInt is `1` and has type `Int`

When converting from a larger number type to a smaller one (narrowing), the conversion will succeed if the value can be represented in the smaller type. If it cannot an error will be thrown indicating overflow or underflow. Converting to a larger number type will always succeed.


_10
let intValue: Int16 = 256
_10
_10
let uintValue: UInt8 = UInt8(intValue)
_10
// error: overflow, UInt8 has max value of `255`
_10
_10
let validUInt: UInt16 = UInt16(intValue)
_10
// validUInt is `256` and has type `UInt16`
_10
_10
let largerIntValue: Int = Int(intValue)
_10
// largerIntValue is `256` and has type `Int`

Converting from integer types to fixed point types and vice versa is supported by calling the conversion functions as well. The same conditions as narrowing applies, an error will be thrown if the value cannot be represented in the range.


_10
let intValue: Int = -1
_10
_10
let fixValue: Fix64 = Fix64(intValue)
_10
// fixValue is `-1.00000000` and has type `Fix64`
_10
_10
let ufixValue: UFix64 = UFix64(intValue)
_10
// error: underflow, UFix64 has min value `0.0`