From 89dee1f36131e5adc7bd3b92256757dbd0dafc02 Mon Sep 17 00:00:00 2001 From: Florian Kleedorfer Date: Thu, 24 Jul 2025 23:37:58 +0200 Subject: [PATCH] Use precision of input value for output of Unit.convert() --- CHANGELOG.md | 3 + .../java/io/github/qudtlib/model/Unit.java | 64 +++++++++++-------- .../java/io/github/qudtlib/QudtTests.java | 22 +++---- 3 files changed, 52 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cdcd882..bb440ad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- in `Unit.convert()` (and therfore, `Qudt.convert()`), use the precision (= number of significant digits) of the input + value as the precision of the output value (notable exception: input value `1`, which is interpreted as precision 3). + ## [7.0.0] - 2025-07-22 ### Added diff --git a/qudtlib-model/src/main/java/io/github/qudtlib/model/Unit.java b/qudtlib-model/src/main/java/io/github/qudtlib/model/Unit.java index 0b04908b..fe799869 100644 --- a/qudtlib-model/src/main/java/io/github/qudtlib/model/Unit.java +++ b/qudtlib-model/src/main/java/io/github/qudtlib/model/Unit.java @@ -1,6 +1,7 @@ package io.github.qudtlib.model; import static io.github.qudtlib.nodedef.Builder.buildSet; +import static java.math.BigDecimal.ONE; import io.github.qudtlib.exception.InconvertibleQuantitiesException; import io.github.qudtlib.nodedef.Builder; @@ -395,16 +396,21 @@ public BigDecimal convert(BigDecimal value, Unit toUnit, QuantityKind quantityKi } BigDecimal fromOffset = ignoreOffset ? BigDecimal.ZERO : this.getConversionOffset().orElse(BigDecimal.ZERO); - BigDecimal fromMultiplier = this.getConversionMultiplier().orElse(BigDecimal.ONE); + BigDecimal fromMultiplier = this.getConversionMultiplier().orElse(ONE); BigDecimal toOffset = ignoreOffset ? BigDecimal.ZERO : toUnit.getConversionOffset().orElse(BigDecimal.ZERO); - BigDecimal toMultiplier = toUnit.getConversionMultiplier().orElse(BigDecimal.ONE); - return value.add(fromOffset) - .multiply(fromMultiplier, MathContext.DECIMAL128) - .divide(toMultiplier, MathContext.DECIMAL128) - .subtract(toOffset); + BigDecimal toMultiplier = toUnit.getConversionMultiplier().orElse(ONE); + BigDecimal result = + value.add(fromOffset) + .multiply(fromMultiplier, MathContext.DECIMAL128) + .divide(toMultiplier, MathContext.DECIMAL128) + .subtract(toOffset); + int resultPrecision = Math.min(BigDecimal.ONE.equals(value) ? 3 : value.precision(), 34); + + MathContext resultContext = new MathContext(resultPrecision); + return result.round(resultContext); } /** @@ -417,7 +423,7 @@ public BigDecimal convert(BigDecimal value, Unit toUnit, QuantityKind quantityKi */ public BigDecimal getConversionMultiplier(Unit toUnit) { if (this.equals(toUnit)) { - return BigDecimal.ONE; + return ONE; } if (this.conversionOffsetDiffers(toUnit)) { throw new IllegalArgumentException( @@ -427,25 +433,31 @@ public BigDecimal getConversionMultiplier(Unit toUnit) { } Optional fromMultiplier = this.getConversionMultiplier(); Optional toMultiplier = toUnit.getConversionMultiplier(); - return fromMultiplier - .map( - from -> - toMultiplier - .map(to -> from.divide(to, MathContext.DECIMAL128)) - .orElse(null)) - .orElseThrow( - () -> - new InconvertibleQuantitiesException( - String.format( - "Cannot convert %s(%s) to %s(%s)", - this.getIriAbbreviated(), - this.getConversionMultiplier().isEmpty() - ? "no multiplier" - : "has multiplier", - toUnit.getIriAbbreviated(), - toUnit.getConversionMultiplier().isEmpty() - ? "no multiplier" - : "has multiplier"))); + BigDecimal result = + fromMultiplier + .map( + from -> + toMultiplier + .map(to -> from.divide(to, MathContext.DECIMAL128)) + .orElse(null)) + .orElseThrow( + () -> + new InconvertibleQuantitiesException( + String.format( + "Cannot convert %s(%s) to %s(%s)", + this.getIriAbbreviated(), + this.getConversionMultiplier().isEmpty() + ? "no multiplier" + : "has multiplier", + toUnit.getIriAbbreviated(), + toUnit.getConversionMultiplier().isEmpty() + ? "no multiplier" + : "has multiplier"))); + int precision = Math.min(34, result.precision()); + return result.round( + new MathContext( + precision)); // TODO: when units know about their precision we can improve + // this } public boolean conversionOffsetDiffers(Unit other) { diff --git a/qudtlib-test/src/test/java/io/github/qudtlib/QudtTests.java b/qudtlib-test/src/test/java/io/github/qudtlib/QudtTests.java index 1a8ef19d..4756ad9b 100644 --- a/qudtlib-test/src/test/java/io/github/qudtlib/QudtTests.java +++ b/qudtlib-test/src/test/java/io/github/qudtlib/QudtTests.java @@ -489,21 +489,19 @@ public void testInconvertible() { @Test public void testConvert_L_to_GAL_US() { - BigDecimal converted = Qudt.convert(BigDecimal.ONE, Qudt.Units.L, Qudt.Units.GAL_US); - MatcherAssert.assertThat( - converted, - Matchers.comparesEqualTo(new BigDecimal("0.2641720523581484153798999216091625"))); + BigDecimal converted = Qudt.convert(new BigDecimal(1.00), Qudt.Units.L, Qudt.Units.GAL_US); + MatcherAssert.assertThat(converted, Matchers.comparesEqualTo(new BigDecimal("0.264"))); } @Test public void testConvert_Celsius_to_Fahrenheit() { QuantityValue celsius100 = - new QuantityValue(new BigDecimal("100"), Qudt.unitFromLocalnameRequired("DEG_C")); + new QuantityValue( + new BigDecimal("100.00"), Qudt.unitFromLocalnameRequired("DEG_C")); QuantityValue fahrenheit = Qudt.convert(celsius100, Qudt.unitIriFromLocalname("DEG_F")); Assertions.assertNotNull(fahrenheit); MatcherAssert.assertThat( - fahrenheit.getValue(), - Matchers.comparesEqualTo(new BigDecimal("211.9999999999999999999999999999999"))); + fahrenheit.getValue(), Matchers.comparesEqualTo(new BigDecimal("212"))); Assertions.assertEquals(Qudt.unitIriFromLocalname("DEG_F"), fahrenheit.getUnit().getIri()); } @@ -533,14 +531,14 @@ public void testConvert_Celsius_to_Kelvin_tempdiff() { public void testConvert_Celsius_to_Fahrenheit_2() { MatcherAssert.assertThat( Qudt.convert(new BigDecimal("100"), Units.DEG_C, Units.DEG_F), - Matchers.comparesEqualTo(new BigDecimal("211.9999999999999999999999999999999"))); + Matchers.comparesEqualTo(new BigDecimal("212"))); } @Test public void testConvert_Fahrenheit_to_Celsius() { MatcherAssert.assertThat( Qudt.convert(new BigDecimal("100"), Units.DEG_F, Units.DEG_C), - Matchers.comparesEqualTo(new BigDecimal("37.7777777777777777777777777777778"))); + Matchers.comparesEqualTo(new BigDecimal("37.8"))); } @Test @@ -601,9 +599,11 @@ public void testConvert_FemtoGM_to_KiloGM() { @Test public void testConvert_Metric_to_Imperial() { BigDecimal converted = Qudt.convert(BigDecimal.ONE, Qudt.Units.LB, Qudt.Units.KiloGM); - MatcherAssert.assertThat(converted, Matchers.comparesEqualTo(new BigDecimal("0.45359237"))); + MatcherAssert.assertThat(converted, Matchers.comparesEqualTo(new BigDecimal("0.454"))); + converted = Qudt.convert(new BigDecimal("1.0"), Qudt.Units.LB, Qudt.Units.KiloGM); + MatcherAssert.assertThat(converted, Matchers.comparesEqualTo(new BigDecimal("0.45"))); converted = Qudt.convert(BigDecimal.ONE, Qudt.Units.BTU_IT__PER__LB, Qudt.Units.J__PER__GM); - MatcherAssert.assertThat(converted, Matchers.comparesEqualTo(new BigDecimal("2.326"))); + MatcherAssert.assertThat(converted, Matchers.comparesEqualTo(new BigDecimal("2.33"))); } @Test