feat!: Use num-bigint for large numbers (#500)

- Supply chain: Use the popular num-bigint crate for handling large integers
- Optimization: Handle f64, i64, u64 directly. These will be the most common instances of a number.

OPA number semantics isn't clear.
https://github.com/open-policy-agent/opa/issues/6281

As part of this change, we update the following failing tests:
- A local test that relies on what 15.3/3 evaluates to.
 With our current change, we round in a different direction than what OPA does, but consistent
 with Rust. We produce 5.1000000000000005 where as the OPA test expects 5.1.
 There is no clear definition in Rego of what the right answer is. Moreover, policies should not
 rely on exact floating point value comparison. Therefore this deviations is justified.
 The test is patched to pass.
- Another local vm test that exercised 1.1 + 2.2
- Another local vm test that exercises 5.5 - 2.2
- An OPA test that expects that a large integer number say 10e308 is printed in exponent notation.
 num-bigint does not print using scientific notation and instead prints all the digits.
 The benefit of preserving this compatibility is not clear. We skip this test.
- Doc tests that exercised handling floating point numbers with more than 15 (what f64 supports)
  digits of precision. There is no usecase for this scenario. The tests are updated to reflect
  the behavior.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2025-12-01 13:25:02 -06:00
committed by GitHub
parent 14deaaa5b6
commit a8a3a9809b
14 changed files with 981 additions and 472 deletions

51
Cargo.lock generated
View File

@@ -175,9 +175,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.2.46"
version = "1.2.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36"
checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -270,7 +270,7 @@ dependencies = [
"heck",
"proc-macro2 1.0.103",
"quote 1.0.42",
"syn 2.0.110",
"syn 2.0.111",
]
[[package]]
@@ -383,7 +383,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.42",
"syn 2.0.110",
"syn 2.0.111",
]
[[package]]
@@ -1131,7 +1131,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.42",
"syn 2.0.110",
"syn 2.0.111",
]
[[package]]
@@ -1197,11 +1197,12 @@ dependencies = [
"lazy_static",
"mimalloc",
"msvc_spectre_libs",
"num-bigint",
"num-traits",
"num_cpus",
"prettydiff",
"rand",
"regex",
"scientific",
"semver",
"serde",
"serde_json",
@@ -1234,12 +1235,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scientific"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38a4b339a8de779ecb098a772ecbba2ace74e23ed959a5b4f30631d8bf1799a8"
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -1279,7 +1274,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.42",
"syn 2.0.110",
"syn 2.0.111",
]
[[package]]
@@ -1357,9 +1352,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.110"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.42",
@@ -1374,7 +1369,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.42",
"syn 2.0.110",
"syn 2.0.111",
]
[[package]]
@@ -1406,7 +1401,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.42",
"syn 2.0.110",
"syn 2.0.111",
]
[[package]]
@@ -1587,7 +1582,7 @@ dependencies = [
"bumpalo",
"proc-macro2 1.0.103",
"quote 1.0.42",
"syn 2.0.110",
"syn 2.0.111",
"wasm-bindgen-shared",
]
@@ -1640,7 +1635,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.42",
"syn 2.0.110",
"syn 2.0.111",
]
[[package]]
@@ -1651,7 +1646,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.42",
"syn 2.0.110",
"syn 2.0.111",
]
[[package]]
@@ -1738,28 +1733,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.42",
"syn 2.0.110",
"syn 2.0.111",
"synstructure",
]
[[package]]
name = "zerocopy"
version = "0.8.27"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.27"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.42",
"syn 2.0.110",
"syn 2.0.111",
]
[[package]]
@@ -1779,7 +1774,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.42",
"syn 2.0.110",
"syn 2.0.111",
"synstructure",
]
@@ -1813,5 +1808,5 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.42",
"syn 2.0.110",
"syn 2.0.111",
]

View File

@@ -22,7 +22,7 @@ doctest = false
[features]
default = ["full-opa", "arc", "rvm"]
arc = ["scientific/arc"]
arc = []
ast = []
azure_policy = ["dep:jsonschema", "arc", "dashmap"]
azure-rbac = []
@@ -101,7 +101,8 @@ lazy_static = { version = "1.4.0", default-features = false }
thiserror = { version = "2.0", default-features = false }
data-encoding = { version = "2.8.0", optional = true, default-features=false, features = ["alloc"] }
scientific = { version = "0.5.3", default-features = false }
num-bigint = { version = "0.4", default-features = false }
num-traits = { version = "0.2", default-features = false }
globset = { version = "0.4.16", features = ["simd-accel"], default-features = false, optional = true }
regex = {version = "1.11.1", optional = true, default-features = false }

View File

@@ -172,9 +172,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.46"
version = "1.2.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36"
checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -211,18 +211,18 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.52"
version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8"
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.52"
version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1"
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
dependencies = [
"anstream",
"anstyle",
@@ -395,9 +395,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "heck"
@@ -533,12 +533,12 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.12.0"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
"equivalent",
"hashbrown 0.16.0",
"hashbrown 0.16.1",
]
[[package]]
@@ -965,9 +965,10 @@ dependencies = [
"lazy_static",
"mimalloc",
"msvc_spectre_libs",
"num-bigint",
"num-traits",
"rand",
"regex",
"scientific",
"semver",
"serde",
"serde_json",
@@ -1013,12 +1014,6 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "scientific"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38a4b339a8de779ecb098a772ecbba2ace74e23ed959a5b4f30631d8bf1799a8"
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -1128,9 +1123,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.110"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",
@@ -1471,18 +1466,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.27"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.27"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -109,9 +109,9 @@ checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
[[package]]
name = "cc"
version = "1.2.46"
version = "1.2.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36"
checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -273,9 +273,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "iana-time-zone"
@@ -405,9 +405,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.12.0"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
"equivalent",
"hashbrown",
@@ -840,9 +840,10 @@ dependencies = [
"lazy_static",
"mimalloc",
"msvc_spectre_libs",
"num-bigint",
"num-traits",
"rand",
"regex",
"scientific",
"semver",
"serde",
"serde_json",
@@ -883,12 +884,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scientific"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38a4b339a8de779ecb098a772ecbba2ace74e23ed959a5b4f30631d8bf1799a8"
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -983,9 +978,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "syn"
version = "2.0.110"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",
@@ -1362,18 +1357,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.27"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.27"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -103,9 +103,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
[[package]]
name = "cc"
version = "1.2.46"
version = "1.2.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36"
checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -251,9 +251,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "heck"
@@ -389,9 +389,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.12.0"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
"equivalent",
"hashbrown",
@@ -899,9 +899,10 @@ dependencies = [
"lazy_static",
"mimalloc",
"msvc_spectre_libs",
"num-bigint",
"num-traits",
"rand",
"regex",
"scientific",
"semver",
"serde",
"serde_json",
@@ -934,12 +935,6 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "scientific"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38a4b339a8de779ecb098a772ecbba2ace74e23ed959a5b4f30631d8bf1799a8"
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -1034,9 +1029,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "syn"
version = "2.0.110"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",
@@ -1311,18 +1306,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.27"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.27"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -123,9 +123,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
[[package]]
name = "cc"
version = "1.2.46"
version = "1.2.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36"
checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -303,9 +303,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "iana-time-zone"
@@ -435,9 +435,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.12.0"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
"equivalent",
"hashbrown",
@@ -942,13 +942,15 @@ dependencies = [
"lazy_static",
"mimalloc",
"msvc_spectre_libs",
"num-bigint",
"num-traits",
"rand",
"regex",
"scientific",
"semver",
"serde",
"serde_json",
"serde_yaml",
"smol_str",
"thiserror",
"url",
"uuid",
@@ -982,12 +984,6 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "scientific"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38a4b339a8de779ecb098a772ecbba2ace74e23ed959a5b4f30631d8bf1799a8"
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -1097,6 +1093,15 @@ version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "smol_str"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead"
dependencies = [
"serde",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
@@ -1105,9 +1110,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "syn"
version = "2.0.110"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",
@@ -1376,18 +1381,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.27"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.27"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -103,9 +103,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
[[package]]
name = "cc"
version = "1.2.46"
version = "1.2.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36"
checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -266,9 +266,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "iana-time-zone"
@@ -398,9 +398,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.12.0"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
"equivalent",
"hashbrown",
@@ -821,9 +821,10 @@ dependencies = [
"lazy_static",
"mimalloc",
"msvc_spectre_libs",
"num-bigint",
"num-traits",
"rand",
"regex",
"scientific",
"semver",
"serde",
"serde_json",
@@ -867,12 +868,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scientific"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38a4b339a8de779ecb098a772ecbba2ace74e23ed959a5b4f30631d8bf1799a8"
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -967,9 +962,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "syn"
version = "2.0.110"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",
@@ -1313,18 +1308,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.27"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.27"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -1,6 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::borrow::Cow;
use core::str::FromStr;
use crate::ast::{Expr, Ref};
use crate::builtins;
use crate::builtins::utils::{ensure_args_count, ensure_string};
@@ -88,25 +91,21 @@ fn parse(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
_ => (string, ""),
};
// Propagating the underlying error is not useful here.
let v: Value = if number_part.starts_with('.') {
serde_json::from_str(format!("0{number_part}").as_str())
} else {
serde_json::from_str(number_part)
}
.map_err(|_| params[0].span().error("could not parse number"))?;
let mut n = match v {
Value::Number(n) => n.clone(),
_ => bail!(span.error("could not parse number")),
};
// Canonicalize quirky literals (e.g. ".5", "-.1") so downstream parsing always sees
// a stable representation that includes an explicit leading digit.
let canonical_part = canonicalize_number_part(number_part);
if let Some(e) = ten_exp(suffix) {
n.mul_assign(&Number::ten_pow(e)?)?;
Ok(Value::from(n))
let combined = combine_decimal_exponent(canonical_part.as_ref(), e)
.ok_or_else(|| params[0].span().error("could not parse number"))?;
let number = Number::from_str(&combined)
.map_err(|_| params[0].span().error("could not parse number"))?;
Ok(Value::from(number))
} else if let Some(e) = two_exp(suffix) {
n.mul_assign(&Number::two_pow(e)?)?;
Ok(Value::from(n))
let mut number = Number::from_str(canonical_part.as_ref())
.map_err(|_| params[0].span().error("could not parse number"))?;
number.mul_assign(&Number::two_pow(e)?)?;
Ok(Value::from(number))
} else {
Ok(Value::Undefined)
}
@@ -166,28 +165,83 @@ fn parse_bytes(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool)
_ => (string, ""),
};
let v: Value = match if number_part.starts_with('.') {
serde_json::from_str(format!("0{number_part}").as_str())
} else {
serde_json::from_str(number_part)
} {
Ok(v) => v,
Err(_) if strict => bail!(span.error("could not parse number")),
_ => return Ok(Value::Undefined),
};
let mut n = match v {
Value::Number(n) => n.clone(),
_ => bail!(span.error("could not parse number")),
};
// Canonicalize quirky literals (e.g. ".5", "-.1") so downstream parsing always sees
// a stable representation that includes an explicit leading digit.
let canonical_part = canonicalize_number_part(number_part);
if let Some(e) = twob_exp(suffix) {
n.mul_assign(&Number::two_pow(e)?)?;
Ok(Value::from(n.round()))
let mut number = match Number::from_str(canonical_part.as_ref()) {
Ok(n) => n,
Err(_) if strict => bail!(span.error("could not parse number")),
Err(_) => return Ok(Value::Undefined),
};
number.mul_assign(&Number::two_pow(e)?)?;
Ok(Value::from(number.round()))
} else if let Some(e) = tenb_exp(suffix) {
n.mul_assign(&Number::ten_pow(e)?)?;
Ok(Value::from(n.round()))
let combined = match combine_decimal_exponent(canonical_part.as_ref(), e) {
Some(s) => s,
None if strict => bail!(span.error("could not parse number")),
None => return Ok(Value::Undefined),
};
let number = match Number::from_str(&combined) {
Ok(n) => n,
Err(_) if strict => bail!(span.error("could not parse number")),
Err(_) => return Ok(Value::Undefined),
};
Ok(Value::from(number.round()))
} else {
Ok(Value::Undefined)
}
}
// Normalizes numbers that start with a decimal point by injecting the implicit leading zero.
// This means inputs like ".5" and "-.1" become "0.5" / "-0.1", which Number::from_str accepts.
fn canonicalize_number_part(number_part: &str) -> Cow<'_, str> {
if let Some(rest) = number_part.strip_prefix("-.") {
Cow::Owned(format!("-0.{rest}"))
} else if let Some(rest) = number_part.strip_prefix("+.") {
Cow::Owned(format!("+0.{rest}"))
} else if let Some(rest) = number_part.strip_prefix('.') {
Cow::Owned(format!("0.{rest}"))
} else {
Cow::Borrowed(number_part)
}
}
// Adds the unit-derived exponent (from suffix) to any exponent already present in the literal.
// Produces a canonical `mantissa eX` string or returns None when the combined exponent overflows.
fn combine_decimal_exponent(number: &str, suffix_exp: i32) -> Option<String> {
if suffix_exp == 0 {
return Some(number.to_string());
}
let (mantissa, base_exp) = match split_decimal_exponent(number) {
Some(parts) => parts,
None => (number, 0),
};
if mantissa.is_empty() {
return None;
}
let combined_exp = base_exp.checked_add(suffix_exp)?;
if combined_exp == 0 {
Some(mantissa.to_string())
} else {
Some(format!("{}e{}", mantissa, combined_exp))
}
}
// Splits scientific notation ("1.2e3") into mantissa/exponent so callers can adjust the exponent.
// Returns None when no exponent exists or the tail cannot be parsed into a 32-bit integer.
fn split_decimal_exponent(number: &str) -> Option<(&str, i32)> {
let idx = number.find(['e', 'E'])?;
let mantissa = &number[..idx];
let exp_part = &number[idx + 1..];
if exp_part.is_empty() {
return None;
}
let exponent = exp_part.parse::<i32>().ok()?;
Some((mantissa, exponent))
}

File diff suppressed because it is too large Load Diff

View File

@@ -575,35 +575,32 @@ impl From<f64> for Value {
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// assert_eq!(
/// Value::from(3.141592653589793),
/// Value::from(3.5f64),
/// Value::from_numeric_string("3.5")?);
/// # Ok(())
/// # }
/// ```
///
/// [`Value::Number`] stores floating-point values as `f64`, so it inherits the same
/// ~15-digit precision limit. Adding additional digits to either the literal or a parsed
/// numeric string causes both to round to the same `f64` value.
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let from_float = Value::from(3.141592653589793238462f64);
/// let from_string = Value::from_numeric_string("3.141592653589793238462")?;
/// assert_eq!(from_float, from_string);
///
/// // All representations round to approximately 15 digits.
/// assert_eq!(
/// from_float,
/// Value::from_numeric_string("3.141592653589793")?);
/// # Ok(())
/// # }
/// ```
///
/// Note, f64 can store only around 15 digits of precision whereas [`Value::Number`]
/// can store arbitrary precision. Adding an extra digit to the f64 literal in the above
/// example causes loss of precision and the Value created from f64 does not match the
/// Value parsed from numeric string (which is more precise).
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// // The last digit is lost in f64.
/// assert_ne!(
/// Value::from(3.1415926535897932),
/// Value::from_numeric_string("3.141592653589793232")?);
///
/// // The value, in this case is equal to parsing the json number with last digit omitted.
/// assert_ne!(
/// Value::from(3.1415926535897932),
/// Value::from_numeric_string("3.14159265358979323")?);
/// # Ok(())
/// # }
/// ```
///
/// If precision is important, it is better to construct numeric values from strings instead
/// of f64 when possible.
/// See [Value::from_numeric_string]
/// If additional precision is required, keep the raw data as strings or use an external
/// arbitrary-precision numeric type before converting it into [`Value`].
fn from(n: f64) -> Self {
Value::Number(Number::from(n))
}

View File

@@ -25,7 +25,7 @@ cases:
}
query: data.test
want_result:
d: 5.1
d: 5.1000000000000005 # 15.3/3 in IEEE754 rounds slightly above 5.1; keep the literal parsed value for parity with runtime results.
e: 3.25
- note: non-numeric

View File

@@ -258,6 +258,11 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
(Ok(actual), Some(expected)) if &actual == expected => {
entry.0 += 1;
}
(Ok(_), Some(_)) if case.note == "strings/sprintf: float too big" => {
// OPA renders large floats in scientific notation while Regorus emits full decimal digits.
// There is no clear benefit in forcing parity for this presentation-only difference.
entry.0 += 1;
}
(Ok(actual), None)
if actual == Value::new_array()
&& case.want_error.is_none()

View File

@@ -152,7 +152,7 @@ cases:
- "Load { dest: 1, literal_idx: 1 }"
- "Add { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: 0.3
want_result: 0.30000000000000004
- note: arithmetic_large_numbers
description: Test arithmetic with large numbers

View File

@@ -205,7 +205,7 @@ cases:
- "Load { dest: 1, literal_idx: 1 }"
- "Add { dest: 2, left: 0, right: 1 }"
- "Return { value: 2 }"
want_result: 3.3
want_result: 3.3000000000000003
- note: number_sub_precision
description: Test Number type subtraction preserves precision