summaryrefslogtreecommitdiff
path: root/chapter14
diff options
context:
space:
mode:
Diffstat (limited to 'chapter14')
-rw-r--r--chapter14/README.md17
-rw-r--r--chapter14/src/main/java/com/seaofnodes/simple/Parser.java2
-rw-r--r--chapter14/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java23
-rw-r--r--chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java3
-rw-r--r--chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java24
-rw-r--r--chapter14/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java2
6 files changed, 62 insertions, 9 deletions
diff --git a/chapter14/README.md b/chapter14/README.md
index 81862508..60e82403 100644
--- a/chapter14/README.md
+++ b/chapter14/README.md
@@ -34,10 +34,16 @@ f = f+x; // Auto-widen x to 2.0, result is 2.5
f = f+true; // Since bools are just synonyms for integers 0,1 they auto-widen also
```
-There is no corresponding way right now to round a `flt` back to an `int`:
+Assigning a float to an integer truncates toward zero:
```java
-int x = 3.14; // error
+int rounded = 17.3/2.5; // 6
+rounded = -17.3/2.5; // -6
```
+This applies to variable initialization, reassignment, and stores to integer
+fields or array elements. Conversion first produces a signed 64-bit integer:
+NaN becomes zero, and values outside the range (including infinities) clamp to
+the nearest limit. Narrow integer destinations then apply the same bit
+truncation and sign/zero extension as integer-to-integer assignments.
Here is Newton's method for computing a square root:
```java
@@ -60,14 +66,17 @@ Floating point operations have their own Nodes:
| AddF, SubF, MulF, DivF | Basic binary FP ops |
| MinusF | Unary negate |
| EQF, LTF, LEF | FP compare operators |
+| ToFloat, ToInteger | Numeric conversions |
+| RoundF32 | Round to f32 precision |
The parser uses [`Node.widen()`](src/main/java/com/seaofnodes/simple/node/Node.java)
to choose the floating-point operation and insert `ToFloatNode` conversions.
The float lattice has constants between `TOP` and `BOT`, like the integer
lattice before ranges. [`TypeFloat`](src/main/java/com/seaofnodes/simple/type/TypeFloat.java)
also records whether a value fits `f32`; assigning to `f32` rounds it to that
-precision. Conversion from float back to integer remains a separate, pending
-language feature.
+precision. `ToIntegerNode` performs the inverse numeric conversion on assignment
+to an integer destination, folding constants with the same rules as runtime
+conversion.
## Narrow or sub-word types
diff --git a/chapter14/src/main/java/com/seaofnodes/simple/Parser.java b/chapter14/src/main/java/com/seaofnodes/simple/Parser.java
index 4ab4d9ac..0602d290 100644
--- a/chapter14/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/chapter14/src/main/java/com/seaofnodes/simple/Parser.java
@@ -733,6 +733,8 @@ public class Parser {
// zero/sign extend. "i" is limited to either classic unsigned (min==0) or
// classic signed (min=minus-power-of-2); max=power-of-2-minus-1.
private Node zsMask(Node val, Type t ) {
+ if( val._type instanceof TypeFloat && t instanceof TypeInteger )
+ val = new ToIntegerNode(val).peephole();
if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) {
if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) )
return val;
diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java
new file mode 100644
index 00000000..df740202
--- /dev/null
+++ b/chapter14/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+import com.seaofnodes.simple.type.TypeInteger;
+
+// Truncate toward zero, saturating overflow; NaN converts to zero.
+public class ToIntegerNode extends Node {
+ public ToIntegerNode(Node lhs) { super(null,lhs); }
+ @Override public String label() { return "ToInteger"; }
+ @Override protected String format() { return "(int)%1"; }
+
+ @Override public Type compute() {
+ Type t = in(1)._type;
+ if( t.isHigh() ) return TypeInteger.TOP;
+ if( t instanceof TypeFloat tf && tf.isConstant() )
+ return TypeInteger.constant((long)tf.value());
+ return TypeInteger.BOT;
+ }
+
+ @Override public Node idealize() { return null; }
+ @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); }
+}
diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index e84da36a..9e7240d8 100644
--- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -130,8 +130,7 @@ N n = new N;
n.i = 3.14;
return n.i;
""");
- try { parser.parse().iterate(); fail(); }
- catch( Exception e ) { assertEquals("Cannot store 3.14 into field int i",e.getMessage()); }
+ assertEquals("3",com.seaofnodes.simple.evaluator.Evaluator.evaluate(parser.parse().iterate(),0).toString());
}
@Test
diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
index f2b51da2..5438fb2b 100644
--- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
+++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
@@ -8,6 +8,25 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter14FloatTest {
+ @Test public void testNumericAssignments() {
+ String[][] tests = {
+ {"int rounded=17.3/2.5; return rounded;", "6", "6"},
+ {"int rounded=arg/2.5; return rounded;", "6", "-6"},
+ {"int rounded=0; rounded=arg/2.5; return rounded;", "6", "-6"},
+ {"i8 rounded=arg*16.0; return rounded;", "16", "-16"},
+ {"int rounded=1e100; return rounded;", "9223372036854775807", "9223372036854775807"},
+ {"int rounded=-1e100; return rounded;", "-9223372036854775808", "-9223372036854775808"},
+ {"int rounded=1e300*1e300-1e300*1e300; return rounded;", "0", "0"},
+ {"f32 rounded=arg+16777216.0; return rounded;", "1.6777232E7", "1.6777199E7"}
+ };
+ for( String[] test : tests ) {
+ var stop = new Parser(test[0]).parse().iterate();
+ assertEquals(test[0],test[1],Evaluator.evaluate(stop,17).toString());
+ assertEquals(test[0],test[2],Evaluator.evaluate(stop,-17).toString());
+ }
+ }
+
+
@Test public void testSubZeroFloat() {
var stop = new Parser("flt x = arg; return 0-x;").parse().iterate();
// Compare strings so +0.0 and -0.0 remain distinct.
@@ -81,7 +100,7 @@ return x+1==x;
assertEquals(0L, Evaluator.evaluate(stop, 1));
}
@Test
- public void testReferenceFieldRejectsFloat() {
+ public void testReferenceFieldConvertsFloat() {
Parser parser = new Parser(
"""
struct N { N next; int i; }
@@ -89,7 +108,6 @@ N n = new N;
n.i = 3.14;
return n.i;
""");
- try { parser.parse().iterate(); fail(); }
- catch( Exception e ) { assertEquals("Cannot store 3.14 into field int i",e.getMessage()); }
+ assertEquals("3",com.seaofnodes.simple.evaluator.Evaluator.evaluate(parser.parse().iterate(),0).toString());
}
}
diff --git a/chapter14/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter14/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java
index afe4cfe3..afa35372 100644
--- a/chapter14/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java
+++ b/chapter14/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java
@@ -157,6 +157,8 @@ public class Evaluator {
case OrNode or -> vall(or .in(1)) | vall(or .in(2));
case XorNode xor -> vall(xor.in(1)) ^ vall(xor.in(2));
case CastNode cast -> val(cast.in(1));
+ case RoundF32Node rnd -> (double)(float)vald(rnd.in(1));
+ case ToIntegerNode cvt -> (long)vald(cvt.in(1));
case ToFloatNode cast -> (double)vall(cast.in(1));
case LoadNode load -> load(load);
case StoreNode store -> store(store);