mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Signed-off-by: lowi <75674438+lohwasser@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
0df3af36c6
commit
80996f241b
2 changed files with 129 additions and 7 deletions
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,21 +14,31 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.core.model.util
|
||||
|
||||
import kotlin.math.ln
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
object UnitConversions {
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun celsiusToFahrenheit(celsius: Float): Float = (celsius * 1.8F) + 32
|
||||
|
||||
fun Float.toTempString(isFahrenheit: Boolean) = if (isFahrenheit) {
|
||||
val fahrenheit = celsiusToFahrenheit(this)
|
||||
"%.0f°F".format(fahrenheit)
|
||||
} else {
|
||||
"%.0f°C".format(this)
|
||||
/** Formats temperature as a string with the unit suffix. */
|
||||
fun Float.toTempString(isFahrenheit: Boolean): String {
|
||||
val temp = if (isFahrenheit) celsiusToFahrenheit(this) else this
|
||||
val unit = if (isFahrenheit) "F" else "C"
|
||||
|
||||
// Convoluted calculation due to edge case: rounding negative values.
|
||||
// We round the absolute value using roundToInt() (banker's rounding), then reapply the sign so values
|
||||
val absoluteTemp: Float = kotlin.math.abs(temp)
|
||||
val roundedAbsoluteTemp: Int = absoluteTemp.roundToInt()
|
||||
|
||||
val isZero = roundedAbsoluteTemp == 0
|
||||
val isPositive = kotlin.math.sign(temp) > 0
|
||||
val sign: String = if (isPositive || isZero) "" else "-"
|
||||
|
||||
return "$sign$roundedAbsoluteTemp°$unit"
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.core.model.util
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.model.util.UnitConversions.toTempString
|
||||
|
||||
class UnitConversionsTest {
|
||||
|
||||
// Test data: (celsius, isFahrenheit, expected)
|
||||
private val tempTestCases =
|
||||
listOf(
|
||||
// Issue #4150: negative zero should display as "0"
|
||||
Triple(-0.1f, false, "0°C"),
|
||||
Triple(-0.2f, false, "0°C"),
|
||||
Triple(-0.4f, false, "0°C"),
|
||||
Triple(-0.49f, false, "0°C"),
|
||||
// Boundary: -0.5 rounds to -1
|
||||
Triple(-0.5f, false, "-1°C"),
|
||||
Triple(-0.9f, false, "-1°C"),
|
||||
Triple(-1.0f, false, "-1°C"),
|
||||
// Zero and small positives
|
||||
Triple(0.0f, false, "0°C"),
|
||||
Triple(0.1f, false, "0°C"),
|
||||
Triple(0.4f, false, "0°C"),
|
||||
// Typical values
|
||||
Triple(1.0f, false, "1°C"),
|
||||
Triple(20.0f, false, "20°C"),
|
||||
Triple(25.4f, false, "25°C"),
|
||||
Triple(25.5f, false, "26°C"),
|
||||
// Negative
|
||||
Triple(-5.0f, false, "-5°C"),
|
||||
Triple(-10.0f, false, "-10°C"),
|
||||
Triple(-20.4f, false, "-20°C"),
|
||||
// Fahrenheit conversions
|
||||
Triple(0.0f, true, "32°F"),
|
||||
Triple(20.0f, true, "68°F"),
|
||||
Triple(25.0f, true, "77°F"),
|
||||
Triple(100.0f, true, "212°F"),
|
||||
Triple(-40.0f, true, "-40°F"), // -40°C = -40°F
|
||||
// Issue #4150: negative zero in Fahrenheit
|
||||
Triple(-0.1f, true, "32°F"),
|
||||
Triple(-17.78f, true, "0°F"),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `toTempString formats all temperatures correctly`() {
|
||||
tempTestCases.forEach { (celsius, isFahrenheit, expected) ->
|
||||
assertEquals(
|
||||
"Failed for $celsius°C (Fahrenheit=$isFahrenheit)",
|
||||
expected,
|
||||
celsius.toTempString(isFahrenheit),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toTempString handles extreme temperatures`() {
|
||||
assertEquals("100°C", 100.0f.toTempString(false))
|
||||
assertEquals("-40°C", (-40.0f).toTempString(false))
|
||||
assertEquals("-40°F", (-40.0f).toTempString(true))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `celsiusToFahrenheit converts correctly`() {
|
||||
mapOf(
|
||||
0.0f to 32.0f,
|
||||
20.0f to 68.0f,
|
||||
100.0f to 212.0f,
|
||||
-40.0f to -40.0f,
|
||||
).forEach { (celsius, expectedFahrenheit) ->
|
||||
assertEquals(expectedFahrenheit, UnitConversions.celsiusToFahrenheit(celsius), 0.01f)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calculateDewPoint returns expected values`() {
|
||||
// At 100% humidity, dew point equals temperature
|
||||
assertEquals(20.0f, UnitConversions.calculateDewPoint(20.0f, 100.0f), 0.1f)
|
||||
|
||||
// Known reference: 20°C at 60% humidity ≈ 12°C dew point
|
||||
assertEquals(12.0f, UnitConversions.calculateDewPoint(20.0f, 60.0f), 0.5f)
|
||||
|
||||
// Higher humidity = higher dew point
|
||||
val highHumidity = UnitConversions.calculateDewPoint(25.0f, 80.0f)
|
||||
val lowHumidity = UnitConversions.calculateDewPoint(25.0f, 40.0f)
|
||||
assertTrue("Dew point should be higher at higher humidity", highHumidity > lowHumidity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calculateDewPoint handles edge cases`() {
|
||||
// 0% humidity results in NaN (ln(0) = -Infinity, causing invalid calculation)
|
||||
val zeroHumidity = UnitConversions.calculateDewPoint(20.0f, 0.0f)
|
||||
assertTrue("Expected NaN for 0% humidity", zeroHumidity.isNaN())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue