mirror of
https://github.com/irmen/prog8.git
synced 2024-11-24 13:32:28 +00:00
fix float generation
This commit is contained in:
parent
8368633ed2
commit
bc558019ee
2
.gitignore
vendored
2
.gitignore
vendored
@ -19,4 +19,4 @@ parsetab.py
|
||||
!/il65/lib/*
|
||||
.pytest_cache/
|
||||
docs/build
|
||||
|
||||
il65/out
|
||||
|
@ -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<Double, Int> {
|
||||
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")
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
@ -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)
|
||||
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<IExpression>, 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<IExpression>, position: Position?, namespace:INameScope): LiteralValue {
|
||||
@ -98,11 +98,11 @@ fun builtin_min(args: List<IExpression>, 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)
|
||||
|
@ -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<CompilerException> { 1.7014118346e+38.toMflpt5() }
|
||||
assertFailsWith<CompilerException> { (-1.7014118346e+38).toMflpt5() }
|
||||
assertFailsWith<CompilerException> { 1.7014118347e+38.toMflpt5() }
|
||||
assertFailsWith<CompilerException> { (-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<CompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
|
||||
assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
|
||||
assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user