You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by sl...@apache.org on 2023/03/27 17:38:42 UTC

[daffodil] branch main updated: Ensure all primitives use textNumberPattern and infinfity/NaN correctly

This is an automated email from the ASF dual-hosted git repository.

slawrence pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/daffodil.git


The following commit(s) were added to refs/heads/main by this push:
     new f8a786734 Ensure all primitives use textNumberPattern and infinfity/NaN correctly
f8a786734 is described below

commit f8a786734237cfb6469e76dcd3c1a0d015ba114c
Author: Steve Lawrence <sl...@apache.org>
AuthorDate: Wed Mar 8 11:40:28 2023 -0500

    Ensure all primitives use textNumberPattern and infinfity/NaN correctly
    
    Currently, if the primitive type is an integer then text number parsing
    disallows parsing decimal points, even if the pattern contains a decimal
    point. Instead, when parsing integers, we should allow decimals as long
    the fractional part is zero. And when unparsing, we should unparse a
    decimal point with a zero fractional part according to the pattern.
    
    This changes the behavior so integer parsing uses the same DecimalFormat
    configuration as non-integer parsing (i.e. decimals are allowed), but we
    throw a parse error if the fractional part that was parsed is non-zero.
    This also means that unparsing integers now outputs decimal points
    according to the pattern.
    
    Additionally, if textNumberCheckPolicy is strict, we enable ICU
    setDecimalPatternMatchRequired to true so that we allow or disallow
    decimal points in the data depending on if the pattern does or does not
    have a decimal point. Note that lax parsing always allows decimal points
    regardless of the pattern. For this reason, we now always require the
    grouping/decimal separator DFDL properties in lax mode.
    
    One bug was discovered in ICU (ICU-22303) where if we require the
    decimal point due to strict mode enabled, then ICU never parses the
    infinity/NaN representation. A workaround is added to manually check for
    these representations until this bug is fixed. ICU unit tests are also
    added which should fail if ICU fixes this bug so we can remove this
    workaround.
    
    Make sure we always specify infinity and NaN representations from the
    DFDL schema for all primitives, not just for xs:double/xs:float. There
    is no way to disable infinity/NaN ICU parsing, so when if we do not
    specify these values ICU just uses the locale values, which could lead
    to unwanted locale specific behavior. Related, this modifies NodeInfo
    types so that fromNumber fails for types that do not support
    infinity/NaN (i.e. everything except Double/Float) and creates a parse
    error.
    
    Modifies virtual decimal logic to ensure we handle cases for numbers
    that do not fit in a Long (should work) or contain decimal points
    (should be a parse error).
    
    Tests are updated so if they want to differentiate between int and
    decimal depending on if a decimal exists in the data, then they must
    specify a pattern with or without a decimal and enable strict mode--lax
    mode allows a decimal regardless of type so cannot differentiate the
    types.
    
    DAFFODIL-2158
---
 .../grammar/primitives/PrimitivesTextNumber.scala  |  29 ++--
 .../core/grammar/primitives/PrimitivesZoned.scala  |   1 -
 .../ConvertTextStandardNumberUnparser.scala        |   2 +-
 .../runtime1/ConvertZonedNumberUnparser.scala      |   4 +-
 .../apache/daffodil/runtime1/dpath/NodeInfo.scala  |  20 ++-
 .../runtime1/processors/EvTextNumber.scala         |  13 +-
 .../parsers/ConvertTextStandardNumberParser.scala  | 168 ++++++++++++++-------
 .../runtime1/processors/input/TestICU.scala        |  73 ++++++++-
 .../test-suite/tresys-contributed/BG.dfdl.xsd      |   2 +-
 .../test-suite/tresys-contributed/BG.tdml          |   4 +-
 .../text_number_props/TextNumberProps.tdml         | 165 ++++++++++++++++++--
 .../org/apache/daffodil/section13/zoned/pv.tdml    |  18 +++
 .../choice_groups/ChoiceGroupInitiatedContent.tdml |  24 +--
 .../text_number_props/TestTextNumberProps.scala    |  34 +++++
 .../apache/daffodil/section13/zoned/TestPV.scala   |   2 +
 15 files changed, 436 insertions(+), 123 deletions(-)

diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesTextNumber.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesTextNumber.scala
index 0d9aa07bb..648d656b8 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesTextNumber.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesTextNumber.scala
@@ -23,6 +23,7 @@ import org.apache.daffodil.core.grammar.Terminal
 import org.apache.daffodil.lib.api.WarnID
 import org.apache.daffodil.lib.cookers.EntityReplacer
 import org.apache.daffodil.lib.exceptions.Assert
+import org.apache.daffodil.lib.schema.annotation.props.gen.TextNumberCheckPolicy
 import org.apache.daffodil.lib.schema.annotation.props.gen.TextNumberRounding
 import org.apache.daffodil.lib.util.Maybe
 import org.apache.daffodil.lib.util.Maybe._
@@ -559,24 +560,17 @@ case class ConvertTextStandardNumberPrim(e: ElementBase)
         case TextNumberRounding.Pattern => (MaybeDouble.Nope, Nope)
       }
 
-    val (infRep, nanRep) = e.primType match {
-      case PrimType.Double | PrimType.Float =>
-        (One(e.textStandardInfinityRep), One(e.textStandardNaNRep))
-      case _ => (Nope, Nope)
-    }
-
-    // If the pattern contains any of these characters, we need to set both
-    // group and decimal separators, even if the pattern doesn't contain the
-    // associated character. This is because even when the pattern does not
-    // contain the grouping/decimal separators, ICU stills seems to take the
-    // separators into account. And since ICU provides default values based on
-    // locales, not setting them can cause subtle locale related bugs. We must
-    // also require the separators if the prim type is not an integer type,
-    // since ICU will use them even if the pattern does not specify them.
+    // If the pattern contains any of these characters, we need to set both group and decimal
+    // separators, even if the pattern doesn't contain the associated character. This is because
+    // even when the pattern does not contain the grouping/decimal separators, ICU stills seems
+    // to take the separators into account. And since ICU provides default values based on
+    // locales, not setting them can cause subtle locale related bugs. We also must required
+    // separators when parsing is lax, which parses decimals and grouping separators regardless
+    // of the pattern.
     val requireDecGroupSeps =
       patternWithoutEscapedChars.contains(",") || patternWithoutEscapedChars.contains(".") ||
         patternWithoutEscapedChars.contains("E") || patternWithoutEscapedChars.contains("@") ||
-        !primNumeric.isInteger
+        (e.textNumberCheckPolicy eq TextNumberCheckPolicy.Lax)
 
     val decSep =
       if (requireDecGroupSeps) {
@@ -597,15 +591,14 @@ case class ConvertTextStandardNumberPrim(e: ElementBase)
       decSep,
       groupSep,
       One(e.textStandardExponentRepEv),
-      infRep,
-      nanRep,
+      One(e.textStandardInfinityRep),
+      One(e.textStandardNaNRep),
       e.textNumberCheckPolicy,
       runtimePattern,
       e.textNumberRounding,
       roundingMode,
       roundingIncrement,
       zeroRepsRaw,
-      primNumeric.isInteger,
       e.primType,
     )
     ev.compile(tunable)
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesZoned.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesZoned.scala
index 2160eff15..d47694f08 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesZoned.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesZoned.scala
@@ -179,7 +179,6 @@ case class ConvertZonedNumberPrim(e: ElementBase)
       roundingMode,
       roundingIncrement,
       Nil,
-      isInt = true,
       e.primType,
     )
     ev.compile(tunable)
diff --git a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/unparsers/runtime1/ConvertTextStandardNumberUnparser.scala b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/unparsers/runtime1/ConvertTextStandardNumberUnparser.scala
index c515c18cc..987ea248c 100644
--- a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/unparsers/runtime1/ConvertTextStandardNumberUnparser.scala
+++ b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/unparsers/runtime1/ConvertTextStandardNumberUnparser.scala
@@ -59,7 +59,7 @@ case class ConvertTextNumberUnparser(
   override def unparse(state: UState): Unit = {
 
     val node = state.currentInfosetNode.asSimple
-    val value = node.dataValue.getAnyRef
+    val value = node.dataValue.getNumber
 
     // The type of value should have the type of S, but type erasure makes this
     // difficult to assert. Could probably check this with TypeTags or Manifest
diff --git a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/unparsers/runtime1/ConvertZonedNumberUnparser.scala b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/unparsers/runtime1/ConvertZonedNumberUnparser.scala
index 121a7ddf0..6b985f764 100644
--- a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/unparsers/runtime1/ConvertZonedNumberUnparser.scala
+++ b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/unparsers/runtime1/ConvertZonedNumberUnparser.scala
@@ -64,9 +64,9 @@ case class ConvertZonedNumberUnparser(
     // if we find this is not the case. Want something akin to:
     // Assert.invariant(value.isInstanceOf[S])
 
-    val valueAsAnyRef = node.dataValue.getAnyRef
+    val number = node.dataValue.getNumber
 
-    val scaledNum = this.applyTextDecimalVirtualPointForUnparse(valueAsAnyRef)
+    val scaledNum = this.applyTextDecimalVirtualPointForUnparse(number)
 
     val value = scaledNum.toString
 
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/NodeInfo.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/NodeInfo.scala
index 8e8fa944a..a844a063a 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/NodeInfo.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/NodeInfo.scala
@@ -704,7 +704,13 @@ object NodeInfo extends Enum {
       type Kind = DecimalKind
       protected override def fromString(s: String): DataValueBigDecimal = new JBigDecimal(s)
       protected override def fromNumberNoCheck(n: Number): DataValueBigDecimal = asBigDecimal(n)
-      override def isValid(n: Number): Boolean = true
+      override def isValid(n: Number): Boolean = {
+        n match {
+          case d: JDouble if d.isInfinite || d.isNaN => false
+          case f: JFloat if f.isInfinite || f.isNaN => false
+          case _ => true
+        }
+      }
 
       override val width: MaybeInt = MaybeInt.Nope
 
@@ -720,7 +726,13 @@ object NodeInfo extends Enum {
       type Kind = IntegerKind
       protected override def fromString(s: String): DataValueBigInt = new JBigInt(s)
       protected override def fromNumberNoCheck(n: Number): DataValueBigInt = asBigInt(n)
-      override def isValid(n: Number): Boolean = true
+      override def isValid(n: Number): Boolean = {
+        n match {
+          case d: JDouble if d.isInfinite || d.isNaN => false
+          case f: JFloat if f.isInfinite || f.isNaN => false
+          case _ => true
+        }
+      }
 
       override val width: MaybeInt = MaybeInt.Nope
 
@@ -795,6 +807,8 @@ object NodeInfo extends Enum {
       def isValid(n: Number): Boolean = n match {
         case bd: JBigDecimal => bd.signum >= 0
         case bi: JBigInt => bi.signum >= 0
+        case d: JDouble if d.isInfinite || d.isNaN => false
+        case f: JFloat if f.isInfinite || f.isNaN => false
         case _ => n.longValue >= 0
       }
 
@@ -815,6 +829,8 @@ object NodeInfo extends Enum {
       def isValid(n: Number): Boolean = n match {
         case bd: JBigDecimal => bd.signum >= 0 && bd.compareTo(maxBD) <= 0
         case bi: JBigInt => bi.signum >= 0 && bi.compareTo(max) <= 0
+        case d: JDouble if d.isInfinite || d.isNaN => false
+        case f: JFloat if f.isInfinite || f.isNaN => false
         case _ => n.longValue >= 0
       }
       val max = new JBigInt(1, scala.Array.fill(8)(0xff.toByte))
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/EvTextNumber.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/EvTextNumber.scala
index a425c2ae6..d4de6012f 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/EvTextNumber.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/EvTextNumber.scala
@@ -81,7 +81,6 @@ class TextNumberFormatEv(
   roundingMode: Maybe[TextNumberRoundingMode],
   roundingIncrement: MaybeDouble,
   zeroRepsRaw: List[String],
-  isInt: Boolean,
   primType: PrimType,
 ) extends Evaluatable[DecimalFormat](tci)
   with InfosetCachedEvaluatable[DecimalFormat] {
@@ -161,6 +160,11 @@ class TextNumberFormatEv(
       case TextNumberCheckPolicy.Lax => false
     }
     df.setParseStrict(cp)
+    // if strict mode is enabled, we also enable setDecimalPatternMatchRequired. This says that
+    // if a decimal point is in the pattern, then the data must contain a decimal point. It also
+    // says the reverse, that if a decimal point is not in the pattern, then the data cannot
+    // contain a decimal point.
+    df.setDecimalPatternMatchRequired(cp)
 
     rounding match {
       case TextNumberRounding.Pattern => {
@@ -182,13 +186,6 @@ class TextNumberFormatEv(
       }
     }
 
-    if (isInt) {
-      df.setMaximumFractionDigits(0)
-      df.setDecimalSeparatorAlwaysShown(false)
-      df.setParseIntegerOnly(true)
-      df.setParseBigDecimal(false)
-    }
-
     df
   }
 
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/ConvertTextStandardNumberParser.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/ConvertTextStandardNumberParser.scala
index f1b1da53d..e5c05a5ca 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/ConvertTextStandardNumberParser.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/ConvertTextStandardNumberParser.scala
@@ -20,6 +20,7 @@ package org.apache.daffodil.runtime1.processors.parsers
 import java.lang.{ Double => JDouble, Float => JFloat, Long => JLong, Number => JNumber }
 import java.math.MathContext
 import java.math.{ BigDecimal => JBigDecimal }
+import java.math.{ BigInteger => JBigInteger }
 import java.text.ParsePosition
 import scala.util.matching.Regex
 
@@ -61,37 +62,46 @@ trait TextDecimalVirtualPointMixin {
   final protected lazy val virtualPointScaleFactor =
     scala.math.pow(10.0, textDecimalVirtualPoint)
 
-  final protected def applyTextDecimalVirtualPointForParse(num1: JNumber): JNumber = {
-    if (textDecimalVirtualPoint == 0) num1
+  final protected def applyTextDecimalVirtualPointForParse(num: JNumber): JNumber = {
+    if (textDecimalVirtualPoint == 0) num
     else {
       // scale for virtual decimal point
-      val scaledNum: JNumber = num1 match {
-        // Empirically, in our test suite, we do get Long back here, so the runtime sometimes represents small integer
-        // (or possibly even smaller decimal numbers with no fraction part) as Long.
+      val scaledNum: JNumber = num match {
         case l: JLong => {
+          // For integers that can fit in a long, ICU will always return a long
           val scaled = JBigDecimal.valueOf(l).scaleByPowerOfTen(-textDecimalVirtualPoint)
           if (textDecimalVirtualPoint < 0) scaled.toBigIntegerExact()
           else scaled
         }
-        case bd: JBigDecimal => bd.scaleByPowerOfTen(-textDecimalVirtualPoint)
-        case f: JFloat => (f / virtualPointScaleFactor).toFloat
-        case d: JDouble => (d / virtualPointScaleFactor).toDouble
+        case bi: JBigInteger => {
+          // For numbers that cannot fit into a long but do not have a decimal, ICU will return
+          // a BigDecimal which we converted to a BigInteger. Convert back to a BigDecimal so it
+          // can be scaled
+          val bd = new JBigDecimal(bi)
+          bd.scaleByPowerOfTen(-textDecimalVirtualPoint)
+        }
+        case d: JDouble => {
+          // ICU only returns doubles if they are infinity/NaN, these cannot be scaled
+          Assert.invariant(d.isNaN || d.isInfinite)
+          d
+        }
         // $COVERAGE-OFF$
-        case _ => badType(num1)
+        case _ => {
+          // ICU returns either Long, Double (for inf/nan) or BigDecimal. BigDecimal should have
+          // been converted to BigInteger if possible, and if not it should have created a PE
+          // since we have a virtual point. If we see any other types, it means ICU returned
+          // something unexpected or we didn't throw a PE correctly.
+          Assert.invariantFailed(s"""
+            |Number cannot be scaled for virtual decimal point,
+            |expected integer type but got ${num.getClass.getSimpleName}.
+            """.stripMargin)
+        }
         // $COVERAGE-ON$
       }
       scaledNum
     }
   }
 
-  // $COVERAGE-OFF$
-  private def badType(num1: AnyRef) = {
-    Assert.invariantFailed(s"""Number cannot be scaled for virtual decimal point,
-         |because it is not a decimal, float, or double.
-         |The type is ${num1.getClass.getSimpleName}.""".stripMargin)
-  }
-  // $COVERAGE-ON$
-
   /**
    * Always creates an integer from a JFloat, JDouble, or JBigDecimal
    * by scaling the argument by the virtual decimal point scaling factor.
@@ -103,11 +113,11 @@ trait TextDecimalVirtualPointMixin {
    *
    * Floating point NaNs and Infinities are tolerated. (No scaling occurs.)
    *
-   * @param valueAsAnyRef value to be scaled. Should be a JNumber. Aborts otherwise.
+   * @param value value to be scaled
    * @return a JNumber of the same concrete type as the argument.
    */
-  final protected def applyTextDecimalVirtualPointForUnparse(valueAsAnyRef: AnyRef): JNumber = {
-    val res: JNumber = valueAsAnyRef match {
+  final protected def applyTextDecimalVirtualPointForUnparse(value: JNumber): JNumber = {
+    val res: JNumber = value match {
       case jn: JNumber if (textDecimalVirtualPoint == 0) => jn
       // This is not perfectly symmetrical with the parse side equivalent.
       // Empirically in our test suite, we do not see JLong here.
@@ -127,16 +137,20 @@ trait TextDecimalVirtualPointMixin {
       case f: JFloat => (f * virtualPointScaleFactor).round.toFloat
       case d: JDouble if d.isNaN || d.isInfinite => d
       case d: JDouble => (d * virtualPointScaleFactor).round.toDouble
-      case bd: JBigDecimal =>
+      case bd: JBigDecimal => {
         bd.scaleByPowerOfTen(textDecimalVirtualPoint).round(MathContext.UNLIMITED)
-      case n: JNumber =>
-        // $COVERAGE-OFF$ // both badType and the next case are coverage-off
-        badType(n)
-      case _ => Assert.invariantFailed("Not a JNumber")
+      }
+      // $COVERAGE-OFF$
+      case _ => {
+        Assert.invariantFailed(s"""
+          |Number cannot be unscaled for virtual decimal point,
+          |expected decimal, float, or double, but got
+          |${value.getClass.getSimpleName}.""".stripMargin)
+      }
       // $COVERAGE-ON$
     }
     // Result type is same as argument type.
-    Assert.invariant(res.getClass == valueAsAnyRef.getClass)
+    Assert.invariant(res.getClass == value.getClass)
     res
   }
 }
@@ -171,47 +185,91 @@ case class ConvertTextStandardNumberParser(
       case Some(_) => primNumeric.fromNumber(0)
       case None => {
         val df = textNumberFormatEv.evaluate(start)
-        val strCheckPolicy = if (df.isParseStrict) str else str.trim
+        val strToParse = if (df.isParseStrict) str else str.trim
         val pos = new ParsePosition(0)
-        val icuNum: Number = df.parse(strCheckPolicy, pos) match {
+        val icuNum: JNumber = df.parse(strToParse, pos) match {
           case null => {
-            PE(
-              start,
-              "Unable to parse %s from text: %s",
-              context.optPrimType.get.globalQName,
-              str,
-            )
-            return
+            val infNaN: JDouble =
+              if (df.isDecimalPatternMatchRequired) {
+                // ICU failed to parse. But there is a bug in ICU4J (ICU-22303) that if there is
+                // a decimal in the pattern and we've set that decimal to be required (due to
+                // strict mode), then it will fail to parse Inf/NaN representations. As a
+                // workaround, we clone the DecimalFormat, disable requiring the decimal, and
+                // reparse. We only accept successful Inf/NaN parses though--everything else is
+                // considered a parse error since it meant the decimal point was missing or
+                // wasn't either inf/nan or a valid number. If ICU fixes this bug, we should
+                // remove this infNan variable and its use, as it is likely pretty expensive to
+                // clone, change a setting, and reparse. Fortunately, it is only in the error
+                // case of strict parsing so should be rare.
+                pos.setIndex(0)
+                val newDF = df.clone().asInstanceOf[DecimalFormat]
+                newDF.setDecimalPatternMatchRequired(false)
+                newDF.parse(strToParse, pos) match {
+                  case d: JDouble => {
+                    Assert.invariant(d.isNaN || d.isInfinite)
+                    d
+                  }
+                  case _ => null
+                }
+              } else {
+                null
+              }
+
+            if (infNaN != null) {
+              infNaN
+            } else {
+              PE(
+                start,
+                "Unable to parse %s from text: %s",
+                context.optPrimType.get.globalQName,
+                str,
+              )
+              return
+            }
           }
-          case d: JDouble if primNumeric.isInteger => {
-            // If ICU returns a Double when only integers are expected, it means the
-            // string must have been NaN, -Infinity, or Infinity using the locales
-            // default symbols. There does not seem to be a way to disable this even
-            // with setParseBigDecimal to false and setParseIntegerOnly set to true.
-            // So just create the same PE as if it failed to parse it, which is what
-            // we really want ICU to do
+          case d: JDouble => {
+            // ICU returns a Double only if it parsed NaN, Infinity, or -Infinity. We will later
+            // pass this value in primNumber.fromNumber, which will fail if the primitive type
+            // does not allow NaN/Infinity
             Assert.invariant(d.isNaN || d.isInfinite)
-            PE(
-              start,
-              "Unable to parse %s from text: %s",
-              context.optPrimType.get.globalQName,
-              str,
-            )
-            return
+            d
           }
           case bd: ICUBigDecimal => {
-            // sometimes ICU will return their own custom BigDecimal, even if the
-            // value could be represented as a BigInteger. We only want Java types,
-            // so detect this and convert it to the appropriate type
+            // ICU will return their own custom BigDecimal if the value cannot fit in a Long and
+            // isn't infinity/NaN. We only want Java types, so detect this and convert it to the
+            // appropriate type. Additionally, due to ICU lax parsing, ICU could successfully
+            // parse something with a non-zero fractional part even if the pattern does not
+            // specify a decimal. So in cases where decimals are not allowed (e.g. integer
+            // primitives, virtual decimal points), we create a PE.
+
+            val fractionalPartMustBeZero = primNumeric.isInteger || textDecimalVirtualPoint > 0
+
             if (bd.scale == 0) bd.unscaledValue
-            else bd.toBigDecimal
+            else if (!fractionalPartMustBeZero) {
+              bd.toBigDecimal
+            } else {
+              PE(
+                start,
+                "Unable to parse %s from text: %s",
+                context.optPrimType.get.globalQName,
+                str,
+              )
+              return
+            }
+          }
+          case l: JLong => l
+          // $COVERAGE-OFF$
+          case num: JNumber => {
+            Assert.invariantFailed(
+              "ICU returned an unexpected type. Expected either Double, ICU BigDecimal, or Long, but got " + num.getClass.getName,
+            )
           }
-          case num: Number => num
+          // $COVERAGE-ON$
         }
 
         // Verify that what was parsed was what was passed exactly in byte count.
         // Use pos to verify all characters consumed & check for errors!
-        if (pos.getIndex != strCheckPolicy.length) {
+        if (pos.getIndex != strToParse.length) {
           val isValid =
             if (df.getPadPosition == DecimalFormat.PAD_AFTER_SUFFIX) {
               // If the DecimalFormat pad position is PAD_AFTER_SUFFIX, ICU
diff --git a/daffodil-runtime1/src/test/scala/org/apache/daffodil/runtime1/processors/input/TestICU.scala b/daffodil-runtime1/src/test/scala/org/apache/daffodil/runtime1/processors/input/TestICU.scala
index 5e5739dec..647404c71 100644
--- a/daffodil-runtime1/src/test/scala/org/apache/daffodil/runtime1/processors/input/TestICU.scala
+++ b/daffodil-runtime1/src/test/scala/org/apache/daffodil/runtime1/processors/input/TestICU.scala
@@ -17,6 +17,8 @@
 
 package org.apache.daffodil.runtime1.processors.input
 
+import java.lang.{ Double => JDouble }
+import java.lang.{ Long => JLong }
 import java.text.ParsePosition
 
 import org.apache.daffodil.lib.calendar.TextCalendarConstants
@@ -30,6 +32,8 @@ import org.junit.Test
 
 class TestICU {
 
+  def pp = new ParsePosition(0)
+
   /*
    * This test is to ensure the reliability of ICU's fractional seconds. ICU
    * throws an exception once you get about ~21 fractional seconds, and it
@@ -83,7 +87,7 @@ class TestICU {
     dfs.setNaN("NaN")
     dfs.setExponentSeparator("x10^")
     val df = new DecimalFormat("000.0#E0", dfs)
-    val posInf = java.lang.Double.POSITIVE_INFINITY
+    val posInf = JDouble.POSITIVE_INFINITY
     val str = df.format(posInf)
     // assertEquals("INFx10^0", str)
     assertEquals("INF", str)
@@ -95,7 +99,7 @@ class TestICU {
     dfs.setNaN("NaN")
     dfs.setExponentSeparator("x10^")
     val df = new DecimalFormat("000.0#E0", dfs)
-    val negInf = java.lang.Double.NEGATIVE_INFINITY
+    val negInf = JDouble.NEGATIVE_INFINITY
     val str = df.format(negInf)
     // assertEquals("-INFx10^0", str)
     assertEquals("-INF", str)
@@ -107,7 +111,7 @@ class TestICU {
     dfs.setNaN("NaN")
     dfs.setExponentSeparator("x10^")
     val df = new DecimalFormat("000.0#E0", dfs)
-    val nan = java.lang.Double.NaN
+    val nan = JDouble.NaN
     val str = df.format(nan)
     // assertEquals("NaNx10^0", str)
     assertEquals("NaN", str)
@@ -203,16 +207,73 @@ class TestICU {
     val ppNaN = new ParsePosition(0)
     val numNaN = df.parse(dfs.getNaN, ppNaN)
     assertTrue(numNaN.isInstanceOf[Double])
-    assertEquals(java.lang.Double.NaN, numNaN.doubleValue, 0.0)
+    assertEquals(JDouble.NaN, numNaN.doubleValue, 0.0)
 
     val ppInf = new ParsePosition(0)
     val numInf = df.parse(dfs.getInfinity, ppInf)
     assertTrue(numInf.isInstanceOf[Double])
-    assertEquals(java.lang.Double.POSITIVE_INFINITY, numInf.doubleValue, 0.0)
+    assertEquals(JDouble.POSITIVE_INFINITY, numInf.doubleValue, 0.0)
 
     val ppNInf = new ParsePosition(0)
     val numNInf = df.parse(dfs.getMinusSign + dfs.getInfinity, ppNInf)
     assertTrue(numNInf.isInstanceOf[Double])
-    assertEquals(java.lang.Double.NEGATIVE_INFINITY, numNInf.doubleValue, 0.0)
+    assertEquals(JDouble.NEGATIVE_INFINITY, numNInf.doubleValue, 0.0)
   }
+
+  // shows that with parseStrict and decimalPatternMatch required, that ICU requires or
+  // disallows a decimal point in the data based on whether or not a decimal point appears in
+  // the pattern. Also shows ICU failing to parse infinity/nan when decimalPatternMatchRequired
+  // is true and the pattern contains a decimal.
+  @Test def test_decimalPatternMatchRequired(): Unit = {
+    val dfs = new DecimalFormatSymbols(java.util.Locale.US)
+
+    val df = new DecimalFormat("", dfs)
+    df.setDecimalPatternMatchRequired(true)
+    df.setParseStrict(true)
+
+    df.applyPattern("0.0")
+
+    assertEquals(JLong.valueOf(1), df.parse("1.0", pp))
+    assertEquals(null, df.parse("1", pp))
+    assertEquals(null, df.parse(dfs.getInfinity, pp)) // see ICU-22303
+    assertEquals(null, df.parse(dfs.getNaN, pp)) // see ICU-22303
+
+    assertEquals("1.0", df.format(1L))
+    assertEquals(dfs.getInfinity, df.format(JDouble.POSITIVE_INFINITY))
+    assertEquals(dfs.getNaN, df.format(JDouble.NaN))
+
+    df.applyPattern("0")
+
+    assertEquals(null, df.parse("1.0", pp))
+    assertEquals(JLong.valueOf(1), df.parse("1", pp))
+    assertEquals(JDouble.POSITIVE_INFINITY, df.parse(dfs.getInfinity, pp))
+    assertEquals(JDouble.NaN, df.parse(dfs.getNaN, pp))
+
+    assertEquals("1", df.format(1L))
+    assertEquals(dfs.getInfinity, df.format(JDouble.POSITIVE_INFINITY))
+    assertEquals(dfs.getNaN, df.format(JDouble.NaN))
+
+    df.applyPattern("#.#")
+
+    assertEquals(JLong.valueOf(1), df.parse("1.0", pp))
+    assertEquals(null, df.parse("1", pp))
+    assertEquals(null, df.parse(dfs.getInfinity, pp)) // see ICU-22303
+    assertEquals(null, df.parse(dfs.getNaN, pp)) // see ICU-22303
+
+    assertEquals("1", df.format(1L))
+    assertEquals(dfs.getInfinity, df.format(JDouble.POSITIVE_INFINITY))
+    assertEquals(dfs.getNaN, df.format(JDouble.NaN))
+
+    df.applyPattern("#")
+
+    assertEquals(null, df.parse("1.0", pp))
+    assertEquals(JLong.valueOf(1), df.parse("1", pp))
+    assertEquals(JDouble.POSITIVE_INFINITY, df.parse(dfs.getInfinity, pp))
+    assertEquals(JDouble.NaN, df.parse(dfs.getNaN, pp))
+
+    assertEquals("1", df.format(1L))
+    assertEquals(dfs.getInfinity, df.format(JDouble.POSITIVE_INFINITY))
+    assertEquals(dfs.getNaN, df.format(JDouble.NaN))
+  }
+
 }
diff --git a/daffodil-test-ibm1/src/test/resources/test-suite/tresys-contributed/BG.dfdl.xsd b/daffodil-test-ibm1/src/test/resources/test-suite/tresys-contributed/BG.dfdl.xsd
index 8c775cd17..acc0db66d 100644
--- a/daffodil-test-ibm1/src/test/resources/test-suite/tresys-contributed/BG.dfdl.xsd
+++ b/daffodil-test-ibm1/src/test/resources/test-suite/tresys-contributed/BG.dfdl.xsd
@@ -75,7 +75,7 @@
       <sequence dfdl:separator="***" dfdl:terminator="%NL;">
         <element name="z" type="xsd:float" maxOccurs="unbounded"
           dfdl:lengthKind="delimited" dfdl:textNumberJustification="right"
-          dfdl:textNumberPattern="####"
+          dfdl:textNumberPattern="####.#"
           dfdl:textNumberPadCharacter="%SP;" />
       </sequence>
     </sequence>
diff --git a/daffodil-test-ibm1/src/test/resources/test-suite/tresys-contributed/BG.tdml b/daffodil-test-ibm1/src/test/resources/test-suite/tresys-contributed/BG.tdml
index 76c4abd46..256c1c29a 100644
--- a/daffodil-test-ibm1/src/test/resources/test-suite/tresys-contributed/BG.tdml
+++ b/daffodil-test-ibm1/src/test/resources/test-suite/tresys-contributed/BG.tdml
@@ -23,9 +23,9 @@
 
   <parserTestCase name="BG000" root="list" model="BG.dfdl.xsd"
     description="Text number properties">
-    <document><![CDATA[    9#876#543#210!01***   12#345!6*** 123456789123456789*** INFINITO***    NNN***   ZERO*** NA*** NIL
+    <document><![CDATA[    9#876#543#210!01***   12#345!6*** 123456789123456789!0*** INFINITO***    NNN***   ZERO*** NA*** NIL
   aabbccddeeff*** 0f0f0f*** 123456789
-  10.1*** 20.3***   -912^-13
+  10.1*** 20.3***   -91.2^-12
 ]]></document>
     <infoset>
       <dfdlInfoset>
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section13/text_number_props/TextNumberProps.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section13/text_number_props/TextNumberProps.tdml
index 1129c6422..21e3ed1cc 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section13/text_number_props/TextNumberProps.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section13/text_number_props/TextNumberProps.tdml
@@ -312,6 +312,15 @@
     <xs:element name="tnp100" type="xs:decimal" dfdl:textNumberPattern="##00"
       dfdl:textNumberRounding="explicit" dfdl:textNumberRoundingIncrement="1" />
 
+    <xs:element name="tnp101" type="xs:int" dfdl:textNumberPattern="###0.0##"
+      dfdl:textNumberCheckPolicy="strict" />
+
+    <xs:element name="tnp102" type="xs:double" dfdl:textNumberPattern="###0.0##"
+      dfdl:textNumberCheckPolicy="strict" />
+
+    <xs:element name="tnp103" type="xs:integer" dfdl:textNumberPattern="###0.0##"
+      dfdl:textNumberCheckPolicy="strict" />
+
   </tdml:defineSchema>
   
   <tdml:defineSchema name="textNumberPattern2"  elementFormDefault="qualified">
@@ -1503,12 +1512,12 @@
      Test Name: textStandardDecimalSeparator09
         Schema: textNumberPattern
           Root: tnp20
-       Purpose: This test demonstrates that textStandardDecimalSeparator will be ignored when the type isn't
-                decimal, float, or double
+       Purpose: This test demonstrates that textStandardDecimalSeparator is used but can be missing in the data if the type isn't
+                decimal, float, or double, as long as the fractional part is non-zero
 -->
 
   <tdml:parserTestCase name="textStandardDecimalSeparator09" root="tnp20" model="textNumberPattern"
-    description="Section 13.6 - textStandardDecimalSeparator - DFDL-13-053R">
+    description="Section 13.6 - textStandardDecimalSeparator - DFDL-13-053R" roundTrip="none">
 
     <tdml:document>
       <tdml:documentPart type="text">$5</tdml:documentPart>
@@ -1521,6 +1530,28 @@
 
   </tdml:parserTestCase>
 
+<!--
+     Test Name: textStandardDecimalSeparator10
+        Schema: textNumberPattern
+          Root: tnp20
+       Purpose: This test demonstrates that textStandardDecimalSeparator is used when unparsing if the pattern specifies
+                a decimal separator, even if the type isn't decimal, float, or double
+-->
+
+  <tdml:unparserTestCase name="textStandardDecimalSeparator09u"  root="tnp20" model="textNumberPattern"
+    description="Section 13.6 - textStandardDecimalSeparator - DFDL-13-053R">
+
+    <tdml:document>
+      <tdml:documentPart type="text">$5^</tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <tnp20>5</tnp20>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+
+  </tdml:unparserTestCase>
+
   <tdml:parserTestCase name="textStandardDecimalSeparatorOneOnly1" root="tnp26" model="textNumberPattern"
     description="Section 13.6 - textStandardDecimalSeparator - DFDL-13-053R">
 
@@ -2850,6 +2881,17 @@
       </tdml:dfdlInfoset>
     </tdml:infoset>
   </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="nonNegIntPadding05" root="nonNegIntPad3" model="textNumberPattern"
+    description="Section 13 - Text number pattern - Padding - DFDL-13-098R">
+    <tdml:document>
+      <tdml:documentPart type="text">****begin: Inf :end</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Parse Error</tdml:error>
+      <tdml:error>xs:nonNegativeInteger</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
   
   <!--
      Test Name: textNumberPattern_inputValueCalc
@@ -4039,16 +4081,13 @@
       <tdml:documentPart type="text"><![CDATA[NOTANUMBER]]]></tdml:documentPart>
     </tdml:document>
 
-    <tdml:warnings>
-      <tdml:warning>DFDL property was ignored</tdml:warning>
-      <tdml:warning>textStandardNaNRep</tdml:warning>
-    </tdml:warnings>
+    <tdml:warnings />
 
     <tdml:errors>
       <tdml:error>Parse Error</tdml:error>
-      <tdml:error>Unable to parse</tdml:error>
+      <tdml:error>NaN</tdml:error>
+      <tdml:error>Out of range</tdml:error>
       <tdml:error>xs:long</tdml:error>
-      <tdml:error>NOTANUMBER</tdml:error>
     </tdml:errors>
 
   </tdml:parserTestCase>
@@ -4061,16 +4100,13 @@
       <tdml:documentPart type="text"><![CDATA[INFINITY]]]></tdml:documentPart>
     </tdml:document>
 
-    <tdml:warnings>
-      <tdml:warning>DFDL property was ignored</tdml:warning>
-      <tdml:warning>textStandardInfinityRep</tdml:warning>
-    </tdml:warnings>
+    <tdml:warnings />
 
     <tdml:errors>
       <tdml:error>Parse Error</tdml:error>
-      <tdml:error>Unable to parse</tdml:error>
+      <tdml:error>Infinity</tdml:error>
+      <tdml:error>Out of range</tdml:error>
       <tdml:error>xs:long</tdml:error>
-      <tdml:error>INFINITY</tdml:error>
     </tdml:errors>
 
   </tdml:parserTestCase>
@@ -4369,4 +4405,103 @@
     </tdml:infoset>
   </tdml:unparserTestCase>
 
+  <tdml:parserTestCase name="textNumberIntWithDecimal01" root="tnp101" model="textNumberPattern" roundTrip="none">
+    <tdml:document>
+      <tdml:documentPart type="text">2.000</tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <tnp101>2</tnp101>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <tdml:unparserTestCase name="textNumberIntWithDecimal02" root="tnp101" model="textNumberPattern">
+    <tdml:document>
+      <tdml:documentPart type="text">2.0</tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <tnp101>2</tnp101>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:unparserTestCase>
+
+  <tdml:parserTestCase name="textNumberIntWithDecimal03" root="tnp101" model="textNumberPattern">
+    <tdml:document>
+      <tdml:documentPart type="text">2.01</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Parse Error</tdml:error>
+      <tdml:error>2.01</tdml:error>
+      <tdml:error>xs:int</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="textNumberIntWithDecimal04" root="tnp101" model="textNumberPattern">
+    <tdml:document>
+      <tdml:documentPart type="text">Inf</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Parse Error</tdml:error>
+      <tdml:error>Inf</tdml:error>
+      <tdml:error>xs:int</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="textNumberDoubleWithDecimal01" root="tnp102" model="textNumberPattern">
+    <tdml:document>
+      <tdml:documentPart type="text">Inf</tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <tnp102>INF</tnp102>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="textNumberIntegerWithDecimal01" root="tnp103" model="textNumberPattern" roundTrip="none">
+    <tdml:document>
+      <tdml:documentPart type="text">9223372036854775808.000</tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <tnp103>9223372036854775808</tnp103>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <tdml:unparserTestCase name="textNumberIntegerWithDecimal02" root="tnp103" model="textNumberPattern">
+    <tdml:document>
+      <tdml:documentPart type="text">9223372036854775808.0</tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <tnp103>9223372036854775808</tnp103>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:unparserTestCase>
+
+  <tdml:parserTestCase name="textNumberIntegerWithDecimal03" root="tnp103" model="textNumberPattern">
+    <tdml:document>
+      <tdml:documentPart type="text">9223372036854775808.01</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Parse Error</tdml:error>
+      <tdml:error>9223372036854775808.01</tdml:error>
+      <tdml:error>xs:integer</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="textNumberIntegerWithDecimal04" root="tnp103" model="textNumberPattern">
+    <tdml:document>
+      <tdml:documentPart type="text">Inf</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Parse Error</tdml:error>
+      <tdml:error>Inf</tdml:error>
+      <tdml:error>xs:integer</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
 </tdml:testSuite>
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section13/zoned/pv.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section13/zoned/pv.tdml
index eea73dec3..14078010a 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section13/zoned/pv.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section13/zoned/pv.tdml
@@ -155,6 +155,24 @@
     </infoset>
   </parserTestCase>
 
+  <parserTestCase name="vpattern_08" root="money" model="s1">
+    <document>9223372036854775808</document>
+    <infoset>
+      <dfdlInfoset>
+        <ex:money>92233720368547758.08</ex:money>
+      </dfdlInfoset>
+    </infoset>
+  </parserTestCase>
+
+  <parserTestCase name="vpattern_09" root="money" model="s1">
+    <document>99.99</document>
+    <errors>
+      <error>Parse Error</error>
+      <error>Unable to parse</error>
+      <error>xs:decimal</error>
+    </errors>
+  </parserTestCase>
+
   <parserTestCase name="vpattern_zero" root="money" model="s1">
     <document>zero</document>
     <infoset>
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section15/choice_groups/ChoiceGroupInitiatedContent.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section15/choice_groups/ChoiceGroupInitiatedContent.tdml
index 219c16220..9a96ee93b 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section15/choice_groups/ChoiceGroupInitiatedContent.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section15/choice_groups/ChoiceGroupInitiatedContent.tdml
@@ -396,17 +396,17 @@
               <xs:choice dfdl:initiatedContent="no">
 
                 <xs:element name="t0" type="xs:int"
-                  dfdl:initiator="t0:" dfdl:textNumberPattern="###.0" />
+                  dfdl:initiator="t0:" dfdl:textNumberPattern="##0" dfdl:textNumberCheckPolicy="strict" />
                 <xs:element name="t1" type="xs:int"
-                  dfdl:initiator="t1:" dfdl:textNumberPattern="###.0" />
+                  dfdl:initiator="t1:" dfdl:textNumberPattern="##0" dfdl:textNumberCheckPolicy="strict" />
                 <xs:element name="t2" type="xs:int"
-                  dfdl:initiator="t2:" dfdl:textNumberPattern="###.0" />
+                  dfdl:initiator="t2:" dfdl:textNumberPattern="##0" dfdl:textNumberCheckPolicy="strict" />
                 <xs:element name="t2f" type="xs:float"
-                  dfdl:initiator="t2:" dfdl:textNumberPattern="###.0" />
+                  dfdl:initiator="t2:" dfdl:textNumberPattern="###.0" dfdl:textNumberCheckPolicy="strict" />
                 <xs:element name="t3" type="xs:int"
-                  dfdl:initiator="t3:" dfdl:textNumberPattern="###.0" />
+                  dfdl:initiator="t3:" dfdl:textNumberPattern="##0" dfdl:textNumberCheckPolicy="strict" />
                 <xs:element name="t4" type="xs:int"
-                  dfdl:initiator="t4:" dfdl:textNumberPattern="###.0" />
+                  dfdl:initiator="t4:" dfdl:textNumberPattern="##0" dfdl:textNumberCheckPolicy="strict" />
                 <xs:element name="other" type="xs:string" />
               </xs:choice>
             </xs:complexType>
@@ -423,18 +423,18 @@
             dfdl:occursCountKind="parsed" dfdl:lengthKind="implicit">
             <xs:complexType>
               <xs:choice dfdl:initiatedContent="yes">
-                <xs:element name="t0" type="xs:int"
+                <xs:element name="t0" type="xs:int" dfdl:textNumberPattern="##0" dfdl:textNumberCheckPolicy="strict"
                   dfdl:initiator="t0:" />
-                <xs:element name="t1" type="xs:int"
+                <xs:element name="t1" type="xs:int" dfdl:textNumberPattern="##0" dfdl:textNumberCheckPolicy="strict"
                   dfdl:initiator="t1:" />
-                <xs:element name="t2" type="xs:int"
+                <xs:element name="t2" type="xs:int" dfdl:textNumberPattern="##0" dfdl:textNumberCheckPolicy="strict"
                   dfdl:initiator="t2:" />
                 <!-- t2f can never happen because of the initiator.and initiatedContent -->
-                <xs:element name="t2f" type="xs:float"
+                <xs:element name="t2f" type="xs:float" dfdl:textNumberPattern="##0.0" dfdl:textNumberCheckPolicy="strict"
                   dfdl:initiator="t2:" />
-                <xs:element name="t3" type="xs:int"
+                <xs:element name="t3" type="xs:int" dfdl:textNumberPattern="##0" dfdl:textNumberCheckPolicy="strict"
                   dfdl:initiator="t3:" />
-                <xs:element name="t4" type="xs:int"
+                <xs:element name="t4" type="xs:int" dfdl:textNumberPattern="##0" dfdl:textNumberCheckPolicy="strict"
                   dfdl:initiator="t4:" />
               </xs:choice>
             </xs:complexType>
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section13/text_number_props/TestTextNumberProps.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section13/text_number_props/TestTextNumberProps.scala
index 51dc733a2..787c6819e 100644
--- a/daffodil-test/src/test/scala/org/apache/daffodil/section13/text_number_props/TestTextNumberProps.scala
+++ b/daffodil-test/src/test/scala/org/apache/daffodil/section13/text_number_props/TestTextNumberProps.scala
@@ -280,6 +280,9 @@ class TestTextNumberProps {
   @Test def test_textStandardDecimalSeparator09(): Unit = {
     runner.runOneTest("textStandardDecimalSeparator09")
   }
+  @Test def test_textStandardDecimalSeparator09u(): Unit = {
+    runner.runOneTest("textStandardDecimalSeparator09u")
+  }
   @Test def test_textStandardDecimalSeparator12(): Unit = {
     runner.runOneTest("textStandardDecimalSeparator12")
   }
@@ -453,6 +456,7 @@ class TestTextNumberProps {
   @Test def test_nonNegIntPadding02(): Unit = { runner.runOneTest("nonNegIntPadding02") }
   @Test def test_nonNegIntPadding03(): Unit = { runner.runOneTest("nonNegIntPadding03") }
   @Test def test_nonNegIntPadding04(): Unit = { runner.runOneTest("nonNegIntPadding04") }
+  @Test def test_nonNegIntPadding05(): Unit = { runner.runOneTest("nonNegIntPadding05") }
 
   @Test def test_hexBinaryPadding01(): Unit = { runner.runOneTest("hexBinaryPadding01") }
 
@@ -489,4 +493,34 @@ class TestTextNumberProps {
   @Test def test_textStandardRoundingIncrement4(): Unit = {
     runner.runOneTest("textNumberRoundingIncrement4")
   }
+
+  @Test def test_textNumberIntWithDecimal01(): Unit = {
+    runner.runOneTest("textNumberIntWithDecimal01")
+  }
+  @Test def test_textNumberIntWithDecimal02(): Unit = {
+    runner.runOneTest("textNumberIntWithDecimal02")
+  }
+  @Test def test_textNumberIntWithDecimal03(): Unit = {
+    runner.runOneTest("textNumberIntWithDecimal03")
+  }
+  @Test def test_textNumberIntWithDecimal04(): Unit = {
+    runner.runOneTest("textNumberIntWithDecimal04")
+  }
+
+  @Test def test_textNumberDoubleWithDecimal01(): Unit = {
+    runner.runOneTest("textNumberIntWithDecimal04")
+  }
+
+  @Test def test_textNumberIntegerWithDecimal01(): Unit = {
+    runner.runOneTest("textNumberIntegerWithDecimal01")
+  }
+  @Test def test_textNumberIntegerWithDecimal02(): Unit = {
+    runner.runOneTest("textNumberIntegerWithDecimal02")
+  }
+  @Test def test_textNumberIntegerWithDecimal03(): Unit = {
+    runner.runOneTest("textNumberIntegerWithDecimal03")
+  }
+  @Test def test_textNumberIntegerWithDecimal04(): Unit = {
+    runner.runOneTest("textNumberIntegerWithDecimal04")
+  }
 }
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section13/zoned/TestPV.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section13/zoned/TestPV.scala
index d137f61d3..2892bbe08 100644
--- a/daffodil-test/src/test/scala/org/apache/daffodil/section13/zoned/TestPV.scala
+++ b/daffodil-test/src/test/scala/org/apache/daffodil/section13/zoned/TestPV.scala
@@ -42,6 +42,8 @@ class TestPV {
   @Test def vpattern_05(): Unit = { runner.runOneTest("vpattern_05") }
   @Test def vpattern_06(): Unit = { runner.runOneTest("vpattern_06") }
   @Test def vpattern_07(): Unit = { runner.runOneTest("vpattern_07") }
+  @Test def vpattern_08(): Unit = { runner.runOneTest("vpattern_08") }
+  @Test def vpattern_09(): Unit = { runner.runOneTest("vpattern_09") }
 
   @Test def vpattern_zero(): Unit = { runner.runOneTest("vpattern_zero") }
   @Test def vpattern_ZZZ(): Unit = { runner.runOneTest("vpattern_ZZZ") }