diff --git a/build.gradle.kts b/build.gradle.kts index 7c16edad9..37008b37b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -54,6 +54,7 @@ dependencies { val arrowVersion = "1.1.5" val olcaSimaproVersion = "3.0.5" val kotlinxSerializationJSONVersion = "1.5.1" + implementation(platform("io.arrow-kt:arrow-stack:$arrowVersion")) implementation("io.arrow-kt:arrow-core") implementation("io.arrow-kt:arrow-optics") @@ -91,7 +92,6 @@ qodana { showReport.set(System.getenv("QODANA_SHOW_REPORT")?.toBoolean() ?: false) } - tasks { // Set the JVM compatibility versions properties("javaVersion").let { diff --git a/src/main/kotlin/ch/kleis/lcaplugin/ui/toolwindow/FloatingPointRepresentation.kt b/src/main/kotlin/ch/kleis/lcaplugin/ui/toolwindow/FloatingPointRepresentation.kt new file mode 100644 index 000000000..2016f9730 --- /dev/null +++ b/src/main/kotlin/ch/kleis/lcaplugin/ui/toolwindow/FloatingPointRepresentation.kt @@ -0,0 +1,100 @@ +package ch.kleis.lcaplugin.ui.toolwindow + +import arrow.core.flatten +import arrow.core.replicate +import com.intellij.util.containers.tail +import kotlin.math.* + +data class FloatingPointRepresentation( + val isPositive: Boolean, + val digits: List, + val positionalExponent: Int, + val nbSignificantDigits: Int, +) { + companion object { + fun of(value: Double, nbSignificantDigits: Int = 3): FloatingPointRepresentation { + if (value == 0.0) { + return FloatingPointRepresentation( + true, + IntRange(1, nbSignificantDigits).map { 0 }, + 0, + nbSignificantDigits + ) + } + val isPositive = value > 0.0 + val w = abs(value) + val e = ceil(log10(w)).toInt() + val positionalExponent = + if (w == 10.0.pow(e)) e + else e - 1 + val shift = nbSignificantDigits - positionalExponent + val reversedDigits = ArrayList() + var u = floor(w * (10.0.pow(shift))).toInt() + while (u > 0) { + reversedDigits.add(u.mod(10)) + u /= 10 + } + val digits = reversedDigits.reversed().take(nbSignificantDigits) + + if (digits.tail().take(nbSignificantDigits - 1).all { it == 9 }) { + val hd = digits[0] + if (hd == 9) { + val zeros = listOf(0).replicate(nbSignificantDigits - 2).flatten() + return FloatingPointRepresentation( + isPositive, + listOf(1, 0) + zeros, + positionalExponent + 1, + nbSignificantDigits + ) + } + val zeros = listOf(0).replicate(nbSignificantDigits - 1).flatten() + return FloatingPointRepresentation( + isPositive, + listOf(hd + 1) + zeros, + positionalExponent, + nbSignificantDigits + ) + } + return FloatingPointRepresentation(isPositive, digits, positionalExponent, nbSignificantDigits) + } + } + + override fun toString(): String { + return when (positionalExponent) { + in -2..-1 -> { + val hd = listOf(0).replicate(-positionalExponent - 1).flatten() + .joinToString("") + .let { "0.$it" } + val tl = digits.take(nbSignificantDigits - positionalExponent + 1) + .dropLastWhile { it == 0 } + .joinToString("") + val sign = if (isPositive) "" else "-" + "$sign$hd$tl" + } + + in 0..2 -> { + val midpoint = min(positionalExponent + 1, nbSignificantDigits) + val hd = digits.subList(0, midpoint) + .joinToString("") + val tl = digits.subList(midpoint, nbSignificantDigits) + .dropLastWhile { it == 0 } + .joinToString("") + .let { if (it.isEmpty()) "" else ".$it" } + val sign = if (isPositive) "" else "-" + "$sign$hd$tl" + } + + else -> { + val hd = digits.subList(0, 1) + .joinToString("") + val tl = digits.subList(1, nbSignificantDigits) + .dropLastWhile { it == 0 } + .joinToString("") + .let { if (it.isEmpty()) "" else ".$it" } + val sign = if (isPositive) "" else "-" + val e = "E$positionalExponent" + "$sign$hd$tl$e" + } + } + } +} diff --git a/src/main/kotlin/ch/kleis/lcaplugin/ui/toolwindow/InventoryTableModel.kt b/src/main/kotlin/ch/kleis/lcaplugin/ui/toolwindow/InventoryTableModel.kt index ebc36d30b..28ffcfe75 100644 --- a/src/main/kotlin/ch/kleis/lcaplugin/ui/toolwindow/InventoryTableModel.kt +++ b/src/main/kotlin/ch/kleis/lcaplugin/ui/toolwindow/InventoryTableModel.kt @@ -2,7 +2,6 @@ package ch.kleis.lcaplugin.ui.toolwindow import ch.kleis.lcaplugin.core.assessment.Inventory import ch.kleis.lcaplugin.core.lang.value.MatrixColumnIndex -import ch.kleis.lcaplugin.core.matrix.ImpactFactorMatrix import javax.swing.event.TableModelListener import javax.swing.table.TableModel @@ -59,7 +58,7 @@ class InventoryTableModel( val quantity = inventory.supply.quantityOf(outputProduct) if (columnIndex == 1) { - return "${quantity.amount}" + return FloatingPointRepresentation.of(quantity.amount).toString() } if (columnIndex == 2) { return "${quantity.unit.symbol}" @@ -67,7 +66,7 @@ class InventoryTableModel( val inputProduct = sortedControllablePorts[columnIndex - 3] val ratio = inventory.impactFactors.valueRatio(outputProduct, inputProduct).amount - return quantity.amount * ratio + return FloatingPointRepresentation.of(quantity.amount * ratio).toString() } override fun setValueAt(aValue: Any?, rowIndex: Int, columnIndex: Int) { diff --git a/src/test/kotlin/ch/kleis/lcaplugin/ui/toolwindow/DisplayedNumberTest.kt b/src/test/kotlin/ch/kleis/lcaplugin/ui/toolwindow/DisplayedNumberTest.kt new file mode 100644 index 000000000..30fed0e48 --- /dev/null +++ b/src/test/kotlin/ch/kleis/lcaplugin/ui/toolwindow/DisplayedNumberTest.kt @@ -0,0 +1,68 @@ +package ch.kleis.lcaplugin.ui.toolwindow + +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameters + + +@RunWith(Parameterized::class) +class DisplayedNumberTest( + private val value: Double, + private val expected: String, +) { + companion object { + @Parameters + @JvmStatic + fun getDisplayedStrings(): Collection> { + return listOf( + arrayOf(0.0, "0"), + arrayOf(0.0001, "1E-4"), + arrayOf(0.001, "1E-3"), + arrayOf(0.01, "0.01"), + arrayOf(0.1, "0.1"), + arrayOf(1.0, "1"), + arrayOf(10.0, "10"), + arrayOf(100.0, "100"), + arrayOf(1000.0, "1E3"), + + arrayOf(0.000999, "1E-3"), + arrayOf(0.00999, "0.01"), + arrayOf(0.0999, "0.1"), + arrayOf(0.999, "1"), + arrayOf(9.99, "10"), + arrayOf(99.9, "100"), + arrayOf(999.0, "1E3"), + + arrayOf(0.00123, "1.23E-3"), + arrayOf(0.0123, "0.0123"), + arrayOf(0.123, "0.123"), + arrayOf(1.2345, "1.23"), + arrayOf(12.345, "12.3"), + arrayOf(123.45, "123"), + arrayOf(1234.5, "1.23E3"), + arrayOf(12345.123, "1.23E4"), + + arrayOf(-0.0, "0"), + arrayOf(-0.00123, "-1.23E-3"), + arrayOf(-0.0123, "-0.0123"), + arrayOf(-0.123, "-0.123"), + arrayOf(-1.2345, "-1.23"), + arrayOf(-12.345, "-12.3"), + arrayOf(-123.45, "-123"), + arrayOf(-1234.5, "-1.23E3"), + arrayOf(-12345.123, "-1.23E4"), + ) + } + } + + @Test + fun run() { + // when + val actual = FloatingPointRepresentation.of(value).toString() + + // then + assertEquals(expected, actual) + } +} diff --git a/src/test/kotlin/ch/kleis/lcaplugin/ui/toolwindow/FloatingPointRepresentationTest.kt b/src/test/kotlin/ch/kleis/lcaplugin/ui/toolwindow/FloatingPointRepresentationTest.kt new file mode 100644 index 000000000..75a6158d8 --- /dev/null +++ b/src/test/kotlin/ch/kleis/lcaplugin/ui/toolwindow/FloatingPointRepresentationTest.kt @@ -0,0 +1,123 @@ +package ch.kleis.lcaplugin.ui.toolwindow + +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameters +import org.junit.Test +import kotlin.test.assertEquals + +private fun digits(s: String): List { + return s.map { Integer.parseInt(it.toString()) } +} + +@RunWith(Parameterized::class) +class FloatingPointRepresentationTest( + private val value: Double, + private val expected: FloatingPointRepresentation, +) { + companion object { + + @Parameters + @JvmStatic + fun getFloatingPointRepresentationSamples(): Collection> { + return listOf( + arrayOf(0.0012345, FloatingPointRepresentation(true, digits("123"), -3, 3)), + arrayOf(0.012345, FloatingPointRepresentation(true, digits("123"), -2, 3)), + arrayOf(0.12345, FloatingPointRepresentation(true, digits("123"), -1, 3)), + arrayOf(1.2345, FloatingPointRepresentation(true, digits("123"), 0, 3)), + arrayOf(12.345, FloatingPointRepresentation(true, digits("123"), 1, 3)), + arrayOf(123.45, FloatingPointRepresentation(true, digits("123"), 2, 3)), + arrayOf(1234.5, FloatingPointRepresentation(true, digits("123"), 3, 3)), + arrayOf(12345.0, FloatingPointRepresentation(true, digits("123"), 4, 3)), + + arrayOf(0.0, FloatingPointRepresentation(true, digits("000"), 0, 3)), + arrayOf(0.0001, FloatingPointRepresentation(true, digits("100"), -4, 3)), + arrayOf(0.001, FloatingPointRepresentation(true, digits("100"), -3, 3)), + arrayOf(0.01, FloatingPointRepresentation(true, digits("100"), -2, 3)), + arrayOf(0.1, FloatingPointRepresentation(true, digits("100"), -1, 3)), + arrayOf(1.0, FloatingPointRepresentation(true, digits("100"), 0, 3)), + arrayOf(10.0, FloatingPointRepresentation(true, digits("100"), 1, 3)), + arrayOf(100.0, FloatingPointRepresentation(true, digits("100"), 2, 3)), + arrayOf(1000.0, FloatingPointRepresentation(true, digits("100"), 3, 3)), + + arrayOf(0.000999, FloatingPointRepresentation(true, digits("100"), -3, 3)), + arrayOf(0.00999, FloatingPointRepresentation(true, digits("100"), -2, 3)), + arrayOf(0.0999, FloatingPointRepresentation(true, digits("100"), -1, 3)), + arrayOf(0.999, FloatingPointRepresentation(true, digits("100"), 0, 3)), + arrayOf(9.99, FloatingPointRepresentation(true, digits("100"), 1, 3)), + arrayOf(99.9, FloatingPointRepresentation(true, digits("100"), 2, 3)), + arrayOf(999.0, FloatingPointRepresentation(true, digits("100"), 3, 3)), + + arrayOf(0.001001, FloatingPointRepresentation(true, digits("100"), -3, 3)), + arrayOf(0.01001, FloatingPointRepresentation(true, digits("100"), -2, 3)), + arrayOf(0.1001, FloatingPointRepresentation(true, digits("100"), -1, 3)), + arrayOf(1.001, FloatingPointRepresentation(true, digits("100"), 0, 3)), + arrayOf(10.01, FloatingPointRepresentation(true, digits("100"), 1, 3)), + arrayOf(100.1, FloatingPointRepresentation(true, digits("100"), 2, 3)), + arrayOf(1001.0, FloatingPointRepresentation(true, digits("100"), 3, 3)), + + arrayOf(0.0002999, FloatingPointRepresentation(true, digits("300"), -4, 3)), + arrayOf(0.003999, FloatingPointRepresentation(true, digits("400"), -3, 3)), + arrayOf(0.04999, FloatingPointRepresentation(true, digits("500"), -2, 3)), + arrayOf(0.5999, FloatingPointRepresentation(true, digits("600"), -1, 3)), + arrayOf(6.999, FloatingPointRepresentation(true, digits("700"), 0, 3)), + arrayOf(79.99, FloatingPointRepresentation(true, digits("800"), 1, 3)), + arrayOf(899.9, FloatingPointRepresentation(true, digits("900"), 2, 3)), + + arrayOf(-0.0012345, FloatingPointRepresentation(false, digits("123"), -3, 3)), + arrayOf(-0.012345, FloatingPointRepresentation(false, digits("123"), -2, 3)), + arrayOf(-0.12345, FloatingPointRepresentation(false, digits("123"), -1, 3)), + arrayOf(-1.2345, FloatingPointRepresentation(false, digits("123"), 0, 3)), + arrayOf(-12.345, FloatingPointRepresentation(false, digits("123"), 1, 3)), + arrayOf(-123.45, FloatingPointRepresentation(false, digits("123"), 2, 3)), + arrayOf(-1234.5, FloatingPointRepresentation(false, digits("123"), 3, 3)), + arrayOf(-12345.0, FloatingPointRepresentation(false, digits("123"), 4, 3)), + + arrayOf(-0.0, FloatingPointRepresentation(true, digits("000"), 0, 3)), + arrayOf(-0.0001, FloatingPointRepresentation(false, digits("100"), -4, 3)), + arrayOf(-0.001, FloatingPointRepresentation(false, digits("100"), -3, 3)), + arrayOf(-0.01, FloatingPointRepresentation(false, digits("100"), -2, 3)), + arrayOf(-0.1, FloatingPointRepresentation(false, digits("100"), -1, 3)), + arrayOf(-1.0, FloatingPointRepresentation(false, digits("100"), 0, 3)), + arrayOf(-10.0, FloatingPointRepresentation(false, digits("100"), 1, 3)), + arrayOf(-100.0, FloatingPointRepresentation(false, digits("100"), 2, 3)), + arrayOf(-1000.0, FloatingPointRepresentation(false, digits("100"), 3, 3)), + + arrayOf(-0.000999, FloatingPointRepresentation(false, digits("100"), -3, 3)), + arrayOf(-0.00999, FloatingPointRepresentation(false, digits("100"), -2, 3)), + arrayOf(-0.0999, FloatingPointRepresentation(false, digits("100"), -1, 3)), + arrayOf(-0.999, FloatingPointRepresentation(false, digits("100"), 0, 3)), + arrayOf(-9.99, FloatingPointRepresentation(false, digits("100"), 1, 3)), + arrayOf(-99.9, FloatingPointRepresentation(false, digits("100"), 2, 3)), + arrayOf(-999.0, FloatingPointRepresentation(false, digits("100"), 3, 3)), + + arrayOf(-0.001001, FloatingPointRepresentation(false, digits("100"), -3, 3)), + arrayOf(-0.01001, FloatingPointRepresentation(false, digits("100"), -2, 3)), + arrayOf(-0.1001, FloatingPointRepresentation(false, digits("100"), -1, 3)), + arrayOf(-1.001, FloatingPointRepresentation(false, digits("100"), 0, 3)), + arrayOf(-10.01, FloatingPointRepresentation(false, digits("100"), 1, 3)), + arrayOf(-100.1, FloatingPointRepresentation(false, digits("100"), 2, 3)), + arrayOf(-1001.0, FloatingPointRepresentation(false, digits("100"), 3, 3)), + + arrayOf(-0.0002999, FloatingPointRepresentation(false, digits("300"), -4, 3)), + arrayOf(-0.003999, FloatingPointRepresentation(false, digits("400"), -3, 3)), + arrayOf(-0.04999, FloatingPointRepresentation(false, digits("500"), -2, 3)), + arrayOf(-0.5999, FloatingPointRepresentation(false, digits("600"), -1, 3)), + arrayOf(-6.999, FloatingPointRepresentation(false, digits("700"), 0, 3)), + arrayOf(-79.99, FloatingPointRepresentation(false, digits("800"), 1, 3)), + arrayOf(-899.9, FloatingPointRepresentation(false, digits("900"), 2, 3)), + ) + } + + } + + @Test + fun run() { + // when + val actual = FloatingPointRepresentation.of(value, 3) + + // then + assertEquals(expected, actual, "value = $value") + } + +} diff --git a/src/test/kotlin/ch/kleis/lcaplugin/ui/toolwindow/LcaProcessAssessResultTest.kt b/src/test/kotlin/ch/kleis/lcaplugin/ui/toolwindow/LcaProcessAssessResultTest.kt index f84b76414..23e5a929f 100644 --- a/src/test/kotlin/ch/kleis/lcaplugin/ui/toolwindow/LcaProcessAssessResultTest.kt +++ b/src/test/kotlin/ch/kleis/lcaplugin/ui/toolwindow/LcaProcessAssessResultTest.kt @@ -68,7 +68,7 @@ class LcaProcessAssessResultTest { assertThat(html, containsString("carrot")) val text = result.getTransferData(DataFlavor("text/plain;class=java.lang.String")) as String assertThat(text, containsString("item\tquantity\tunit\t[Resource] propanol(air) [kg]")) - assertThat(text, containsString("\ncarrot\t1.0\tg\t0.001\t")) + assertThat(text, containsString("\ncarrot\t1\tg\t1E-3\t")) } }