Back to flin
flin

Math, Statistics, and Geometry Functions

How FLIN ships a complete mathematics library -- 100+ functions covering arithmetic, trigonometry, statistics, and geometry -- built into the language runtime with zero imports.

Thales & Claude | March 25, 2026 12 min flin
flinmathstatisticsgeometry

When we audited what web developers actually compute, the results surprised us. It was not just addition and multiplication. Dashboards needed standard deviation and percentile calculations. Map integrations needed distance formulas and coordinate transforms. Financial applications needed precise rounding and currency arithmetic. Data visualizations needed trigonometry for chart rendering.

Across Sessions 122 through 128, we built FLIN's complete mathematics library: over 100 functions spanning basic arithmetic, advanced math, statistics, and geometry. All built-in. All zero-import. All handling edge cases that most third-party libraries ignore.

Basic Math: The Foundation Everyone Assumes Exists

Every language has basic math. FLIN's basic math functions are not remarkable in themselves -- what is remarkable is how they handle edge cases that other languages punt to the developer.

abs(-42)                   // 42
min(3, 7)                  // 3
max(3, 7)                  // 7
clamp(15, 0, 10)           // 10 (constrained to range)
clamp(-5, 0, 10)           // 0

clamp is the function that every developer writes and gets wrong. The correct behavior: if the value is below the minimum, return the minimum. If above the maximum, return the maximum. Otherwise, return the value. What happens if min > max? In most implementations, you get undefined behavior. In FLIN, clamp(5, 10, 0) returns 5 -- the function silently swaps the bounds. This is a deliberate design choice. A function that crashes because the developer passed arguments in the wrong order is a function that hates its users.

Rounding

floor(3.7)                 // 3
ceil(3.2)                  // 4
round(3.5)                 // 4
round(3.14159, 2)          // 3.14 (to 2 decimal places)
trunc(3.7)                 // 3 (toward zero)
trunc(-3.7)                // -3 (toward zero, not -4)

The distinction between floor, trunc, and round trips up every developer at some point. floor rounds toward negative infinity: floor(-3.2) is -4. trunc rounds toward zero: trunc(-3.2) is -3. round rounds to the nearest integer using banker's rounding (round half to even) to avoid statistical bias. round(2.5) is 2, not 3. round(3.5) is 4.

The two-argument form of round -- round(3.14159, 2) -- rounds to a specific number of decimal places. This is the function that every financial application needs and that JavaScript does not provide. In JavaScript, rounding to two decimal places requires the notorious Math.round(x * 100) / 100 pattern, which fails for certain floating-point values. In FLIN, round(value, 2) is correct by construction.

Arithmetic

pow(2, 10)                 // 1024
sqrt(16)                   // 4.0
cbrt(27)                   // 3.0
log(100)                   // Natural log (4.605...)
log10(100)                 // 2.0
log2(8)                    // 3.0
exp(1)                     // e^1 (2.718...)

These are thin wrappers around Rust's f64 math functions, which in turn call the platform's libm implementation. They are as fast as native C math. The only addition FLIN makes is none-safety: sqrt(none) returns none, not a crash.

Random Numbers

random()                   // 0.0 to 1.0 (exclusive)
random_int(1, 100)         // Integer in [1, 100]
random_choice(items)       // Random element from list
random_shuffle(list)       // Shuffled copy (Fisher-Yates)
uuid()                     // Random UUID v4 string

The random number generator uses a cryptographically secure source (Rust's rand crate with OsRng). This matters for uuid() and for any application that uses random values for security purposes (token generation, password reset codes). Most languages use a weaker PRNG by default and require explicit opt-in for cryptographic randomness. FLIN uses the strong source everywhere because the performance difference is negligible for the volume of random numbers a web application generates.

Number Properties

Numbers in FLIN have property-style methods that read like English:

n = -42
n.is_positive              // false
n.is_negative              // true
n.is_zero                  // false
n.is_even                  // true
n.is_odd                   // false
n.is_integer               // true
n.sign                     // -1

pi = 3.14159 pi.is_integer // false pi.sign // 1 ```

These properties eliminate a surprising number of if statements. Instead of if (n > 0), you write if n.is_positive. Instead of if (n % 2 == 0), you write if n.is_even. The intent is clearer, and the code reads like a specification rather than an implementation.

Number Formatting

n = 1234567.89
n.format()                 // "1,234,567.89" (locale-aware)
n.format(2)                // "1234567.89" (2 decimal places)
n.to_fixed(2)              // "1234567.89" (always 2 decimals)
n.to_percent               // "123456789%" (multiply by 100, add %)
n.to_hex                   // "12d687" (hexadecimal)
n.to_binary                // "100101011010000110000111" (binary)

price = 0.15 price.to_percent // "15%" ```

format() is locale-aware. In French locale, 1234.56.format() produces "1 234,56" (space as thousands separator, comma as decimal separator). In English locale, it produces "1,234.56". The locale is determined by the application's configuration, not by the server's system locale. This matters for FLIN's target audience -- African developers building applications for users who speak French, English, Arabic, Portuguese, and dozens of local languages.

Statistics: Beyond the Average

When Thales described the analytics dashboards he wanted for Deblo.ai -- showing student performance distributions, identifying outliers, calculating confidence intervals -- it became clear that basic sum and average were not enough. FLIN needed real statistics.

scores = [85, 92, 78, 95, 88, 72, 91, 86, 79, 94]

scores.sum // 860 scores.average // 86.0 scores.min // 72 scores.max // 95

// These are the functions that matter for real analytics: scores.median // 87.0 scores.mode // none (no repeated values) scores.std_dev // 7.46 (standard deviation) scores.variance // 55.6 scores.percentile(90) // 94.1 (90th percentile) scores.range // 23 (max - min) ```

median sorts the list and returns the middle value (or the average of the two middle values for even-length lists). std_dev computes the population standard deviation. variance is the square of the standard deviation. percentile(p) uses linear interpolation between data points, matching the behavior of Excel's PERCENTILE.INC function.

These functions operate on lists of numbers. If the list contains none values, they are silently skipped. If the list is empty, they return none. This graceful handling of missing data is critical for real-world analytics where data is always incomplete.

// Real-world data with gaps
temperatures = [22.1, none, 23.4, 21.8, none, 24.2, 22.9]
temperatures.average       // 22.88 (none values skipped)
temperatures.count         // 7 (total elements)
temperatures.count(t => t != none)  // 5 (non-none elements)

Correlation and Regression

For more advanced analytics, FLIN includes correlation and simple linear regression:

hours_studied = [2, 3, 5, 7, 8, 10]
exam_scores = [65, 70, 80, 85, 90, 95]

correlation(hours_studied, exam_scores) // 0.99 (strong positive)

// Simple linear regression model = linear_regression(hours_studied, exam_scores) model.slope // 3.57 model.intercept // 58.57 model.r_squared // 0.98

// Predict predicted = model.predict(6) // 80.0 ```

Is linear regression a "basic" function? For a language targeting educational platforms -- where teachers want to show the relationship between study time and grades -- it absolutely is. For a language targeting financial applications -- where analysts plot revenue against marketing spend -- it absolutely is. The function is 40 lines of Rust. It adds negligible size to the binary. And it saves every developer from writing their own (or finding, evaluating, and installing a statistics library).

Trigonometry

Trigonometry functions exist because charts, animations, and map calculations all need them:

sin(PI / 2)                // 1.0
cos(0)                     // 1.0
tan(PI / 4)                // 1.0 (approximately)
asin(1.0)                  // PI / 2
acos(0.0)                  // PI / 2
atan(1.0)                  // PI / 4
atan2(1.0, 1.0)            // PI / 4

// Converting between degrees and radians to_radians(180) // PI to_degrees(PI) // 180.0 ```

The constants PI and E are available without any import:

PI                         // 3.14159265358979
E                          // 2.71828182845904
INFINITY                   // Positive infinity
NEG_INFINITY               // Negative infinity

These are not variables. They are true constants, resolved at compile time. You cannot reassign PI. The compiler rejects PI = 3 with a clear error message.

Geometry: The Surprising Necessity

Geometry functions were the most debated addition. "Does a web programming language really need distance and area_circle?" The answer came from three use cases that appeared in every modern application we analyzed.

Maps and location. Every application with a map -- ride sharing, delivery, store locators -- needs distance calculations between coordinates:

// Haversine distance between two GPS coordinates
abidjan = { lat: 5.3600, lon: -4.0083 }
paris = { lat: 48.8566, lon: 2.3522 }

km = haversine_distance( abidjan.lat, abidjan.lon, paris.lat, paris.lon ) // 4,868 km ```

Chart rendering. Pie charts need angles. Radar charts need polar coordinates. Scatter plots need point distances:

// Convert polar to Cartesian for chart rendering
angle = 45
radius = 100
point = polar_to_cartesian(angle, radius)
// { x: 70.71, y: 70.71 }

// Rotate a point around a center rotated = rotate_point( { x: 100, y: 0 }, // point { x: 0, y: 0 }, // center 90 // degrees ) // { x: 0, y: 100 } ```

UI calculations. Tooltip positioning, collision detection for drag-and-drop, responsive layout calculations:

// Check if a point is inside a rectangle
inside = point_in_rect(
    { x: 50, y: 50 },           // point
    { x: 0, y: 0, w: 100, h: 100 }  // rectangle
)
// true

// Distance between two points d = distance( { x: 0, y: 0 }, { x: 3, y: 4 } ) // 5.0 (Pythagorean theorem) ```

Area and Perimeter Functions

area_circle(10)            // 314.159... (radius 10)
area_rectangle(5, 10)      // 50
area_triangle(3, 4, 5)     // 6.0 (Heron's formula)

perimeter_circle(10) // 62.832... (circumference) perimeter_rectangle(5, 10) // 30 ```

These are simple functions -- most are one-liners in Rust. But having them built-in means a developer building a geometry visualization for a math education app (like Deblo.ai) does not need to derive Heron's formula from scratch or search for a geometry library.

Implementation Details

The math functions are implemented in two layers. Basic functions (abs, min, max, floor, ceil, round) are dedicated VM opcodes for maximum performance. Advanced functions (std_dev, percentile, haversine_distance) are implemented as native functions registered in the VM's function table.

// Basic math: dedicated opcodes (fastest path)
Op::Abs => {
    let val = self.pop_number()?;
    self.push(Value::Float(val.abs()));
}

// Advanced math: native function (still fast, slightly more overhead) fn builtin_std_dev(vm: &mut Vm, args: &[Value]) -> Result { let list = vm.get_list(args[0])?; let numbers: Vec = list.iter() .filter_map(|v| v.as_number()) .collect();

if numbers.is_empty() { return Ok(Value::None); }

let mean = numbers.iter().sum::() / numbers.len() as f64; let variance = numbers.iter() .map(|x| (x - mean).powi(2)) .sum::() / numbers.len() as f64;

Ok(Value::Float(variance.sqrt())) } ```

The native function path has slightly more overhead than a dedicated opcode (one hash table lookup to find the function, plus the function call itself), but for functions that operate on entire lists, the overhead is negligible compared to the computation itself. Computing the standard deviation of 1,000 numbers takes microseconds. The function lookup takes nanoseconds.

Constants and Precision

FLIN uses 64-bit floating-point numbers (IEEE 754 f64) for all numeric operations. This gives 15-17 significant decimal digits of precision -- sufficient for every web application use case.

For financial calculations where exact decimal arithmetic matters, FLIN provides rounding functions that produce exact results:

// Currency arithmetic
price = 19.99
tax_rate = 0.075
tax = round(price * tax_rate, 2)    // 1.50 (exact)
total = round(price + tax, 2)       // 21.49 (exact)

The round(value, places) function uses the "round half to even" strategy (also called banker's rounding) to avoid the systematic bias that "round half up" introduces. This is the same rounding strategy used by Python's round(), IEEE 754, and financial software standards. round(2.5, 0) is 2, not 3. round(3.5, 0) is 4. Over thousands of transactions, this eliminates the upward bias that "round half up" creates.

What We Deliberately Left Out

Designing a standard library is as much about what you exclude as what you include. We deliberately left out:

Matrix operations. Matrix multiplication, inversion, and decomposition are essential for machine learning and 3D graphics. They are not essential for web applications. A developer who needs matrices is better served by a dedicated linear algebra library than by built-in functions.

Symbolic math. Equation solving, differentiation, and integration are academic mathematics functions. They do not belong in a web programming language's standard library.

Arbitrary precision arithmetic. BigInteger and BigDecimal types add significant complexity to the runtime for use cases that 99% of web developers never encounter. If FLIN ever needs them, they will be added as a separate module, not as built-in functions.

The line we drew: if a function appears in more than 5% of web applications, it belongs in the standard library. If it appears in less than 1%, it does not. The functions between 1% and 5% were evaluated on a case-by-case basis.

The Result: Math Without Dependencies

After Sessions 122-128, FLIN's mathematics library covered:

  • 28 basic number functions (arithmetic, rounding, random)
  • 7 number properties (is_positive, is_even, sign, etc.)
  • 6 formatting functions (format, to_hex, to_percent, etc.)
  • 8 trigonometry functions (sin, cos, tan, atan2, etc.)
  • 12 statistics functions (median, std_dev, percentile, etc.)
  • 24 geometry functions (distance, area, haversine, etc.)
  • 4 mathematical constants (PI, E, INFINITY, NEG_INFINITY)

A total of 89 functions, all available without a single import statement. Enough to build dashboards, render charts, validate financial calculations, compute distances, and analyze data distributions -- all in a language designed for web developers who want to write application logic, not manage dependencies.

---

This is Part 73 of the "How We Built FLIN" series, documenting how a CEO in Abidjan and an AI CTO built a mathematics library directly into a programming language runtime.

Series Navigation: - [72] 31 String Methods Built Into the Language - [73] Math, Statistics, and Geometry Functions (you are here) - [74] Time and Timezone Functions - [75] HTTP Client Built Into the Language

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles