How do Integer and Float work in EK9?

← Getting Started · Ref: Q39

EK9 has two numeric types: Integer (64-bit signed whole numbers) and Float (64-bit IEEE 754 floating-point). Both are full objects, not primitives.

INTEGER LITERALS

Decimal: 42, -17, 0
Hex: 0xFF, 0x1F (prefix 0x)
Binary: 0b1010, 0b11001100 (prefix 0b)
Underscores for readability: 1_000_000, 0xFF_FF
Range: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

FLOAT LITERALS

Decimal: 3.14, -2.5, 0.0
Scientific notation: 4.9E-324, 1.7976931348623157E308

ARITHMETIC OPERATORS

Both types support: + - * / ^ (power), abs, sqrt

  17 + 5 = 22,  17 / 5 = 3 (integer division)
  3.14 * 2.0 = 6.28,  2.0 ^ 0.5 = square root of 2

DIVISION BY ZERO RETURNS UNSET

This is one of EK9's most important safety decisions. Java throws ArithmeticException for integer division by zero and produces NaN/Infinity for float. JavaScript silently produces Infinity or NaN that propagates through calculations. EK9 returns unset for ANY division by zero:

  90 / 0 is unset,  0 / 0 is unset,  90.0 / 0.0 is unset

But 0 / 90 = 0 (zero divided by non-zero is valid).
IEEE 754 NaN was designed to let calculations continue, hoping programmers would check later. 50 years shows they don't. NaN propagates silently producing garbage, and NaN != NaN breaks equality. EK9's unset achieves the same 'continue calculating' benefit but the ? operator makes checking explicit.

PROMOTE INTEGER TO FLOAT: #^

Use the #^ operator to convert Integer to Float:

  intVal <- 42
  floatVal <- #^ intVal

Float has no promote because it is already the widest numeric type. This explicit promotion means you always know where Integer becomes Float.

WHY ONLY TWO NUMERIC TYPES?

Java has 6 numeric primitives plus boxed wrappers. Rust has 12 numeric types. Go has 15+. EK9 has Integer and Float. Period. No byte, short, long, unsigned, int8, float32.
This eliminates: silent narrowing conversions (Java long to int), signed/unsigned confusion (C unsigned overflow), float-to-double precision surprises, integer overflow wrapping. One whole number type, one decimal type. If you need arbitrary precision decimals, use Money. If you need bit manipulation, use Bits.

See Q91 for advanced features (mod vs rem, factorial, bitwise, locale formatting, streams). See Q40 for the Bits type. See Q25 for more on the #^ promote operator. See Q44 for Locale formatting. See Q240 for arithmetic and math operators (mod, rem, abs, sqrt, ^). Use 'ek9 -h Integer' and 'ek9 -h Float' for the full API.

Example

defines module qa.integer.and.float

  defines program
    IntegerAndFloatDemo()
      stdout <- Stdout()

      // === INTEGER LITERALS ===

      decimal <- 42
      hex <- 0xFF
      binary <- 0b10101010
      big <- 1_000_000

      stdout.println(`Decimal: ${decimal}`)
      stdout.println(`Hex 0xFF: ${hex}`)
      stdout.println(`Binary 0b10101010: ${binary}`)
      stdout.println(`With underscores: ${big}`)

      // === BASIC ARITHMETIC ===

      a <- 17
      b <- 5
      stdout.println(`${a} + ${b} = ${a + b}`)
      stdout.println(`${a} - ${b} = ${a - b}`)
      stdout.println(`${a} * ${b} = ${a * b}`)
      stdout.println(`${a} / ${b} = ${a / b}`)

      // Power, absolute value, square root
      stdout.println(`2 ^ 10 = ${2 ^ 10}`)
      stdout.println(`abs -49 = ${abs -49}`)
      stdout.println(`sqrt 49 = ${sqrt 49}`)

      // === FLOAT BASICS ===

      pi <- 3.14159
      scientific <- 2.5E10
      stdout.println(`Pi: ${pi}`)
      stdout.println(`Scientific: ${scientific}`)
      stdout.println(`2.0 ^ 0.5 = ${2.0 ^ 0.5}`)
      stdout.println(`abs -16.0 = ${abs -16.0}`)

      // === DIVISION BY ZERO RETURNS UNSET ===

      zero <- 0
      divResult <- 90 / zero
      stdout.println(`90 / 0 isSet: ${divResult?}`)

      fzero <- 0.0
      fResult <- 90.0 / fzero
      stdout.println(`90.0 / 0.0 isSet: ${fResult?}`)

      // Zero divided by non-zero is valid
      normalDiv <- 0 / 90
      stdout.println(`0 / 90 = ${normalDiv}`)

      // === PROMOTE INTEGER TO FLOAT ===

      intVal <- 42
      floatVal <- #^ intVal
      stdout.println(`Promoted: ${floatVal}`)

      // === COMPARISON ===

      five <- 5
      anotherFive <- 5
      ten <- 10
      stdout.println(`5 < 10: ${five < ten}`)
      stdout.println(`5 == 5: ${five == anotherFive}`)
      ordering <- five <=> ten
      stdout.println(`5 <=> 10: ${ordering}`)

Common mistakes

E50030 — A String value cannot be assigned to an Integer variable. EK9 is strongly typed and does not implicitly convert between unrelated types. Use Integer("42") for explicit parsing. See ek9 -h E50030 for details.

Incorrect:

decimal as Integer: "forty-two"

Correct:

decimal <- 42
Other ways to ask this
  • What numeric types does EK9 have and how do hex and binary literals work?
  • Why does division by zero return unset instead of throwing an exception?
  • Why does EK9 only have two numeric types instead of byte, short, long, unsigned?

Coming from another language?

Java: 6 numeric primitives (byte, short, int, long, float, double) plus boxed wrappers, ArithmeticException on integer div-by-zero, Double.NaN for float div-by-zero. Python: arbitrary precision integers, float backed by C double, ZeroDivisionError. JavaScript: single Number type (IEEE 754 double), Infinity/NaN, no integer type. Rust: 12 numeric types (i8-i128, u8-u128, f32, f64), panic on overflow in debug. Go: 15+ numeric types, panic on integer div-by-zero, math.NaN for float. EK9: Integer (64-bit) and Float (64-bit) only, div-by-zero returns unset, explicit #^ promote.

Keywords: beginner, arithmetic, start, basics, binary, unset, zero, float, promote, integer, intro, hex, underscore, number, first, literal, migrate, nan, division, numeric