Skip to main content

Floating Point

IEEE 754 double-precision floating point with Float. Use sparingly in verified code because the verifier treats Float as an uninterpreted type.

Overview

Float represents 64-bit IEEE 754 double-precision floating-point numbers. At runtime, Float supports the full range of arithmetic operations, comparisons, and mathematical functions. However, for verification purposes, Float is modeled as a backend Float sort with opaque operations. Equality and expression identity are useful, but Sector9 does not provide IEEE arithmetic, rounding, NaN, infinity, or ordering axioms to the prover.

float-basic.sr9
// Basic Float operations - IEEE 754 double-precision floating point

module {
// Float literals in decimal notation
public func constants() : (Float, Float, Float) {
let pi = 3.141592653589793;
let small = 0.001;
let scientific = 1.5e-10; // Scientific notation
(pi, small, scientific)
};

// Basic arithmetic operations
public func arithmetic(a : Float, b : Float) : Float {
let sum = a + b;
let diff = a - b;
let prod = a * b;
sum
};

// Comparison operations
public func compare(x : Float, y : Float) : Bool {
x < y or x == y or x > y
};
}

Literal Syntax

Float literals use decimal notation with optional exponential form:

let a = 3.14;              // Decimal with fractional part
let b = 1.0; // Must include decimal point
let c = 1e-10; // Scientific notation (1 × 10⁻¹⁰)
let d = 2.5e3; // 2500.0
let e = -0.5; // Negative literal

Float literals require a decimal point or exponent to distinguish them from integers.

Runtime Operations

At runtime, Float supports comprehensive arithmetic and mathematical operations:

Arithmetic

OperationSyntaxDescription
Additiona + bSum
Subtractiona - bDifference
Multiplicationa * bProduct
Divisiona / bQuotient
Moduloa % bRemainder
Powera ** bExponentiation
Negation-aSign flip

Comparisons

OperationSyntax
Equala == b
Not equala != b
Less thana < b
Greater thana > b
Less or equala <= b
Greater or equala >= b

Standard Library Functions

The core library provides a Float module. Prefer this over Prim:

import Float "mo:core/Float";

Float wraps the same runtime operations as Prim, but keeps user code off the low-level primitives:

Float.abs(x)        // Absolute value
Float.sqrt(x) // Square root
Float.ceil(x) // Round up
Float.floor(x) // Round down
Float.trunc(x) // Truncate toward zero
Float.nearest(x) // Round to nearest
Float.min(x, y) // Minimum
Float.max(x, y) // Maximum

Float.sin(x) // Sine
Float.cos(x) // Cosine
Float.tan(x) // Tangent
Float.arcsin(x) // Arc sine
Float.arccos(x) // Arc cosine
Float.arctan(x) // Arc tangent

Float.exp(x) // e^x
Float.log(x) // Natural logarithm

Float.toInt(x) // Float to Int (truncates)
Float.fromInt(n) // Int to Float

The Prim module provides the low-level equivalents:

Prim.floatAbs(x)      // Absolute value
Prim.floatSqrt(x) // Square root
Prim.floatCeil(x) // Round up
Prim.floatFloor(x) // Round down
Prim.floatTrunc(x) // Truncate toward zero
Prim.floatNearest(x) // Round to nearest
Prim.floatMin(x, y) // Minimum
Prim.floatMax(x, y) // Maximum

// Trigonometric
Prim.sin(x) // Sine
Prim.cos(x) // Cosine
Prim.tan(x) // Tangent
Prim.arcsin(x) // Arc sine
Prim.arccos(x) // Arc cosine
Prim.arctan(x) // Arc tangent

// Exponential/Logarithmic
Prim.exp(x) // e^x
Prim.log(x) // Natural logarithm

// Conversions
Prim.floatToInt(x) // Float to Int (truncates)
Prim.intToFloat(n) // Int to Float

Verification Model

In Sector9's verification, Float operations are deliberately opaque. This means:

  • Equality is verified: The verifier reasons about == and != on floats
  • Expression identity is verified: returning a + b can satisfy ensures result == a + b, because both sides are the same symbolic expression
  • Arithmetic facts are not verified: operations like +, -, *, / do not give the prover algebraic facts such as monotonicity, identities, associativity, or bounds
  • Ordering is opaque: comparisons like <, <=, >, >= are modeled but have no IEEE ordering theory
  • No IEEE semantics: Special values (NaN, Infinity), rounding, and precision are not modeled

What Works in Verification

Contracts involving Float equality verify correctly:

float-equality.sr9
// Float equality in verification - the only verified operation

module {
// Equality comparison is verified
public func compareFloat(a : Float, b : Float) : Bool
ensures result == (a == b);
{
a == b
};

// Reflexivity holds for all floats
public func floatSelfEqual(f : Float) : Bool
ensures result == true;
{
f == f
};

// Identity preserves value
public func identityFloat(f : Float) : Float
ensures result == f;
{
f
};

// Conditional selection works with floats
public func selectFloat(cond : Bool, a : Float, b : Float) : Float
ensures cond ==> result == a;
ensures (not cond) ==> result == b;
{
if (cond) { a } else { b }
};
}

What Does Not Work

The verifier cannot prove meaningful properties about Float arithmetic:

public func identity(f : Float) : Float
ensures result == f; // Equality: verified
{ f }

The arithmetic example fails verification because a + b produces an opaque value; Sector9 cannot prove it equals a for all b. For verified arithmetic properties, prefer Nat, Int, fixed-width integers, or fixed-point encodings over integers.

Why Uninterpreted?

Modeling IEEE 754 semantics precisely is complex:

  • Special values: NaN, positive/negative infinity, positive/negative zero
  • Rounding modes: Nearest, toward zero, toward positive/negative infinity
  • Precision limits: Operations may lose precision
  • Non-associativity: (a + b) + c may not equal a + (b + c)

Rather than model these incompletely or unsoundly, Sector9 takes the conservative approach of treating Float as uninterpreted. This means:

  • Verified properties are sound (never falsely claim correctness)
  • You cannot verify IEEE arithmetic properties on floats
  • Use integer arithmetic when you need verified numeric reasoning

When to Use Float

Use Float when:

  • You need floating-point math at runtime but not in contracts
  • Contracts only check Float equality or identity
  • You're interfacing with external systems expecting IEEE 754

Avoid Float when:

  • You need verified arithmetic properties
  • Precision and rounding matter for correctness
  • You can use fixed-point or integer arithmetic instead

Alternative Approaches

When you need verified numeric properties, consider these alternatives:

Fixed-Point with Integers

Use integers with an implicit decimal point:

// Represent dollars as cents (2 decimal places)
var balance : Nat = 10050; // $100.50

public func addCents(amount : Nat) : async Nat
modifies balance
ensures balance == old(balance) + amount
{
balance += amount;
balance
}

Bounded Rational Approximations

For ratios, use pairs of integers:

// Represent ratio as numerator/denominator
public func percentage(part : Nat, whole : Nat) : async Nat
entry_requires whole > 0;
requires whole > 0;
ensures result == (part * 100) / whole;
{
(part * 100) / whole
}

Float in Spec Collections

Float values can be stored in spec collections (Map, Set, Seq, Multiset) because they are immutable primitives. The verifier can reason about collection membership and equality:

module {
public ghost func remember(seen : Set<Float>, f : Float) : Set<Float>
ensures Set.contains(f, result);
{
Set.union(seen, Set.singleton(f))
};
}

Common Patterns

Runtime Float with Verified Guards

Use entry_requires for equality-style Float guards on exported methods:

public func safeDivide(a : Float, b : Float) : async Float
entry_requires b != 0.0;
requires b != 0.0;
{
a / b
}

The verifier can use the equality guard, but it still does not prove IEEE arithmetic facts about the division result.

Float as Opaque Data

When Float is just data passing through, verification works:

var stored : Float = 0.0;

public func setFloat(f : Float) : async ()
modifies stored
ensures stored == f
{
stored := f;
}

public func getFloat() : async Float
reads stored
ensures result == stored
{
stored
}

Summary

  • Float is IEEE 754 double-precision floating point
  • Literals use decimal notation: 3.14, 1e-10, -0.5
  • Runtime supports full arithmetic, comparisons, and math functions
  • Verification treats Float as uninterpreted - only equality is reasoned about
  • Arithmetic contracts on Float values will not verify
  • Use integers or fixed-point arithmetic when you need verified numeric properties
  • Float can be used in spec collections and passed as opaque data