Pudu programming language
Menu
All documentation pages

Documentation

Guide

Numbers

Author
Chris M. Pérez Santiago
Version
0.1.1
Source
Edit this page on GitHub

Pudu has whole numbers of every common width, floating-point numbers, exact decimals, and whole numbers of any size. Each is its own type, and no number turns into another without the program saying so.

Choosing a type

TypeUse it forLiteral
Intcounting, indexing, most arithmetic42
Int8Int128, UInt8UInt128a value with a fixed size: a byte, a file offset, a protocol field255u8, -7i32, 9000000000i64
Float64, Float32measurements, where a tiny rounding error is acceptable3.14, 1.5f32
Decimalmoney, and anything else a person reads as a written number19.99d
BigIntwhole numbers that outgrow every fixed widtha whole literal given the type

A literal with no suffix is an Int or a Float64. A suffix gives it a width: u8 through u128 for unsigned integers, i8 through i128 for signed ones, f32 and f64 for floats, and d for a decimal. Underscores may separate digits: 1_000_000.

Arithmetic that cannot go wrong quietly

+, -, and * on a fixed-width integer are checked. A result that does not fit stops the program with a diagnostic naming the type, rather than wrapping around to a small or negative number:

error[E7005]: UInt8 cannot hold the result of this add
   = help: use the wrapping or saturating form, or a wider type; checked arithmetic never truncates quietly

When wrapping around or stopping at the limit is what the program means, it says so with an operator of its own:

module Arithmetic

fn main() -> Int {
  let level: UInt8 = 250u8
  let wrapped = level &+ 10u8
  let saturated = level +| 10u8
  let floor = 3u8 -| 5u8
  let hashed = 4000000000u32 &* 3u32
  if wrapped == 4u8 && saturated == 255u8 && floor == 0u8 && hashed == 3410065408u32 { 0 } else { 1 }
}
OperationCheckedWrappingSaturating
add+&++|
subtract-&--|
multiply*&**|

Division and remainder by nought have no answer at all, so Std.Math offers Math.divide and Math.remainder, which answer an Option.

Moving between widths

Two numbers of different types never meet in one operation: 1u8 + 1i32 is refused where it is written. Moving a value to another width goes through BigInt, which holds any whole number. Widening is always exact; narrowing answers an Option, because the value may not fit:

module Widths

import Std.Num {Integer}

fn main() -> Int {
  let reading = 300
  let wide = reading.toBigInt()
  let asByte = 0u8.fromBigInt(wide)
  let asShort = 0i16.fromBigInt(wide)
  if asByte == None && asShort == Some(300i16) { 0 } else { 1 }
}

The receiver of fromBigInt only names the type wanted — 0u8 asks for a UInt8 — so generic code can keep its caller's type.

Exact decimals

A Float64 stores a binary fraction, so 0.1 + 0.2 is not quite 0.3. A Decimal stores the digits that were written, so sums of prices come out exact. Std.Decimal rounds with a named rule, because every rule decides the halfway case differently:

module Prices

import Std.Decimal as D

fn main() -> Int {
  let items = [19.99d, 5.01d, 0.10d]
  let subtotal = D.sum(items)
  let tax = D.round(subtotal * 0.0825d, 2, D.HalfEven)
  let total = subtotal + tax
  let exact = 0.1d + 0.2d == 0.3d
  if exact && subtotal == 25.10d && tax == 2.07d && D.toText(total) == "27.17" { 0 } else { 1 }
}

Division is the one operation that may not terminate in base ten. D.divide(value, divisor, digits, rule) says how many digits to keep and how to round the last one, and answers None only for a divisor of nought.

Whole numbers of any size

A BigInt grows as it needs to. It is what a program reaches for when a factorial, a checksum, or a counter will not fit in 128 bits:

module Factorials

import Std.Io as Io

fn factorial(n: BigInt) -> BigInt {
  var product: BigInt = 1
  var step: BigInt = 2
  while step <= n {
    product = product * step
    step = step + 1
  }
  product
}

fn main() -> Int {
  let big = factorial(30)
  let _written = Io.writeLine("30! = {big}")
  if "{big}" == "265252859812191058636308480000000" { 0 } else { 1 }
}

Numeric helpers

ModuleProvides
Std.Mathmin, max, clamp, abs, pow, gcd, isPrime, and division that answers an Option
Std.Math.Floatsqrt, floor, round, trigonometry, logarithms, and the constants pi() and e()
Std.Decimalrounding rules, exact division, parsing, and formatting decimals
Std.Numthe traits generic numeric code asks for, and conversion through BigInt
Std.Bitsbitwise operations, shifts, and counting bits
Std.Randomseeded and clock-driven random numbers