fix float generation

This commit is contained in:
Irmen de Jong 2018-08-30 23:07:50 +02:00
parent 8368633ed2
commit bc558019ee
4 changed files with 114 additions and 109 deletions

2
.gitignore vendored
View File

@ -19,4 +19,4 @@ parsetab.py
!/il65/lib/* !/il65/lib/*
.pytest_cache/ .pytest_cache/
docs/build docs/build
il65/out

View File

@ -2,8 +2,9 @@ package il65.compiler
import il65.ast.INameScope import il65.ast.INameScope
import il65.ast.Module import il65.ast.Module
import java.nio.ByteBuffer import kotlin.experimental.and
import java.nio.ByteOrder import kotlin.math.absoluteValue
import kotlin.math.pow
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -28,86 +29,61 @@ fun Number.toHex(): String {
} }
fun Double.frexp(): Pair<Double, Int> { data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short){
var exponent: Int = Math.getExponent(this)
val mantissa: Double
when (exponent) { companion object {
1024 val zero = Mflpt5(0, 0,0,0,0)
-> { fun fromNumber(num: Number): Mflpt5 {
// Inf or NaN // see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
mantissa = this // and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
exponent = 0 // and https://en.wikipedia.org/wiki/IEEE_754-1985
}
-1023 -> if (this == 0.0) { val flt = num.toDouble()
// -0.0 or 0.0 if(flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
mantissa = this throw CompilerException("floating point number out of 5-byte mflpt range: $this")
exponent = 0 if(flt==0.0)
} else { return zero
exponent = Math.getExponent(this * 0x10000000000000) - 51 // that is 0x1p52 == 2**52
mantissa = Math.scalb(this, -exponent)
}
else -> { val sign = if(flt<0.0) 0x80L else 0x00L
exponent++ var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
mantissa = Math.scalb(this, -exponent) var mantissa = flt.absoluteValue
}
}
return Pair(mantissa, exponent)
}
fun Number.toMflpt5(): ShortArray { // if mantissa is too large, shift right and adjust exponent
// algorithm here https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a while(mantissa >= 0x100000000) {
mantissa /= 2.0
// @todo fix this exponent ++
}
var flt = this.toDouble() // if mantissa is too small, shift left and adjust exponent
if(flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE) while(mantissa < 0x80000000) {
throw CompilerException("floating point number out of 5-byte mflpt range: $this") mantissa *= 2.0
if(flt==0.0) exponent --
return shortArrayOf(0, 0, 0, 0, 0)
val sign: Long =
when {
flt < 0.0 -> {
flt = -flt
0x80000000L
}
else -> 0x00000000L
} }
var (mant, exp) = flt.frexp() return when {
exp += 128 exponent<0 -> zero // underflow, use zero instead
if(exp < 1) { exponent>255 -> throw CompilerException("floating point overflow: $this")
// underflow, use zero instead exponent==0 -> zero
return shortArrayOf(0, 0, 0 ,0 ,0) 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")
*/
} }

View File

@ -76,7 +76,7 @@ fun builtin_abs(args: List<IExpression>, position: Position?, namespace:INameSco
throw SyntaxError("built-in function abs requires one numeric argument", position) throw SyntaxError("built-in function abs requires one numeric argument", position)
val float = args[0].constValue(namespace)?.asFloat() val float = args[0].constValue(namespace)?.asFloat()
if(float!=null) if(float!=null)
return IntOrFloatLiteral(Math.abs(float), args[0].position) return intOrFloatLiteral(Math.abs(float), args[0].position)
else else
throw SyntaxError("built-in function abs requires floating point value as argument", position) throw SyntaxError("built-in function abs requires floating point value as argument", position)
} }
@ -88,7 +88,7 @@ fun builtin_max(args: List<IExpression>, position: Position?, namespace:INameSco
if(constants.contains(null)) if(constants.contains(null))
throw SyntaxError("not all arguments to max are a constant value", position) throw SyntaxError("not all arguments to max are a constant value", position)
val result = constants.map { it?.asFloat()!! }.max() val result = constants.map { it?.asFloat()!! }.max()
return IntOrFloatLiteral(result!!, args[0].position) return intOrFloatLiteral(result!!, args[0].position)
} }
fun builtin_min(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue { fun builtin_min(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue {
@ -98,11 +98,11 @@ fun builtin_min(args: List<IExpression>, position: Position?, namespace:INameSco
if(constants.contains(null)) if(constants.contains(null))
throw SyntaxError("not all arguments to min are a constant value", position) throw SyntaxError("not all arguments to min are a constant value", position)
val result = constants.map { it?.asFloat()!! }.min() 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 intresult = value.toInt()
val result = if(value-intresult==0.0) val result = if(value-intresult==0.0)
LiteralValue(intvalue = intresult) LiteralValue(intvalue = intresult)

View File

@ -5,6 +5,7 @@ import il65.ast.VarDecl
import il65.ast.VarDeclType import il65.ast.VarDeclType
import il65.compiler.* import il65.compiler.*
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.closeTo
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
@ -36,42 +37,70 @@ class TestCompiler {
@Test @Test
fun testFloatToMflpt5() { fun testFloatToMflpt5() {
assertThat((0).toMflpt5(), equalTo(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00))) assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
assertThat((3.141592653).toMflpt5(), equalTo(shortArrayOf(0x82, 0x49, 0x0F, 0xDA, 0xA1))) assertThat(Mflpt5.fromNumber(3.141592653), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1)))
assertThat((3.141592653589793).toMflpt5(), equalTo(shortArrayOf(0x82, 0x49, 0x0F, 0xDA, 0xA2))) assertThat(Mflpt5.fromNumber(3.141592653589793), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2)))
assertThat((-32768).toMflpt5(), equalTo(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00))) assertThat(Mflpt5.fromNumber(32768), equalTo(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00)))
assertThat((1).toMflpt5(), equalTo(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00))) assertThat(Mflpt5.fromNumber(-32768), equalTo(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00)))
assertThat((0.7071067812).toMflpt5(), equalTo(shortArrayOf(0x80, 0x35, 0x04, 0xF3, 0x34))) assertThat(Mflpt5.fromNumber(1), equalTo(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00)))
assertThat((0.7071067811865476).toMflpt5(), equalTo(shortArrayOf(0x80, 0x35, 0x04, 0xF3, 0x33))) assertThat(Mflpt5.fromNumber(0.7071067812), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34)))
assertThat((1.4142135624).toMflpt5(), equalTo(shortArrayOf(0x81, 0x35, 0x04, 0xF3, 0x34))) assertThat(Mflpt5.fromNumber(0.7071067811865476), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33)))
assertThat((1.4142135623730951).toMflpt5(), equalTo(shortArrayOf(0x81, 0x35, 0x04, 0xF3, 0x33))) assertThat(Mflpt5.fromNumber(1.4142135624), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34)))
assertThat((-.5).toMflpt5(), equalTo(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00))) assertThat(Mflpt5.fromNumber(1.4142135623730951), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33)))
assertThat((0.69314718061).toMflpt5(), equalTo(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xF8))) assertThat(Mflpt5.fromNumber(-.5), equalTo(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00)))
assertThat((0.6931471805599453).toMflpt5(), equalTo(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xF7))) assertThat(Mflpt5.fromNumber(0.69314718061), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8)))
assertThat((10).toMflpt5(), equalTo(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00))) assertThat(Mflpt5.fromNumber(0.6931471805599453), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7)))
assertThat((1000000000).toMflpt5(), equalTo(shortArrayOf(0x9E, 0x6E, 0x6B, 0x28, 0x00))) assertThat(Mflpt5.fromNumber(10), equalTo(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00)))
assertThat((.5).toMflpt5(), equalTo(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00))) assertThat(Mflpt5.fromNumber(1000000000), equalTo(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00)))
assertThat((1.4426950408889634).toMflpt5(), equalTo(shortArrayOf(0x81, 0x38, 0xAA, 0x3B, 0x29))) assertThat(Mflpt5.fromNumber(.5), equalTo(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00)))
assertThat((1.5707963267948966).toMflpt5(), equalTo(shortArrayOf(0x81, 0x49, 0x0F, 0xDA, 0xA2))) assertThat(Mflpt5.fromNumber(1.4426950408889634), equalTo(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29)))
assertThat((6.283185307179586).toMflpt5(), equalTo(shortArrayOf(0x83, 0x49, 0x0F, 0xDA, 0xA2))) assertThat(Mflpt5.fromNumber(1.5707963267948966), equalTo(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2)))
assertThat((.25).toMflpt5(), equalTo(shortArrayOf(0x7F, 0x00, 0x00, 0x00, 0x00))) 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 @Test
fun testFloatRange() { fun testFloatRange() {
assertThat(FLOAT_MAX_POSITIVE.toMflpt5(), equalTo(shortArrayOf(0xff, 0x7f, 0xff, 0xff, 0xff))) assertThat(Mflpt5.fromNumber(FLOAT_MAX_POSITIVE), equalTo(Mflpt5(0xff, 0x7f, 0xff, 0xff, 0xff)))
assertThat(FLOAT_MAX_NEGATIVE.toMflpt5(), equalTo(shortArrayOf(0xff, 0xff, 0xff, 0xff, 0xff))) assertThat(Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE), equalTo(Mflpt5(0xff, 0xff, 0xff, 0xff, 0xff)))
assertThat((1.7e-38).toMflpt5(), equalTo(shortArrayOf(0x03, 0x39, 0x1d, 0x15, 0x63))) assertThat(Mflpt5.fromNumber(1.7e-38), equalTo(Mflpt5(0x03, 0x39, 0x1d, 0x15, 0x63)))
assertThat((1.7e-39).toMflpt5(), equalTo(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00))) assertThat(Mflpt5.fromNumber(1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
assertThat((-1.7e-38).toMflpt5(), equalTo(shortArrayOf(0x03, 0xb9, 0x1d, 0x15, 0x63))) assertThat(Mflpt5.fromNumber(-1.7e-38), equalTo(Mflpt5(0x03, 0xb9, 0x1d, 0x15, 0x63)))
assertThat((-1.7e-39).toMflpt5(), equalTo(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00))) assertThat(Mflpt5.fromNumber(-1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
assertFailsWith<CompilerException> { 1.7014118346e+38.toMflpt5() } assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
assertFailsWith<CompilerException> { (-1.7014118346e+38).toMflpt5() } assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
assertFailsWith<CompilerException> { 1.7014118347e+38.toMflpt5() } assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
assertFailsWith<CompilerException> { (-1.7014118347e+38).toMflpt5() } assertFailsWith<CompilerException> { 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))
}
} }