Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/analysis/numeric_policy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ function validate_policy(spec::NumericPolicySpec)
end

function assert_mode(spec::NumericPolicySpec, mode::Symbol)
spec.mode === mode || throw(UnsupportedRepresentationError(
"mode $mode", "policy is $(spec.mode)",
"this analysis; select the required mode explicitly or use the existing ordinary workflow"))
spec.mode === mode || throw(ArgumentError(
"this analysis requires numeric mode '$mode' but the policy is '$(spec.mode)'; " *
"select the required mode explicitly or use the existing ordinary workflow"))
spec
end

Expand Down
7 changes: 6 additions & 1 deletion src/canonical.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const SAFE_INT_MAX = 2^53 - 1
jcs_bytes(x) -> Vector{UInt8}

The RFC 8785 canonical form of `x`, as bytes. Accepts the JSON-shaped
values the server produces: `Dict` and `JSON3.Object` (key order is
values the server produces: `Dict`, `JSON3.Object` and `NamedTuple` (key order is
irrelevant; it is sorted away), `AbstractVector` and `JSON3.Array` (order
is preserved, as JCS preserves array order), `AbstractString`, `Integer`,
`Float16`, `Float32`, `Float64`, `Bool` and `nothing`. Floats serialize per ECMAScript's
Expand Down Expand Up @@ -120,6 +120,11 @@ function _write_char!(io, c::Char)
end
end

# NamedTuple: the same object rules as a Dict. Records in the statistics layer are
# built as named tuples, and a named tuple has unique symbol keys by construction.
_write!(io, x::NamedTuple) =
_write!(io, Dict{String,Any}(String(k) => v for (k, v) in pairs(x)))

# AbstractDict (Dict and JSON3.Object alike): sorted keys, then each pair.
function _write!(io, d::AbstractDict)
print(io, '{')
Expand Down
5 changes: 5 additions & 0 deletions test/unit/test_canonical.jl
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ end
@test_throws ArgumentError jcs_bytes(invalid)
@test_throws ArgumentError jcs_bytes(Dict(invalid => 1))
@test_throws ArgumentError jcs_bytes(BigFloat(1) + eps(BigFloat))
# Records in the statistics layer are built as named tuples; a named tuple is
# an object with unique keys, so it must hash exactly like the equivalent Dict.
@test jcs_bytes((b = 2, a = 1)) == jcs_bytes(Dict("a" => 1, "b" => 2))
@test String(jcs_bytes((; a = [1, nothing], b = "s"))) == "{\"a\":[1,null],\"b\":\"s\"}"
@test_throws ArgumentError jcs_bytes((a = BigFloat(1) + eps(BigFloat),))
@test String(jcs_bytes([Float16(1.5), Float32(1.5), Float64(1.5)])) == "[1.5,1.5,1.5]"
for T in (UInt8, UInt16, UInt32, UInt64, UInt128)
@test String(jcs_bytes(T(1))) == "1"
Expand Down
4 changes: 3 additions & 1 deletion test/unit/test_numeric_policy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ end
tight = numeric_policy(mode=:exact_counts, max_denominator_bits=32)
@test_throws ResourceLimitError exact_fraction(1, big(2)^40; policy=tight)
@test_throws ResourceLimitError exact_fraction(big(2)^40, 1; policy=tight)
# Each addend fits the 32-bit budget on its own; their sum 1/2^31 + 1/3 needs
# 33 bits, so the limit must fire on the running total, not only on inputs.
@test_throws ResourceLimitError exact_rational_sum(
[big(1)//big(2)^31, big(1)//big(2)^31 + big(1)//3]; policy=tight)
[big(1)//big(2)^31, big(1)//big(3)]; policy=tight)
end

@testset "Precision scopes and restoration" begin
Expand Down
Loading