diff --git a/.gitignore b/.gitignore index 7ccf0345c..7e8ba8c67 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,4 @@ parsetab.py !/il65/lib/* .pytest_cache/ docs/build - +il65/out diff --git a/il65/src/il65/compiler/Compiler.kt b/il65/src/il65/compiler/Compiler.kt index 52b71b6c9..e17182157 100644 --- a/il65/src/il65/compiler/Compiler.kt +++ b/il65/src/il65/compiler/Compiler.kt @@ -2,8 +2,9 @@ package il65.compiler import il65.ast.INameScope import il65.ast.Module -import java.nio.ByteBuffer -import java.nio.ByteOrder +import kotlin.experimental.and +import kotlin.math.absoluteValue +import kotlin.math.pow import kotlin.system.exitProcess @@ -28,86 +29,61 @@ fun Number.toHex(): String { } -fun Double.frexp(): Pair { - var exponent: Int = Math.getExponent(this) - val mantissa: Double +data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short){ - when (exponent) { - 1024 - -> { - // Inf or NaN - mantissa = this - exponent = 0 - } + companion object { + val zero = Mflpt5(0, 0,0,0,0) + fun fromNumber(num: Number): Mflpt5 { + // see https://en.wikipedia.org/wiki/Microsoft_Binary_Format + // and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a + // and https://en.wikipedia.org/wiki/IEEE_754-1985 - -1023 -> if (this == 0.0) { - // -0.0 or 0.0 - mantissa = this - exponent = 0 - } else { - exponent = Math.getExponent(this * 0x10000000000000) - 51 // that is 0x1p52 == 2**52 - mantissa = Math.scalb(this, -exponent) - } + val flt = num.toDouble() + if(flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE) + throw CompilerException("floating point number out of 5-byte mflpt range: $this") + if(flt==0.0) + return zero - else -> { - exponent++ - mantissa = Math.scalb(this, -exponent) - } - } - return Pair(mantissa, exponent) -} + val sign = if(flt<0.0) 0x80L else 0x00L + var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias + var mantissa = flt.absoluteValue -fun Number.toMflpt5(): ShortArray { - // algorithm here https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a - - // @todo fix this - - var flt = this.toDouble() - if(flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE) - throw CompilerException("floating point number out of 5-byte mflpt range: $this") - if(flt==0.0) - return shortArrayOf(0, 0, 0, 0, 0) - val sign: Long = - when { - flt < 0.0 -> { - flt = -flt - 0x80000000L - } - else -> 0x00000000L + // if mantissa is too large, shift right and adjust exponent + while(mantissa >= 0x100000000) { + mantissa /= 2.0 + exponent ++ + } + // if mantissa is too small, shift left and adjust exponent + while(mantissa < 0x80000000) { + mantissa *= 2.0 + exponent -- } - var (mant, exp) = flt.frexp() - exp += 128 - if(exp < 1) { - // underflow, use zero instead - return shortArrayOf(0, 0, 0 ,0 ,0) + return when { + exponent<0 -> zero // underflow, use zero instead + exponent>255 -> throw CompilerException("floating point overflow: $this") + exponent==0 -> zero + else -> { + val mant_long = mantissa.toLong() + Mflpt5( + exponent.toShort(), + (mant_long.and(0x7f000000L) ushr 24).or(sign).toShort(), + (mant_long.and(0x00ff0000L) ushr 16).toShort(), + (mant_long.and(0x0000ff00L) ushr 8).toShort(), + (mant_long.and(0x000000ffL)).toShort()) + } + } + } } - if(exp > 255) { - throw CompilerException("floating point number out of 5-byte mflpt range: $this") + + fun toDouble(): Double { + if(this == zero) return 0.0 + val exp = b0 - 128 + val sign = (b1.and(0x80)) > 0 + val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong()) + val result = number.toDouble() * (2.0).pow(exp) / 0x100000000 + return if(sign) -result else result } - val mflpt = sign.or((mant * 0x100000000L).toLong()).and(0x7fffffffL) - - - val result = ShortArray(5) - result[0] = exp.toShort() - result[1] = (mflpt and -0x1000000 shr 24).toShort() - result[2] = (mflpt and 0x00FF0000 shr 16).toShort() - result[3] = (mflpt and 0x0000FF00 shr 8).toShort() - result[4] = (mflpt and 0x000000FF shr 0).toShort() - return result - /* - - mant, exp = math.frexp(number) - exp += 128 - if exp < 1: - # underflow, use zero instead - return bytearray([0, 0, 0, 0, 0]) - if exp > 255: - raise OverflowError("floating point number out of 5-byte mflpt range", number) - mant = sign | int(mant * 0x100000000) & 0x7fffffff - return bytearray([exp]) + int.to_bytes(mant, 4, "big") - - */ } diff --git a/il65/src/il65/functions/BuiltinFunctions.kt b/il65/src/il65/functions/BuiltinFunctions.kt index b4b407ac5..8c6cb5127 100644 --- a/il65/src/il65/functions/BuiltinFunctions.kt +++ b/il65/src/il65/functions/BuiltinFunctions.kt @@ -76,7 +76,7 @@ fun builtin_abs(args: List, position: Position?, namespace:INameSco throw SyntaxError("built-in function abs requires one numeric argument", position) val float = args[0].constValue(namespace)?.asFloat() if(float!=null) - return IntOrFloatLiteral(Math.abs(float), args[0].position) + return intOrFloatLiteral(Math.abs(float), args[0].position) else throw SyntaxError("built-in function abs requires floating point value as argument", position) } @@ -88,7 +88,7 @@ fun builtin_max(args: List, position: Position?, namespace:INameSco if(constants.contains(null)) throw SyntaxError("not all arguments to max are a constant value", position) val result = constants.map { it?.asFloat()!! }.max() - return IntOrFloatLiteral(result!!, args[0].position) + return intOrFloatLiteral(result!!, args[0].position) } fun builtin_min(args: List, position: Position?, namespace:INameScope): LiteralValue { @@ -98,11 +98,11 @@ fun builtin_min(args: List, position: Position?, namespace:INameSco if(constants.contains(null)) throw SyntaxError("not all arguments to min are a constant value", position) val result = constants.map { it?.asFloat()!! }.min() - return IntOrFloatLiteral(result!!, args[0].position) + return intOrFloatLiteral(result!!, args[0].position) } -private fun IntOrFloatLiteral(value: Double, position: Position?): LiteralValue { +private fun intOrFloatLiteral(value: Double, position: Position?): LiteralValue { val intresult = value.toInt() val result = if(value-intresult==0.0) LiteralValue(intvalue = intresult) diff --git a/il65/test/UnitTests.kt b/il65/test/UnitTests.kt index 79d5b68b1..add1edd35 100644 --- a/il65/test/UnitTests.kt +++ b/il65/test/UnitTests.kt @@ -5,6 +5,7 @@ import il65.ast.VarDecl import il65.ast.VarDeclType import il65.compiler.* import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.closeTo import org.hamcrest.Matchers.equalTo import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @@ -36,42 +37,70 @@ class TestCompiler { @Test fun testFloatToMflpt5() { - assertThat((0).toMflpt5(), equalTo(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00))) - assertThat((3.141592653).toMflpt5(), equalTo(shortArrayOf(0x82, 0x49, 0x0F, 0xDA, 0xA1))) - assertThat((3.141592653589793).toMflpt5(), equalTo(shortArrayOf(0x82, 0x49, 0x0F, 0xDA, 0xA2))) - assertThat((-32768).toMflpt5(), equalTo(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00))) - assertThat((1).toMflpt5(), equalTo(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00))) - assertThat((0.7071067812).toMflpt5(), equalTo(shortArrayOf(0x80, 0x35, 0x04, 0xF3, 0x34))) - assertThat((0.7071067811865476).toMflpt5(), equalTo(shortArrayOf(0x80, 0x35, 0x04, 0xF3, 0x33))) - assertThat((1.4142135624).toMflpt5(), equalTo(shortArrayOf(0x81, 0x35, 0x04, 0xF3, 0x34))) - assertThat((1.4142135623730951).toMflpt5(), equalTo(shortArrayOf(0x81, 0x35, 0x04, 0xF3, 0x33))) - assertThat((-.5).toMflpt5(), equalTo(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00))) - assertThat((0.69314718061).toMflpt5(), equalTo(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xF8))) - assertThat((0.6931471805599453).toMflpt5(), equalTo(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xF7))) - assertThat((10).toMflpt5(), equalTo(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00))) - assertThat((1000000000).toMflpt5(), equalTo(shortArrayOf(0x9E, 0x6E, 0x6B, 0x28, 0x00))) - assertThat((.5).toMflpt5(), equalTo(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00))) - assertThat((1.4426950408889634).toMflpt5(), equalTo(shortArrayOf(0x81, 0x38, 0xAA, 0x3B, 0x29))) - assertThat((1.5707963267948966).toMflpt5(), equalTo(shortArrayOf(0x81, 0x49, 0x0F, 0xDA, 0xA2))) - assertThat((6.283185307179586).toMflpt5(), equalTo(shortArrayOf(0x83, 0x49, 0x0F, 0xDA, 0xA2))) - assertThat((.25).toMflpt5(), equalTo(shortArrayOf(0x7F, 0x00, 0x00, 0x00, 0x00))) + assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00))) + assertThat(Mflpt5.fromNumber(3.141592653), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1))) + assertThat(Mflpt5.fromNumber(3.141592653589793), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2))) + assertThat(Mflpt5.fromNumber(32768), equalTo(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00))) + assertThat(Mflpt5.fromNumber(-32768), equalTo(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00))) + assertThat(Mflpt5.fromNumber(1), equalTo(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00))) + assertThat(Mflpt5.fromNumber(0.7071067812), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34))) + assertThat(Mflpt5.fromNumber(0.7071067811865476), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33))) + assertThat(Mflpt5.fromNumber(1.4142135624), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34))) + assertThat(Mflpt5.fromNumber(1.4142135623730951), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33))) + assertThat(Mflpt5.fromNumber(-.5), equalTo(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00))) + assertThat(Mflpt5.fromNumber(0.69314718061), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8))) + assertThat(Mflpt5.fromNumber(0.6931471805599453), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7))) + assertThat(Mflpt5.fromNumber(10), equalTo(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00))) + assertThat(Mflpt5.fromNumber(1000000000), equalTo(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00))) + assertThat(Mflpt5.fromNumber(.5), equalTo(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00))) + assertThat(Mflpt5.fromNumber(1.4426950408889634), equalTo(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29))) + assertThat(Mflpt5.fromNumber(1.5707963267948966), equalTo(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2))) + assertThat(Mflpt5.fromNumber(6.283185307179586), equalTo(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2))) + assertThat(Mflpt5.fromNumber(.25), equalTo(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00))) + assertThat(Mflpt5.fromNumber(123.45678e22), equalTo(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb))) + assertThat(Mflpt5.fromNumber(-123.45678e-22), equalTo(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b))) } @Test fun testFloatRange() { - assertThat(FLOAT_MAX_POSITIVE.toMflpt5(), equalTo(shortArrayOf(0xff, 0x7f, 0xff, 0xff, 0xff))) - assertThat(FLOAT_MAX_NEGATIVE.toMflpt5(), equalTo(shortArrayOf(0xff, 0xff, 0xff, 0xff, 0xff))) - assertThat((1.7e-38).toMflpt5(), equalTo(shortArrayOf(0x03, 0x39, 0x1d, 0x15, 0x63))) - assertThat((1.7e-39).toMflpt5(), equalTo(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00))) - assertThat((-1.7e-38).toMflpt5(), equalTo(shortArrayOf(0x03, 0xb9, 0x1d, 0x15, 0x63))) - assertThat((-1.7e-39).toMflpt5(), equalTo(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00))) - assertFailsWith { 1.7014118346e+38.toMflpt5() } - assertFailsWith { (-1.7014118346e+38).toMflpt5() } - assertFailsWith { 1.7014118347e+38.toMflpt5() } - assertFailsWith { (-1.7014118347e+38).toMflpt5() } + assertThat(Mflpt5.fromNumber(FLOAT_MAX_POSITIVE), equalTo(Mflpt5(0xff, 0x7f, 0xff, 0xff, 0xff))) + assertThat(Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE), equalTo(Mflpt5(0xff, 0xff, 0xff, 0xff, 0xff))) + assertThat(Mflpt5.fromNumber(1.7e-38), equalTo(Mflpt5(0x03, 0x39, 0x1d, 0x15, 0x63))) + assertThat(Mflpt5.fromNumber(1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00))) + assertThat(Mflpt5.fromNumber(-1.7e-38), equalTo(Mflpt5(0x03, 0xb9, 0x1d, 0x15, 0x63))) + assertThat(Mflpt5.fromNumber(-1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00))) + assertFailsWith { Mflpt5.fromNumber(1.7014118346e+38) } + assertFailsWith { Mflpt5.fromNumber(-1.7014118346e+38) } + assertFailsWith { Mflpt5.fromNumber(1.7014118347e+38) } + assertFailsWith { Mflpt5.fromNumber(-1.7014118347e+38) } } - // @todo test the other way round, mflpt-bytes -> float. + @Test + fun testMflpt5ToFloat() { + val PRECISION=0.000000001 + assertThat(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(0.0)) + assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1).toDouble(), closeTo(3.141592653, PRECISION)) + assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(3.141592653589793, PRECISION)) + assertThat(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(32768.0)) + assertThat(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-32768.0)) + assertThat(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(1.0)) + assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(0.7071067812, PRECISION)) + assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(0.7071067811865476, PRECISION)) + assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(1.4142135624, PRECISION)) + assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(1.4142135623730951, PRECISION)) + assertThat(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-.5)) + assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8).toDouble(), closeTo(0.69314718061, PRECISION)) + assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7).toDouble(), closeTo(0.6931471805599453, PRECISION)) + assertThat(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00).toDouble(), equalTo(10.0)) + assertThat(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00).toDouble(), equalTo(1000000000.0)) + assertThat(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.5)) + assertThat(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29).toDouble(), closeTo(1.4426950408889634, PRECISION)) + assertThat(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(1.5707963267948966, PRECISION)) + assertThat(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(6.283185307179586, PRECISION)) + assertThat(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.25)) + assertThat(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb).toDouble(), closeTo(123.45678e22, 1.0e15)) + assertThat(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b).toDouble(), closeTo(-123.45678e-22, PRECISION)) + } }