Conventional Types
DataCanon Conventional Types — Version 1 Draft
Status: Draft for independent implementation review
Depends on: DataCanon Core Version 1
Related: DataCanon Persistence Version 1
Scope: Optional conventional numerical and collection types, their operations, and conversion relationships with DCIs
Out of scope: DCT definitions, canonical encoding, textual syntax, and complete symbolic algebra
Purpose and boundaries
DataCanon Core defines data and identity. Core does not perform arithmetic. An implementation can also provide conventional types used directly for data and computation in its programming language. Conventional types include built-in, library-provided, and user-defined types.
An Integer DCI and a conventional integer are different objects with different contracts. The same distinction applies to a Sum DCI and a conventional Sum: the DCI preserves its constituent information, while the conventional object supports arithmetic and construction-time simplification.
Many conventional types already exist in programming languages and libraries. This companion specification defines a common behavioral foundation for additional types, including Power, Sum, Product, OrderedProduct, Quotient, ComplexNumber, Multiset, and an unordered mapping. An implementation MAY use an existing type when it satisfies the relevant requirements.
Support for this companion specification is optional. Conformance to Core, Shorthand, or a persistence specification does not require conventional arithmetic. Supporting conventional arithmetic MUST NOT change DCI equality, canonical encoding, or fingerprints.
Conformance and language bindings
The terms MUST, MUST NOT, SHOULD, and MAY express requirements, recommendations, and permissions.
Conformance is declared per conventional type. An implementation MUST identify which types it supports and any dependencies needed for their operations. For example, a Power implementation need not provide Multiset, but a numerical implementation claiming the operations below must support their result types. Construction-only adapters MUST NOT claim the arithmetic conformance defined here.
A language binding specifies class or type names, constructors, operators
or equivalent functions, error reporting, and conversions in a particular
language. Names such as Power(base, numerator, denominator) in this document
describe a language-independent interface. A binding need not reproduce Python
classes, exceptions, method names, or operator overloading.
A conforming binding MUST document:
- supported conventional types and scalar domains;
- construction, arithmetic, and explicit numerical conversion interfaces;
- mappings to and from DCIs, including possible loss and failure;
- additional simplifications and limits of exact comparison; and
- practical resource limits and how exhausted limits are reported.
Additional conventional types and stronger exact simplification MAY be provided. Extensions MUST preserve the requirements below. No universal canonical representation of conventional objects is defined: independently implemented conventional objects can have different stored forms while representing the same numerical value.
Common numerical model
Operands and scalar domains
Scalar real numerical operands comprise conventional integers, exact rational numbers, finite floating-point numbers, and supported conventional Product, Power, Quotient, and Constant objects, plus recursively scalar Sum and OrderedProduct objects. Complex numbers are handled separately. Sum and OrderedProduct also accept numerical Arrays and array-valued expressions. Product, Power, Quotient, and ComplexNumber components remain scalar-only. Booleans MUST NOT be accepted as arithmetic operands merely because a language treats them as integers.
The portable scalar domains are mathematical integers, rational numbers with positive denominators, and finite IEEE-754 binary64 numbers. A binding SHOULD reuse appropriate existing integer and rational types. Fixed-width bindings MUST detect unsupported ranges rather than silently wrap exact arithmetic. Additional floating-point formats MAY be supported as documented extensions.
A rational with denominator one MAY be stored as an integer. Exact rational arithmetic MUST preserve the mathematical result, subject to reported resource limits. Nonfinite floats MUST be rejected as operands and MUST NOT be silently introduced into valid numerical objects by arithmetic.
Stored structure and numerical value
Conventional numerical equality concerns numerical values, rather than DCI identity or equality of stored constituents. Construction can simplify stored structure, but construction does not promise a unique normal form for every mathematically equivalent expression.
An explicit constructor MUST retain the requested outer conventional type. For example, constructing a Power whose value is two still produces a Power. Arithmetic operations MAY instead return an integer, rational, float, or another supported numerical type when simplification permits it.
Sum and Product MUST retain the first-occurrence order of surviving operand groups for traversal and presentation. Combining an operand with an earlier matching group does not move that group. Equality MUST ignore the order of Sum terms and Product factors. A binding MUST NOT sort stored operands merely to obtain commutative equality.
Exactness and floating-point arithmetic
Construction and arithmetic MUST NOT approximate an unresolved rational power by a floating-point value. Explicit numerical conversion is a separate operation.
Existing floats remain floating-point operands. Rational and floating-point coefficient groups MUST remain separate during collection of terms and factors. An implementation MUST NOT replace a float with a large rational merely to obtain an exact stored coefficient.
Arithmetic combining floats uses the binding’s documented floating-point operations and rounding. Such arithmetic can round even though an unresolved Power remains exact. Retaining a float in a structured object does not create arbitrary-precision floating-point arithmetic.
Exact comparison MAY interpret a finite float as its exact binary rational value internally. Comparison MUST NOT modify the stored float or use a rounded numerical evaluation as evidence of equality.
Constant
A conventional Constant stores exactly one name, pi or e, representing the
same exact mathematical constant as the corresponding Constant DCI. A binding
MUST reject other names. The Python binding provides Constant("pi") and
Constant("e"), with Pi and E as conventional instances.
Construction and arithmetic MUST preserve the named constant exactly, rather than replacing it with an approximate float. Explicit floating-point conversion MAY approximate its value. Named constants participate as real numerical operands in Sum, Product, Power, Quotient, and ComplexNumber.
Comparison MUST distinguish a named constant from any finite rational or float
approximation. Bindings MUST recognize identical names, cancellation of an
identical nonzero constant, and matching symbolic terms. Bindings are not
required to decide arbitrary identities involving transcendental constants.
For example, Pi + Pi == 2 * Pi, Pi / Pi == 1, and Pi != float(Pi).
No stored approximation participates in exact comparison.
Power
A Power has a real numerical base and a positive rational exponent supplied as a positive integer numerator and positive integer denominator. The denominator defaults to one. The exponent pair MUST be reduced by its greatest common divisor during construction.
For real values, a negative base with an odd reduced denominator uses the real root. A negative base with an even reduced denominator has no real value and MUST be rejected when the sign is established. A binding MAY defer a domain decision for a compound base whose sign its exact machinery cannot establish; an attempted numerical evaluation outside the real domain MUST fail.
Construction MUST extract an exact denominator-th root of an integer or rational scalar base when the entire required root is exact. The extracted root becomes the base and the reduced numerator becomes the integer exponent. The integer exponent MUST then remain stored, rather than being evaluated solely by the Power constructor.
| Input constructor | Required stored result |
|---|---|
Power(5, 2, 4) | Power(5, 1, 2) |
Power(4, 1, 2) | Power(2, 1, 1) |
Power(16, 3, 2) | Power(4, 3, 1) |
Power(4, 8) | Power(4, 8, 1) |
Power(8, 1, 2) | Power(8, 1, 2) |
Power(-8, 2, 3) | Power(-2, 2, 1) |
For a float base, a replacement base MUST preserve the exact value of the
stored power. An exact representable root MAY be extracted; an approximate
root MUST NOT be substituted. For example, the Python binding extracts the
square root of 4.0 as 2.0, retaining a float base.
Nested powers MAY be combined only when the real branch is preserved. Blindly multiplying exponents is incorrect for negative bases: the square root of the square of negative two has value positive two. Additional exact power simplification is allowed in surrounding arithmetic; retaining an exponent in a directly constructed Power does not prohibit its evaluation in a Sum or Product.
A binding MAY provide exponentiation by zero and negative rational exponents as arithmetic operations. Zero exponent gives one wherever the operation is defined. A negative exponent is represented using a reciprocal Quotient and a positive Power. Such operations do not permit zero or negative exponent constituents in a Power constructor.
OrderedProduct and array-valued Sum
OrderedProduct preserves a sequence of real scalar and Array-expression operands. Array operands in this section are instances of the numerical array type provided by the binding. Construction MUST NOT evaluate Array products, approximate exact coefficients, infer result shapes, or flatten nesting. Implementations MUST preserve multiplication order. Scalar-only OrderedProducts are also valid.
Bindings MAY declare scalar-only Sum support separately from Array-expression arithmetic. Unsupported operands MUST be rejected explicitly. For bindings supporting Array-expression arithmetic, the Version 1 contract is:
- Sum accepts Arrays of any rank with exactly matching shapes. No broadcasting or automatic reshaping is performed.
- OrderedProduct accepts rank-two Arrays, multiplying them in order. Real scalar factors scale the result and may appear anywhere in the sequence.
- Nonzero scalar terms mixed into an Array-valued Sum are rejected during evaluation; a user can supply an explicit identity Array. Scalar zero is accepted as an additive identity, supporting ordinary summation interfaces.
- Unsupported ranks, incompatible dimensions, and nonfinite floating-point results cause evaluation to fail. Structural DCI validity is unaffected.
- Exact rational, radical, and named-constant coefficients MUST remain exact during evaluation. Explicit floating-point or machine-complex conversion may approximate them. Fixed-width integer multiplication MUST NOT silently wrap.
The Python binding supports integer, floating-point, and complex numerical Arrays for this arithmetic. Boolean Arrays are not arithmetic operands. Object Arrays may contain supported conventional scalar entries. Exact coefficients remain exact when scaling complex Array elements as well.
The Python binding offers .evaluate() on conventional numerical objects.
Array-expression evaluation returns an object-dtype NumPy Array containing
conventional scalar values, including ComplexNumber where needed. Such an
object Array is not a portable DataCanon Array. To encode its result, explicitly
choose a supported machine dtype, or convert its nested contents to Lists so
individual exact values retain their DCTs.
np.asarray(expression, dtype=float) or dtype=complex explicitly requests a
machine approximation. Without a dtype, exact object entries remain. Evaluation
requires allocation; copy=False is rejected. An Array-valued expression cannot
be converted to scalar float or int. Array division by a real scalar may
construct reciprocal scaling; division by an Array is not supported.
Python * and @ involving a conventional array-expression object preserve
factor order and construct OrderedProduct. Ordinary NumPy operations involving
only ordinary NumPy Arrays retain NumPy’s own semantics. Expressions retain
owned read-only copies of supplied Arrays; callers MUST NOT circumvent that
protection. Equality of conventional array expressions compares whole shapes
and scalar entries, returning one Boolean rather than an Array of Booleans.
Sum
The following normalization rules apply to scalar-only Sums. Array-valued Sums may retain operand boundaries until explicit evaluation as described above.
A Sum contains zero or more real numerical terms. Its value is their sum; an empty Sum has value zero. Construction MUST flatten directly nested Sums and combine exact rational constants.
Matching symbolic terms with exact rational coefficients MUST combine their coefficients. The mandatory comparison cases below establish a minimum set of recognized matches; complete algebraic matching is not required. Floating-point coefficient groups MUST be collected separately from exact rational groups.
For example, two occurrences of the square root of three, one multiplied by three, combine to four times the square root of three. A separate floating-point offset remains a float. Exact cancellation MAY remove terms and can leave an empty Sum; the explicit constructor still returns a Sum.
Construction MUST preserve surviving term-group order. An implementation MAY perform additional exact term simplifications within the documented comparison limits. It MUST NOT evaluate an irrational term to collect it with a float.
Product
A Product contains zero or more real numerical factors. Its value is their product; an empty Product has value one. Construction MUST flatten directly nested Products and combine rational scalar factors. Float factors form a separate coefficient group.
Construction MUST combine powers with an identical supported scalar base by adding their rational exponents when the operation preserves the real value. An exact zero factor reduces the product’s value to zero. A floating-point zero factor remains floating-point when no exact zero determines the result. Recognized nonzero identity factors MAY be omitted.
An explicit Product constructor need not distribute over Sum factors. The arithmetic multiplication operation MUST support distribution when a scalar Sum is an operand. Retaining a factored explicit Product and expanding multiplication are compatible: exact comparison recognizes the required distributive cases.
Quotient
A Quotient contains a real numerical numerator and denominator. Unlike the Fraction DCT, either operand can be a compound numerical object. Its value is the numerator divided by the denominator.
A denominator established to be zero MUST be rejected. A binding MAY defer a zero decision that lies outside its exact comparison capabilities; numerical division by zero MUST fail when encountered.
Construction MUST reduce a quotient of rational scalars, flatten directly nested Quotients, and cancel recognized matching nonzero factors. A zero numerator with an established nonzero denominator reduces to zero over one. Equal established nonzero numerator and denominator reduce to one over one. Construction retains the Quotient outer type.
A denominator containing a scalar Sum is permitted. Rationalizing such a denominator is not a construction requirement. For example, the reciprocal of the sum of the square roots of three and two can remain stored as that Quotient.
An explicit Quotient containing a float identifies division by the exact stored float. Arithmetic division involving floats MAY use ordinary rounded floating-point division or a rounded reciprocal according to the binding. The binding MUST document that distinction. An implementation MUST NOT claim that a rounded reciprocal is an exact algebraic rewrite.
Arithmetic and numerical conversion
Conforming numerical implementations MUST provide addition, subtraction, negation, multiplication, and division with supported real operands, including scalar operands on either side. Operator spelling is language-specific. Unsupported operands MUST produce an explicit failure or the host language’s unsupported-operation protocol; they MUST NOT be silently approximated.
Arithmetic MUST preserve exact integer, rational, and unresolved power values, apart from explicitly documented operations involving floats. A result MAY unwrap an empty or singleton Sum or Product to a conventional scalar or remaining numerical object. Explicit constructors retain their outer type.
A binding MUST provide an explicit conversion to its floating-point numerical type. The conversion evaluates the numerical value and permits approximation. Overflow, an invalid real domain, and division by zero MUST be reported rather than yielding an accepted nonfinite structured operand.
An integer conversion, if provided, MUST document its rounding rule. The Python
binding evaluates and truncates toward zero, following Python’s int behavior.
Evaluation of an irrational expression may first round; the conversion does
not promise an exact floor or truncation algorithm for arbitrary algebraic
numbers near an integer boundary.
Numerical equality
Numerical equality MUST use exact reasoning for supported comparisons. Equality MUST NOT be implemented by converting both operands to floats, by tolerance, or by comparing approximate decimal renderings.
The mandatory comparison foundation includes rational arithmetic, the actual
binary rational values of finite floats, operand permutation, directly nested
Sum/Product flattening, distribution for finite rational combinations, exact
scalar root extraction, and the radical identities in the following table.
R(n, d) denotes an exact conventional rational; sqrt(n) denotes
Power(n, 1, 2), not a floating-point square-root function.
| Comparison | Required result |
|---|---|
Power(4, 1, 2) == 2 | true |
Power(0, 1, 2) == 0 | true |
Power(4, 8) == 65536 | true |
sqrt(12) == 2 * sqrt(3) | true |
sqrt(2) * sqrt(3) == sqrt(6) | true |
sqrt(3) * sqrt(3) == 3 | true |
Quotient(1, Sum(sqrt(3), sqrt(2))) == sqrt(3) - sqrt(2) | true |
Sum(R(1, 2), 0.25) == R(3, 4) | true |
Quotient(1, 2) == 0.5 | true |
Quotient(1, 10) == 0.1 | false |
Sum(2**53, 1) == float(2**53) | false |
No complete decision procedure for all mathematical identities is required. For identities beyond the mandatory foundation, a binding MAY conservatively return false when equality is not established, or report an unsupported comparison. The choice MUST be documented. Such a false result is not a proof of mathematical inequality. A resource-limit failure MAY be reported explicitly.
Factorization algorithms, search budgets, and cached comparison forms are implementation choices. A binding MUST NOT publish a factorization cutoff as a universal mathematical definition of equality. Cross-language fixtures MUST include the mandatory cases and identify any additional promised cases.
Conventional numerical objects MAY be unhashable. If hashing is provided, objects that compare equal MUST hash equally under the language’s hash contract, including comparisons with existing scalar types. Hashing arbitrary stored syntax is insufficient for mathematical equality. DCI hashing is a separate matter governed by DCI identity.
ComplexNumber
A ComplexNumber contains real and imaginary conventional numerical components. The components MAY be integers, rationals, floats, Constant, Product, Power, Quotient, or recursively scalar Sum and OrderedProduct objects. Construction MUST preserve supported exact components rather than converting them unconditionally to floats.
Equality compares corresponding component values. A binding SHOULD support comparison with a real scalar by treating its imaginary component as zero, and with an existing machine complex type using its actual stored components.
Addition, subtraction, negation, multiplication, and division MUST follow the usual complex formulas using the binding’s component arithmetic. Division by an established zero complex value MUST fail. Native scalar arithmetic may affect result types and rounding; the binding MUST document that behavior. An explicit conversion to a machine complex type MAY approximate components.
Multiset
A conventional Multiset represents a finite unordered collection with multiplicity. Equality requires equal member multiplicities under the conventional member equality relation. Member order does not affect equality. Construction and iteration SHOULD preserve insertion order for presentation.
A binding MUST provide construction from a finite collection, iteration, total occurrence count, and insertion of one occurrence. Mutability and additional collection operations are binding choices.
Membership comparison uses conventional equality, not DCI equality. For example, a language that considers integer one and floating-point one equal can treat their occurrences as belonging to the same equality group. Converting the Multiset to a Multiset DCI still converts the actual stored members individually; distinct conventional storage can produce distinct DCIs.
A Multiset SHOULD support unhashable members when their equality is usable. A binding MUST document restrictions on member comparison, including members whose equality does not return a usable Boolean result. A mutable Multiset need not be hashable.
Unordered mapping
A conventional unordered mapping supplies the language’s ordinary key/value
operations with equality independent of entry order. An existing mapping type
MAY satisfy this contract. The Python binding uses UnorderedDict to identify
conversion to the UnorderedMapping DCT, while ordinary dict converts to the
ordered Mapping DCT.
Key hashability and equality follow the conventional mapping’s rules. A binding MUST NOT claim that an ordinary mapping can retain arbitrary distinct DCI keys after converting them to conventional objects. Key collisions and unsupported keys are possible conversion outcomes.
Conversion to and from DCIs
Conversion MUST distinguish an instance of a conventional type from an object implementing a DCT. Sharing a programming language does not make their rules identical.
| DCT | Typical conventional counterpart |
|---|---|
| Null, Boolean, Integer, Float, String, Bytes | Existing language or library scalar types |
| Fraction | Exact rational type |
| Constant | Named conventional Constant |
| Power, Sum, Product, Quotient, OrderedProduct | Corresponding conventional numerical types |
| Complex | Structured conventional ComplexNumber or a machine complex type |
| List, Tuple, Set | Existing conventional collection types |
| Multiset | Conventional Multiset |
| Mapping, UnorderedMapping | Suitable conventional mapping types or adapters |
| Record | Binding-defined record or application type |
| Array | Numerical array type |
| ExternalFile, ExternalArray | Resource adapters or verified materialized content |
An implementation claiming conversions for a supported conventional type MUST document both directions independently. Conversion to a DCI MUST use the conventional object’s current stored information. It MUST NOT substitute an approximate evaluated scalar for a structured numerical object merely because the numerical values are close.
Conversion from a DCI MAY invoke a conventional constructor and simplify its information. Conversion through conventional objects is therefore not universally identity preserving. A conventional Power constructor can reduce an exponent or extract a root that its source Power DCI stores structurally. A conventional rational constructor can normalize its numerator and denominator.
Mappings and Sets can merge members under conventional equality, reject unhashable members, or fail under binding policy. A binding MUST document whether collisions fail or follow ordinary collection construction. Conversion MUST NOT be advertised as lossless when such outcomes are possible.
Not every valid numerical DCI has a defined real numerical value. A conversion to a conventional numerical type MAY fail even when decoding the DCI succeeds. An implementation MUST NOT repair the DCI to hide that failure.
Array type, shape, signed zeros, and copy or mutability policy MUST be stated by the binding. External payload retrieval and verification follow Persistence; no conversion rule in this document weakens digest or element validation. Metadata-only external adapters MAY be provided. Converting an external DCI into materialized conventional content does not change the original DCI.
Python binding examples
The Python reference binding exports the conventional numerical and collection
classes from data_canon. Python’s fractions.Fraction supplies exact rationals.
from fractions import Fraction
from data_canon import (
Power, Sum, Product, Quotient, ComplexNumber, Multiset, UnorderedDict,
as_dc_instance, dc_instance_to_python,
)
root_two = Power(2, 1, 2)
root_three = Power(3, 1, 2)
assert Sum(root_three, 3 * root_three) == 4 * root_three
assert Product(root_two, root_three) == Power(6, 1, 2)
assert Quotient(1, Sum(root_three, root_two)) == root_three - root_two
assert Quotient(1, 10) != 0.1
power = Power(16, 3, 2)
assert (power.base, power.numerator, power.denominator) == (4, 3, 1)
retained = Power(4, 8)
assert (retained.base, retained.numerator, retained.denominator) == (4, 8, 1)
assert retained == 65536
assert ComplexNumber(Fraction(1, 2), root_three) == ComplexNumber(0.5, root_three)
assert Multiset(["O", "Ti", "O"]) == Multiset(["Ti", "O", "O"])
assert UnorderedDict({"a": 1, "b": 2}) == UnorderedDict({"b": 2, "a": 1})
Conventional construction and DCI construction have distinct outcomes:
from data_canon.instance import IntegerDC, PowerDC
source = PowerDC(IntegerDC(16), 3, 2)
conventional = dc_instance_to_python(source)
converted = as_dc_instance(conventional)
assert converted == PowerDC(IntegerDC(4), 3, 1)
assert converted != source
Application-defined Record conventions
Bindings MAY provide explicit application-defined conversions between conventional types and Record DCIs. Such conventions do not define additional DCTs. For example, an application can choose a date Record with year, month, and day fields, or include calendar metadata according to its requirements. This specification does not prescribe a date schema or calendar.
A binding offering custom conversion SHOULD support recursive conversion within supported constituent structures and document handler selection, validation, precedence, and error behavior. The application is responsible for its Record schema and for the meaning and validity of the conventional result. A valid Record DCI need not satisfy an application’s schema.
Application conversion MUST NOT change DCI equality or canonical encoding. Canonical decoding MUST reconstruct the DCI independently of application conversion handlers. A Record name alone MUST NOT cause implicit execution of application code during decoding. Custom conversion is a separate explicit operation. Registries and executable handlers are not DCI constituents.
The Python binding provides an explicit ConversionRegistry with exact
conventional-type matching and exact Record-name matching. Other bindings may
use idiomatic mechanisms with separately documented policies. Cross-language
interoperability of an application convention requires agreement on the Record
name, field names, DCTs, order, and application meaning; matching language class
names is not sufficient.
Independent implementation review
Cross-language tests SHOULD cover the required stored Power results, mandatory equality cases, operand-order preservation, empty and singleton constructors, float separation and rounding, zero denominators, negative-base domains, collection multiplicities, and conversions that change DCI identity.
Tests MUST distinguish numerical agreement from equal stored conventional forms and from equal DCIs. Matching numerical values alone do not imply matching canonical bytes after conversion.
Before finalization, the Python reference implementation and independent bindings should be audited for component validation in ComplexNumber, documented behavior beyond the mandatory exact-comparison foundation, floating-point overflow and signed zero, and resource-limit reporting. Reference implementation behavior is evidence for conformance, not permission to silently relax a normative requirement.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.