feat(lang): float and double types with ordinary operators
`float` (32-bit) and `double` (64-bit) with + - * / %, comparisons and unary minus. Decimal literals take their type from context and stay `fixed` elsewhere; int and long promote implicitly (LUDIC_WARN_FLOAT_PROMOTE=1 lists every promotion). float(), double(), int(), long() and fixed() convert; floats(n)/doubles(n) buffers; float fields, globals, constants and parameters; Math.* computes in float for float arguments; string/print write the shortest round-tripping decimal; float_bits/float_from_bits expose the bits. @deterministic code may not use floats. The f_* runtime helpers stay as they are. Editors know the new type words; the JetBrains plugin is 1.5.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f92d7f89c6
commit
e010c2cecc
40 changed files with 67726 additions and 57272 deletions
|
|
@ -4,15 +4,15 @@ name: fixed
|
|||
category: builtins
|
||||
kind: builtin
|
||||
tokens: fixed
|
||||
sig: fixed(n) -> fixed
|
||||
tip: Lift an integer into a Q16.16 fixed-point value.
|
||||
sig: fixed(x) -> fixed
|
||||
tip: Convert an int (or a float) into a Q16.16 fixed-point value.
|
||||
order: 50
|
||||
---
|
||||
|
||||
Converts an integer into a <code>fixed</code> value (Ludic's Q16.16 fixed-point type), so it can take part in fractional arithmetic. Ludic has no floating point; <code>fixed</code> is how you carry sub-pixel precision for smooth movement and physics-like accumulation. Use <code>fixed</code> when you need to combine an <code>int</code> with fixed-point values or divide to get a fraction — for example <code>fixed(1) / fixed(4)</code> is <code>0.25</code>. Convert back to a whole number for drawing with <code>flr</code>.
|
||||
Converts an integer into a <code>fixed</code> value (Ludic's Q16.16 fixed-point type), so it can take part in fractional arithmetic. <code>fixed</code> is how you carry deterministic sub-pixel precision for smooth movement and physics-like accumulation; a <code>float</code> or <code>double</code> converts too, truncated toward zero to Q16.16. Use <code>fixed</code> when you need to combine an <code>int</code> with fixed-point values or divide to get a fraction — for example <code>fixed(1) / fixed(4)</code> is <code>0.25</code>. Convert back to a whole number for drawing with <code>flr</code>.
|
||||
|
||||
Parameters:
|
||||
- `n` — the integer to lift into fixed-point
|
||||
- `x` — the integer (or float) to convert
|
||||
|
||||
```ludic
|
||||
program SmoothAccumulate {
|
||||
|
|
|
|||
25
docs/language/builtins/fn-float.md
Normal file
25
docs/language/builtins/fn-float.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
id: fn-float
|
||||
name: float
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: float double
|
||||
sig: float(x) -> float / double(x) -> double
|
||||
tip: Convert any number to a float (or double).
|
||||
order: 51
|
||||
---
|
||||
|
||||
Converts an <code>int</code>, <code>long</code>, <code>fixed</code>, <code>float</code> or <code>double</code> to a <code>float</code> (or, with <code>double(x)</code>, to a <code>double</code>). It is the one conversion that crosses from fixed-point to floating point: `float(fixed(1)) / 4.0` is <code>0.25</code>. A decimal literal converts exactly, so `float(0.1)` is the nearest float to 0.1, not the nearest Q16.16 value.
|
||||
|
||||
Parameters:
|
||||
- `x` — the number to convert
|
||||
|
||||
```ludic
|
||||
program Convert {
|
||||
entry {
|
||||
let half = fixed(1) / 2
|
||||
print(float(half) * 3.0) # 1.5
|
||||
print(double(7) / 2) # 3.5
|
||||
}
|
||||
}
|
||||
```
|
||||
24
docs/language/builtins/fn-float_bits.md
Normal file
24
docs/language/builtins/fn-float_bits.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
id: fn-float_bits
|
||||
name: float_bits
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: float_bits float_from_bits double_bits double_from_bits
|
||||
sig: float_bits(x) -> int / float_from_bits(bits) -> float
|
||||
tip: A float's IEEE bit pattern as an int, and back.
|
||||
order: 53
|
||||
---
|
||||
|
||||
<code>float_bits(x)</code> returns the 32 bits of a <code>float</code> as an <code>int</code>, and <code>float_from_bits(bits)</code> reads them back. <code>double_bits</code> / <code>double_from_bits</code> do the same for a <code>double</code> with a <code>long</code>. Use them where a float has to travel as raw data: a file format, a network packet, or a <code>words</code> buffer shared with code that still stores bit patterns.
|
||||
|
||||
Parameters:
|
||||
- `x` — the float to encode (`float_bits`), or the bits to decode (`float_from_bits`)
|
||||
|
||||
```ludic
|
||||
program Bits {
|
||||
entry {
|
||||
print(float_bits(1.0)) # 1065353216 (0x3F800000)
|
||||
print(float_from_bits(0x40000000)) # 2.0
|
||||
}
|
||||
}
|
||||
```
|
||||
25
docs/language/builtins/fn-int.md
Normal file
25
docs/language/builtins/fn-int.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
id: fn-int
|
||||
name: int
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: int long
|
||||
sig: int(x) -> int / long(x) -> long
|
||||
tip: Convert a number to a whole number, truncating toward zero.
|
||||
order: 52
|
||||
---
|
||||
|
||||
Converts a <code>float</code>, <code>double</code>, <code>fixed</code> or <code>long</code> to an <code>int</code> (or, with <code>long(x)</code>, to a <code>long</code>). Floating-point values truncate toward zero, as in C: `int(2.9)` is <code>2</code> and `int(-2.9)` is <code>-2</code>. A <code>fixed</code> value rounds down, like <code>floor</code>. For other rounding use <code>Math.floor</code>, <code>Math.ceil</code> or <code>Math.round</code>.
|
||||
|
||||
Parameters:
|
||||
- `x` — the number to convert
|
||||
|
||||
```ludic
|
||||
program Whole {
|
||||
entry {
|
||||
let pixels: float = 12.75
|
||||
print(int(pixels)) # 12
|
||||
print(long(pixels * 1000.0)) # 12750
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -5,3 +5,5 @@ order: 6
|
|||
---
|
||||
|
||||
Deterministic fixed-point math. Every function is computed in Q16.16 with plain integer arithmetic, so results are bit-identical on every platform and every run — the same guarantee the rest of the runtime gives. Arguments are positional.
|
||||
|
||||
Given a <code>float</code> or <code>double</code> argument, the same functions compute in that type instead (using the platform's math library) and return it — <code>floor</code>, <code>ceil</code> and <code>round</code> included, which return an <code>int</code> only for <code>fixed</code>. <code>sign</code> returns an <code>int</code> either way. <code>trunc</code> and <code>atan</code> exist only for floats.
|
||||
|
|
|
|||
25
docs/language/types/type-double.md
Normal file
25
docs/language/types/type-double.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
id: type-double
|
||||
name: double
|
||||
category: types
|
||||
kind: type
|
||||
tokens: double
|
||||
sig: double
|
||||
tip: A 64-bit IEEE floating-point number for precise math.
|
||||
order: 4
|
||||
---
|
||||
|
||||
A <code>double</code> is an IEEE-754 double-precision number: about sixteen significant digits. Use it where a <code>float</code> loses precision — large world coordinates, accumulated time, geodesy — and convert to <code>float</code> at the boundary to the GPU.
|
||||
|
||||
It behaves like <code>float</code>: decimal literals take its type from context, <code>int</code> and <code>long</code> promote to it, and a <code>float</code> mixed with a <code>double</code> widens to <code>double</code>. Narrowing back is explicit: <code>float(d)</code>.
|
||||
|
||||
```ludic
|
||||
program Precise {
|
||||
entry {
|
||||
let total: double = 0.1
|
||||
print(total + 0.2) # 0.30000000000000004
|
||||
let small: float = float(total)
|
||||
print(small) # 0.1
|
||||
}
|
||||
}
|
||||
```
|
||||
30
docs/language/types/type-float.md
Normal file
30
docs/language/types/type-float.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
id: type-float
|
||||
name: float
|
||||
category: types
|
||||
kind: type
|
||||
tokens: float
|
||||
sig: float
|
||||
tip: A 32-bit IEEE floating-point number — what the GPU uses.
|
||||
order: 3
|
||||
---
|
||||
|
||||
A <code>float</code> is an IEEE-754 single-precision number: about seven significant digits, a huge range, and ordinary operators (<code>+ - * / %</code>, comparisons). It is the type for rendering and physics that runs on the GPU or talks to it — positions, normals, colours in shaders, matrices — and for any math where <code>fixed</code>'s ±32768 range is too small.
|
||||
|
||||
A decimal literal takes its type from where it is used: `let s: float = 0.5`, `x * 0.5` with a float `x`, and a float parameter all read `0.5` exactly as a float, while an untyped `let k = 0.5` stays <code>fixed</code>. An <code>int</code> promotes to float automatically; everything else converts explicitly with <code>float(x)</code>, <code>int(x)</code>, <code>fixed(x)</code>. <code>Math.*</code> accepts floats and answers in float.
|
||||
|
||||
Floats are not deterministic across machines the way <code>fixed</code> is, so simulation that must replay bit-for-bit stays in <code>fixed</code>.
|
||||
|
||||
```ludic
|
||||
program Orbit {
|
||||
function radius(x: float, y: float) -> float { return Math.sqrt(x * x + y * y) }
|
||||
|
||||
entry {
|
||||
let speed: float = 1.5
|
||||
var angle: float = 0.0
|
||||
angle = angle + speed * 2 # the int 2 promotes
|
||||
print(radius(3, 4)) # 5.0
|
||||
print(Math.sin(angle) < 1.0)
|
||||
}
|
||||
}
|
||||
```
|
||||
24
docs/language/types/type-floats.md
Normal file
24
docs/language/types/type-floats.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
id: type-floats
|
||||
name: floats
|
||||
category: types
|
||||
kind: type
|
||||
tokens: floats doubles
|
||||
sig: floats / doubles
|
||||
tip: A buffer of floats (or doubles) — v[i] reads and writes one.
|
||||
order: 9
|
||||
---
|
||||
|
||||
<code>floats(n)</code> allocates room for <code>n</code> <code>float</code> values and returns a <code>floats</code> buffer; <code>doubles(n)</code> does the same for <code>double</code>. Index it with <code>v[i]</code> to read or write an element, with no bounds checking. The memory is exactly what a GPU vertex or uniform buffer expects, so a <code>floats</code> buffer uploads as it is.
|
||||
|
||||
```ludic
|
||||
program Vertices {
|
||||
entry {
|
||||
let v = floats(6)
|
||||
v[0] = 0.0; v[1] = 0.5
|
||||
v[2] = -0.5; v[3] = -0.5
|
||||
v[4] = 0.5; v[5] = -0.5
|
||||
print(v[1] - v[3]) # 1.0
|
||||
}
|
||||
}
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue