Class DynamicNumber

java.lang.Object
java.lang.Number
org.daiitech.naftah.builtin.lang.DynamicNumber
All Implemented Interfaces:
Serializable, Cloneable, Comparable<DynamicNumber>

public class DynamicNumber extends Number implements Comparable<DynamicNumber>, Cloneable
A wrapper class for numeric values that supports dynamic typing and provides utility methods for type checking, conversion, promotion, and normalization of numbers.

This class can hold any subclass of Number and provides convenience methods to interpret and convert the value to various numeric types like byte, short, int, long, BigInteger, float, double, and BigDecimal.

It supports promotion of types to larger ranges and normalization to the smallest suitable numeric type.

Author:
Chakib Daii
See Also:
  • Field Details

    • value

      private Number value
      The underlying numeric value.
  • Constructor Details

    • DynamicNumber

      public DynamicNumber(Number value)
      Constructs a DynamicNumber from a Number.
      Parameters:
      value - the numeric value, must not be null
      Throws:
      NaftahBugError - if the value is null
    • DynamicNumber

      public DynamicNumber(Object value)
      Constructs a DynamicNumber from an Object by parsing it to a numeric value.
      Parameters:
      value - the value to parse as a number, must not be null
      Throws:
      NaftahBugError - if the value is null
      See Also:
  • Method Details

    • of

      public static DynamicNumber of(Number value)
      Creates a new DynamicNumber from a Number.
      Parameters:
      value - the number
      Returns:
      a new DynamicNumber wrapping the given value
    • of

      public static DynamicNumber of(Object value)
      Creates a new DynamicNumber by parsing an Object.
      Parameters:
      value - the value to parse
      Returns:
      a new DynamicNumber representing the parsed value
    • isNaN

      public static boolean isNaN(Number number)
      Checks if the given number is NaN (Not-a-Number).

      Only applies to Float and Double values. If the number is an instance of DynamicNumber, it will be unwrapped first.

      Parameters:
      number - the number to check
      Returns:
      true if the number is NaN, false otherwise
    • isInfinite

      public static boolean isInfinite(Number number)
      Checks if the given number is positive or negative infinity.

      Only applies to Float and Double values. If the number is an instance of DynamicNumber, it will be unwrapped first.

      Parameters:
      number - the number to check
      Returns:
      true if the number is Infinity or -Infinity, false otherwise
    • isAssignableFrom

      public static boolean isAssignableFrom(Class<?> valueClass, Class<?> targetType)
      Determines whether a value of the given runtime class can be assigned to a target type using relaxed numeric and primitive compatibility rules.

      This method extends Class.isAssignableFrom(Class) semantics by:

      • Normalizing primitive types to their boxed equivalents
      • Allowing numeric widening conversions between Number types based on a predefined numeric rank hierarchy

      Two types are considered compatible if:

      • They are equal after primitive normalization, or
      • Both are subclasses of Number and the value type’s numeric rank is less than or equal to the target type’s numeric rank

      If either valueClass or targetType is null, this method returns false.

      Parameters:
      valueClass - the runtime class of the value being assigned
      targetType - the desired target type
      Returns:
      true if a value of valueClass can be assigned to targetType under these rules; false otherwise
    • numericRank

      private static int numericRank(Class<?> clazz)
      Returns the numeric rank of a class to determine widening order.

      The hierarchy is:

       Byte: 1
       Short: 2
       Integer: 3
       Long: 4
       BigInteger: 5
       Float: 6
       Double: 7
       BigDecimal: 8
       
      Any unknown numeric class returns Integer.MAX_VALUE.
      Parameters:
      clazz - the numeric class
      Returns:
      the rank of the numeric type
    • isByte

      public boolean isByte()
      Checks if the value is a Byte.
      Returns:
      true if the underlying value is a Byte
    • isShort

      public boolean isShort()
      Checks if the value is a Short.
      Returns:
      true if the underlying value is a Short
    • isInt

      public boolean isInt()
      Checks if the value is an Integer.
      Returns:
      true if the underlying value is an Integer
    • isLong

      public boolean isLong()
      Checks if the value is a Long.
      Returns:
      true if the underlying value is a Long
    • isBigInteger

      public boolean isBigInteger()
      Checks if the value is a BigInteger.
      Returns:
      true if the underlying value is a BigInteger
    • isInteger

      public boolean isInteger()
      Checks if the value is an integral integer type (Byte, Short, Integer, Long, or BigInteger).
      Returns:
      true if the value is an integral number
    • isFloat

      public boolean isFloat()
      Checks if the value is a Float.
      Returns:
      true if the underlying value is a Float
    • isDouble

      public boolean isDouble()
      Checks if the value is a Double.
      Returns:
      true if the underlying value is a Double
    • isBigDecimal

      public boolean isBigDecimal()
      Checks if the value is a BigDecimal.
      Returns:
      true if the underlying value is a BigDecimal
    • isDecimal

      public boolean isDecimal()
      Checks if the value is a floating point type (Float, Double, or BigDecimal).
      Returns:
      true if the value is a decimal number
    • intValue

      public int intValue()
      Returns the value as an int.
      Specified by:
      intValue in class Number
      Returns:
      the int value
    • longValue

      public long longValue()
      Returns the value as a long.
      Specified by:
      longValue in class Number
      Returns:
      the long value
    • floatValue

      public float floatValue()
      Returns the value as a float.
      Specified by:
      floatValue in class Number
      Returns:
      the float value
    • doubleValue

      public double doubleValue()
      Returns the value as a double.
      Specified by:
      doubleValue in class Number
      Returns:
      the double value
    • asBigInteger

      public BigInteger asBigInteger()
      Returns the value as a BigInteger.

      If the underlying value is not already a BigInteger, it converts by parsing the string representation.

      Returns:
      the BigInteger representation
    • asBigDecimal

      public BigDecimal asBigDecimal()
      Returns the value as a BigDecimal.

      Converts from BigInteger or parses from string if needed.

      Returns:
      the BigDecimal representation
    • get

      public Number get()
      Returns the underlying Number value.
      Returns:
      the wrapped numeric value
    • set

      public DynamicNumber set(Number value)
      Sets the underlying value.
      Parameters:
      value - the new numeric value
      Returns:
      this DynamicNumber instance for chaining
    • promote

      public DynamicNumber promote()
      Promotes the number to the next wider numeric type.
      • Byte -> Short
      • Short -> Int
      • Int -> Long
      • Long -> BigInteger
      • Float -> Double
      • Double -> BigDecimal
      If no promotion is possible, returns this instance.
      Returns:
      a promoted DynamicNumber, or this if no promotion
    • normalize

      public DynamicNumber normalize()
      Normalizes the internal number value to the smallest suitable numeric type.

      This method simplifies the internal representation of the number when possible:

      • Converts a BigDecimal with no fractional part to a BigInteger
      • Downcasts a BigInteger to long if it fits
      • Downcasts integral long values to int, short, or byte if within range
      • If floating point support is enabled via normalize(boolean), it also tries to:
        • Convert Double values to Float if within precision range
        • Convert whole Float or Double values to integral types
      Returns:
      this, after normalizing the value in place
      See Also:
    • normalize

      public DynamicNumber normalize(boolean processFloatingNumbers)
      Normalizes the internal number to the most compact numeric type possible, optionally including support for floating point number simplification.

      The normalization process works as follows:

      • BigDecimal: If it has no fractional part, it's converted to BigInteger
      • BigInteger: Downcast to long if it fits (bit length ≤ 63)
      • long: Downcast to int, short, or byte if within range
      • Double/Float: If processFloatingNumbers is true, and:
        • It's a finite, whole number → convert to long and then normalize further
        • Double fits in Float range → convert to Float

      Special floating-point values like NaN or Infinity are preserved and skipped.

      Parameters:
      processFloatingNumbers - whether to process and simplify Float / Double types
      Returns:
      this, after normalizing the value in place
    • equals

      public boolean equals(Object o)
      Compares this DynamicNumber to another object for equality.

      Equality is based on numeric value comparison using NumberUtils.equals(T, T).

      Overrides:
      equals in class Object
      Parameters:
      o - the object to compare to
      Returns:
      true if numerically equal; false otherwise
    • hashCode

      public int hashCode()
      Returns the hash code of the underlying numeric value.
      Overrides:
      hashCode in class Object
      Returns:
      the hash code
    • toString

      public String toString()
      Returns the string representation of the numeric value.
      Overrides:
      toString in class Object
      Returns:
      the string form of the wrapped number
    • isNaN

      public boolean isNaN()
      Checks if the internal value is a floating-point NaN (Not-a-Number).
      Returns:
      true if the value is NaN, otherwise false
      See Also:
    • isInfinite

      public boolean isInfinite()
      Checks if the internal value is positive or negative infinity.
      Returns:
      true if the value is Infinity or -Infinity, otherwise false
      See Also:
    • clone

      public DynamicNumber clone()
      Creates and returns a copy of this DynamicNumber.

      This performs a shallow clone. If your subclass adds mutable fields, ensure they are also copied to prevent shared state.

      Overrides:
      clone in class Object
      Returns:
      a cloned copy of this instance
      Throws:
      NaftahBugError - if cloning fails unexpectedly
    • compareTo

      public int compareTo(DynamicNumber anotherDynamicNumber)
      Specified by:
      compareTo in interface Comparable<DynamicNumber>
    • isAssignableFrom

      public boolean isAssignableFrom(Class<?> targetType)
      Checks if the current numeric value can be assigned to the specified target type.

      This is a strict numeric assignability check for Number values. It allows "widening" conversions only, according to the following hierarchy:

       Byte < Short < Integer < Long < BigInteger < Float < Double < BigDecimal
       
      For example, a Byte value can be assigned to Integer, but an Integer value cannot be assigned to Byte.

      Parameters:
      targetType - the target Class to check against; must not be null
      Returns:
      true if the value can be safely assigned to targetType, false otherwise