From 00ad1858822a34090a4032e51bb47a7efcb9da43 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 19 Aug 2024 07:08:59 +0800 Subject: [PATCH] rework decimal lowBoundary() and highBoundary() after discussion on Zulip, and add extensive testing --- .../hl7/fhir/r5/fhirpath/FHIRPathEngine.java | 48 ++++++++++++------- .../org/hl7/fhir/utilities/Utilities.java | 40 +++++++++++----- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java index cc72e3e89..5d1efdcec 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java @@ -3735,20 +3735,20 @@ public class FHIRPathEngine { case LowBoundary: case HighBoundary: { - checkContextContinuous(focus, exp.getFunction().toCode(), exp); + checkContextContinuous(focus, exp.getFunction().toCode(), exp, true); if (paramTypes.size() > 0) { checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); } - if (focus.hasType("decimal") && (focus.hasType("date") || focus.hasType("datetime") || focus.hasType("instant"))) { + if ((focus.hasType("date") || focus.hasType("datetime") || focus.hasType("instant"))) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal, TypeDetails.FP_DateTime); - } else if (focus.hasType("decimal")) { + } else if (focus.hasType("decimal") || focus.hasType("integer")) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); } else { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); } } case Precision: { - checkContextContinuous(focus, exp.getFunction().toCode(), exp); + checkContextContinuous(focus, exp.getFunction().toCode(), exp, false); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); } case hasTemplateIdOf: { @@ -3897,8 +3897,8 @@ public class FHIRPathEngine { } } - private void checkContextContinuous(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { - if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("date") && !focus.hasType("dateTime") && !focus.hasType("time") && !focus.hasType("Quantity")) { + private void checkContextContinuous(TypeDetails focus, String name, ExpressionNode expr, boolean allowInteger) throws PathEngineException { + if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("date") && !focus.hasType("dateTime") && !focus.hasType("time") && !focus.hasType("Quantity") && !(allowInteger && focus.hasType("integer"))) { throw makeException(expr, I18nConstants.FHIRPATH_CONTINUOUS_ONLY, name, focus.describe()); } } @@ -4295,7 +4295,7 @@ public class FHIRPathEngine { if (focus.size() > 1) { throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "lowBoundary", focus.size()); } - int precision = 0; + Integer precision = null; if (expr.getParameters().size() > 0) { List n1 = execute(context, focus, expr.getParameters().get(0), true); if (n1.size() != 1) { @@ -4308,17 +4308,23 @@ public class FHIRPathEngine { List result = new ArrayList(); if (base.hasType("decimal")) { - result.add(new DecimalType(Utilities.lowBoundaryForDecimal(base.primitiveValue(), precision == 0 ? 8 : precision))); + if (precision == null || (precision >= 0 && precision < 17)) { + result.add(new DecimalType(Utilities.lowBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision))); + } + } else if (base.hasType("integer")) { + if (precision == null || (precision >= 0 && precision < 17)) { + result.add(new DecimalType(Utilities.lowBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision))); + } } else if (base.hasType("date")) { - result.add(new DateTimeType(Utilities.lowBoundaryForDate(base.primitiveValue(), precision == 0 ? 10 : precision))); + result.add(new DateTimeType(Utilities.lowBoundaryForDate(base.primitiveValue(), precision == null ? 10 : precision))); } else if (base.hasType("dateTime")) { - result.add(new DateTimeType(Utilities.lowBoundaryForDate(base.primitiveValue(), precision == 0 ? 17 : precision))); + result.add(new DateTimeType(Utilities.lowBoundaryForDate(base.primitiveValue(), precision == null ? 17 : precision))); } else if (base.hasType("time")) { - result.add(new TimeType(Utilities.lowBoundaryForTime(base.primitiveValue(), precision == 0 ? 9 : precision))); + result.add(new TimeType(Utilities.lowBoundaryForTime(base.primitiveValue(), precision == null ? 9 : precision))); } else if (base.hasType("Quantity")) { String value = getNamedValue(base, "value"); Base v = base.copy(); - v.setProperty("value", new DecimalType(Utilities.lowBoundaryForDecimal(value, precision == 0 ? 8 : precision))); + v.setProperty("value", new DecimalType(Utilities.lowBoundaryForDecimal(value, precision == null ? 8 : precision))); result.add(v); } else { makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date"); @@ -4333,7 +4339,7 @@ public class FHIRPathEngine { if (focus.size() > 1) { throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "highBoundary", focus.size()); } - int precision = 0; + Integer precision = null; if (expr.getParameters().size() > 0) { List n1 = execute(context, focus, expr.getParameters().get(0), true); if (n1.size() != 1) { @@ -4346,17 +4352,23 @@ public class FHIRPathEngine { Base base = focus.get(0); List result = new ArrayList(); if (base.hasType("decimal")) { - result.add(new DecimalType(Utilities.highBoundaryForDecimal(base.primitiveValue(), precision == 0 ? 8 : precision))); + if (precision == null || (precision >= 0 && precision < 17)) { + result.add(new DecimalType(Utilities.highBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision))); + } + } else if (base.hasType("integer")) { + if (precision == null || (precision >= 0 && precision < 17)) { + result.add(new DecimalType(Utilities.highBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision))); + } } else if (base.hasType("date")) { - result.add(new DateTimeType(Utilities.highBoundaryForDate(base.primitiveValue(), precision == 0 ? 10 : precision))); + result.add(new DateTimeType(Utilities.highBoundaryForDate(base.primitiveValue(), precision == null ? 10 : precision))); } else if (base.hasType("dateTime")) { - result.add(new DateTimeType(Utilities.highBoundaryForDate(base.primitiveValue(), precision == 0 ? 17 : precision))); + result.add(new DateTimeType(Utilities.highBoundaryForDate(base.primitiveValue(), precision == null ? 17 : precision))); } else if (base.hasType("time")) { - result.add(new TimeType(Utilities.highBoundaryForTime(base.primitiveValue(), precision == 0 ? 9 : precision))); + result.add(new TimeType(Utilities.highBoundaryForTime(base.primitiveValue(), precision == null ? 9 : precision))); } else if (base.hasType("Quantity")) { String value = getNamedValue(base, "value"); Base v = base.copy(); - v.setProperty("value", new DecimalType(Utilities.highBoundaryForDecimal(value, precision == 0 ? 8 : precision))); + v.setProperty("value", new DecimalType(Utilities.highBoundaryForDecimal(value, precision == null ? 8 : precision))); result.add(v); } else { makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date"); diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java index 22f13734e..c56787430 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java @@ -1695,31 +1695,47 @@ public class Utilities { value = value.substring(0, value.indexOf("e")); } if (isZero(value)) { - return applyPrecision("-0.5000000000000000000000000", precision); + return applyPrecision("-0.5000000000000000000000000", precision, true); } else if (value.startsWith("-")) { return "-"+highBoundaryForDecimal(value.substring(1), precision)+(e == null ? "" : e); } else { if (value.contains(".")) { - return applyPrecision(minusOne(value)+"50000000000000000000000000000", precision)+(e == null ? "" : e); + return applyPrecision(minusOne(value)+"50000000000000000000000000000", precision, true)+(e == null ? "" : e); } else { - return applyPrecision(minusOne(value)+".50000000000000000000000000000", precision)+(e == null ? "" : e); + return applyPrecision(minusOne(value)+".50000000000000000000000000000", precision, true)+(e == null ? "" : e); } } } - private static String applyPrecision(String v, int p) { + private static String applyPrecision(String v, int p, boolean down) { + String nv = v; + int dp = -1; + if (nv.contains(".")) { + dp = nv.indexOf("."); + nv = nv.substring(0, dp)+nv.substring(dp+1); + } + String s = null; int d = p - getDecimalPrecision(v); if (d == 0) { - return v; + s = nv; } else if (d > 0) { - return v + padLeft("", '0', d); + s = nv + padLeft("", '0', d); } else { - if (v.charAt(v.length()+d) >= '6') { - return v.substring(0, v.length()+d-1)+((char) (v.charAt(v.length()+d)+1)); + int l = v.length(); + int ld = l+d; + if (dp > -1) { + ld--; + } + if (nv.charAt(ld) >= '5' && !down) { + s = nv.substring(0, ld-1)+((char) (nv.charAt(ld-1)+1)); } else { - return v.substring(0, v.length()+d); + s = nv.substring(0, ld); } } + if (s.endsWith(".")) { + s = s.substring(0, s.length()-1); + } + return dp == -1 || dp >= s.length() ? s : s.substring(0, dp)+"."+s.substring(dp); } private static String minusOne(String value) { @@ -1831,14 +1847,14 @@ public class Utilities { value = value.substring(0, value.indexOf("e")); } if (isZero(value)) { - return applyPrecision("0.50000000000000000000000000000", precision); + return applyPrecision("0.50000000000000000000000000000", precision, false); } else if (value.startsWith("-")) { return "-"+lowBoundaryForDecimal(value.substring(1), precision)+(e == null ? "" : e); } else { if (value.contains(".")) { - return applyPrecision(value+"50000000000000000000000000000", precision)+(e == null ? "" : e); + return applyPrecision(value+"50000000000000000000000000000", precision, false)+(e == null ? "" : e); } else { - return applyPrecision(value+".50000000000000000000000000000", precision)+(e == null ? "" : e); + return applyPrecision(value+".50000000000000000000000000000", precision, false)+(e == null ? "" : e); } } }