smarter_json 1.2.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +2 -2
- data/ext/smarter_json/smarter_json.c +17 -16
- data/ext/smarter_json/vendor/eisel_lemire.md +1 -1
- data/lib/smarter_json/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a31a7485e53042c06cb28c92f23e24cb55067fd312704c0c9ac0776868f20293
|
|
4
|
+
data.tar.gz: 531187f8f7ea38f573785f09fa6e217f63a4393fd537d80c92f00973aeb2b375
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f88266d06416277355f770645d694be6e81a8faef22a86806cd1e4620e773dee50aeeeeb09cfbc0de47354972250c2654e599c5c03ba0926ca129d9d6a638b0c
|
|
7
|
+
data.tar.gz: c8bdd4a34d4617c897c815f2526c0f5555459b35bad47106a1db2b71dedac5d7a2605d0a5f92676735ae4895208fcdf1272f05dce09026aa696b84f143693ee5
|
data/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
> ⚠️ We discourage the use of `process(input).first` / `process(input)[0]` because it silently drops potential additional documents
|
|
14
14
|
> Please use `process_one` if you are expecting only one JSON doc, e.g. in API payloads, because it emits on_warning if it finds multiple docs.
|
|
15
15
|
|
|
16
|
+
## 1.2.2 (unreleased)
|
|
17
|
+
|
|
18
|
+
RSpec tests: 1,167
|
|
19
|
+
|
|
20
|
+
- The Eisel-Lemire fast path for `decimal_precision: :float` now covers decimals with **up to 19 significant digits** (was 18). 19 digits is the most that fits exactly in a `uint64` (max 19-digit ≈ 1.0e19 < `UINT64_MAX` ≈ 1.8e19), so these no longer fall back to the slower `strtod`. Still correctly rounded, bit-for-bit identical to the stdlib — verified across 18/19-digit round-to-even tie shapes.
|
|
21
|
+
|
|
16
22
|
## 1.2.1 (2026-06-17)
|
|
17
23
|
|
|
18
24
|
RSpec tests: 1,165
|
data/README.md
CHANGED
|
@@ -130,10 +130,10 @@ SmarterJSON.process_file("config.json5") # read a file, then process
|
|
|
130
130
|
|
|
131
131
|
## Usage in APIs
|
|
132
132
|
|
|
133
|
-
At an API boundary the JSON comes from someone you don't control — a client
|
|
133
|
+
At an API boundary the JSON comes from someone you don't control — a client sending a request body to *your* service, or an upstream service answering a call *you* made — and it isn't always clean: a stray trailing comma, a `NaN`, a payload wrapped in prose, or a quiet change to the format. A strict parser turns any of those into an exception (a request you reject, or a failed call chain). SmarterJSON extracts the data that's there instead, so one formatting quirk doesn't sink the whole request:
|
|
134
134
|
|
|
135
135
|
```ruby
|
|
136
|
-
# Inbound — JSON a caller sent to your
|
|
136
|
+
# Inbound — JSON a caller sent to your service:
|
|
137
137
|
data = SmarterJSON.process(request.body)
|
|
138
138
|
|
|
139
139
|
# Outbound — JSON from a service you called:
|
|
@@ -629,14 +629,15 @@ static VALUE fj_float_strtod(const char *p, long n) {
|
|
|
629
629
|
/* e10 is the final base-10 exponent (already adjusted by the fraction length). */
|
|
630
630
|
static FJ_ALWAYS_INLINE VALUE fj_float_from_parts(fj_state *st, uint64_t m10, int m10digits, int64_t e10, int neg, int overflow, const char *p, long n) {
|
|
631
631
|
double d;
|
|
632
|
-
/* Fast path by mantissa width (our scanner accumulates m10 exactly up to
|
|
632
|
+
/* Fast path by mantissa width (our scanner accumulates m10 exactly up to 19
|
|
633
633
|
digits, flagging overflow beyond):
|
|
634
|
-
1..
|
|
635
|
-
(Mushtak-Lemire).
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
634
|
+
1..19 digits -> Eisel-Lemire, correctly-rounded for any exact uint64 mantissa
|
|
635
|
+
(Mushtak-Lemire). 19 digits is the most that fits exactly in a
|
|
636
|
+
uint64 (max 19-digit ~1.0e19 < UINT64_MAX ~1.8e19); this pulls
|
|
637
|
+
full-double-precision data (e.g. citylots coordinates) off the
|
|
638
|
+
slow strtod fallback — the stdlib json gem still strtods it.
|
|
639
|
+
>19 digits / overflow / extreme exponent -> strtod (round-to-odd). */
|
|
640
|
+
if (!overflow && m10digits >= 1 && m10digits <= 19 && (long)m10digits + e10 >= -307) {
|
|
640
641
|
if (m10 == 0) return rb_float_new(neg ? -0.0 : 0.0);
|
|
641
642
|
d = fj_eisel_lemire_s2d(e10, m10, neg);
|
|
642
643
|
} else {
|
|
@@ -683,7 +684,7 @@ static int fj_try_decimal(fj_state *st, const char *p, long n, VALUE *out) {
|
|
|
683
684
|
for (;;) {
|
|
684
685
|
while (i < n && p[i] >= '0' && p[i] <= '9') {
|
|
685
686
|
had_leading_zero = 1;
|
|
686
|
-
if (m10digits <
|
|
687
|
+
if (m10digits < 19) { m10 = m10 * 10 + (uint64_t)(p[i] - '0'); m10digits++; }
|
|
687
688
|
else overflow = 1;
|
|
688
689
|
i++;
|
|
689
690
|
}
|
|
@@ -695,7 +696,7 @@ static int fj_try_decimal(fj_state *st, const char *p, long n, VALUE *out) {
|
|
|
695
696
|
has_digit = 1;
|
|
696
697
|
for (;;) {
|
|
697
698
|
while (i < n && p[i] >= '0' && p[i] <= '9') {
|
|
698
|
-
if (m10digits <
|
|
699
|
+
if (m10digits < 19) { m10 = m10 * 10 + (uint64_t)(p[i] - '0'); m10digits++; }
|
|
699
700
|
else overflow = 1;
|
|
700
701
|
i++;
|
|
701
702
|
}
|
|
@@ -710,7 +711,7 @@ static int fj_try_decimal(fj_state *st, const char *p, long n, VALUE *out) {
|
|
|
710
711
|
for (;;) {
|
|
711
712
|
while (i < n && p[i] >= '0' && p[i] <= '9') {
|
|
712
713
|
has_digit = 1;
|
|
713
|
-
if (m10digits <
|
|
714
|
+
if (m10digits < 19) { m10 = m10 * 10 + (uint64_t)(p[i] - '0'); m10digits++; frac++; }
|
|
714
715
|
else overflow = 1;
|
|
715
716
|
i++;
|
|
716
717
|
}
|
|
@@ -774,7 +775,7 @@ static VALUE fj_parse_number(fj_state *st) {
|
|
|
774
775
|
long nlen;
|
|
775
776
|
int is_float = 0, neg = 0, overflow = 0, has_sign = 0, had_leading_zero = 0;
|
|
776
777
|
uint64_t m10 = 0; /* mantissa: integer + fraction digits */
|
|
777
|
-
int m10digits = 0; /* mantissa digit chars (caps the Eisel-Lemire fast path at
|
|
778
|
+
int m10digits = 0; /* mantissa digit chars (caps the Eisel-Lemire fast path at 19) */
|
|
778
779
|
int frac = 0; /* fraction digit chars: e10 -= frac */
|
|
779
780
|
int64_t e10 = 0;
|
|
780
781
|
|
|
@@ -810,7 +811,7 @@ static VALUE fj_parse_number(fj_state *st) {
|
|
|
810
811
|
for (;;) {
|
|
811
812
|
while (*p >= '0' && *p <= '9') {
|
|
812
813
|
had_leading_zero = 1;
|
|
813
|
-
if (m10digits <
|
|
814
|
+
if (m10digits < 19) { m10 = m10 * 10 + (uint64_t)(*p - '0'); m10digits++; }
|
|
814
815
|
else overflow = 1;
|
|
815
816
|
p++;
|
|
816
817
|
}
|
|
@@ -821,7 +822,7 @@ static VALUE fj_parse_number(fj_state *st) {
|
|
|
821
822
|
} else if (*p >= '1' && *p <= '9') {
|
|
822
823
|
for (;;) {
|
|
823
824
|
while (*p >= '0' && *p <= '9') {
|
|
824
|
-
if (m10digits <
|
|
825
|
+
if (m10digits < 19) { m10 = m10 * 10 + (uint64_t)(*p - '0'); m10digits++; }
|
|
825
826
|
else overflow = 1;
|
|
826
827
|
p++;
|
|
827
828
|
}
|
|
@@ -841,7 +842,7 @@ static VALUE fj_parse_number(fj_state *st) {
|
|
|
841
842
|
p++;
|
|
842
843
|
for (;;) {
|
|
843
844
|
while (*p >= '0' && *p <= '9') {
|
|
844
|
-
if (m10digits <
|
|
845
|
+
if (m10digits < 19) { m10 = m10 * 10 + (uint64_t)(*p - '0'); m10digits++; frac++; }
|
|
845
846
|
else overflow = 1;
|
|
846
847
|
p++;
|
|
847
848
|
}
|
|
@@ -1245,7 +1246,7 @@ static int fj_try_member_number(fj_state *st, VALUE *out) {
|
|
|
1245
1246
|
} else if (*p >= '1' && *p <= '9') {
|
|
1246
1247
|
for (;;) {
|
|
1247
1248
|
while (*p >= '0' && *p <= '9') {
|
|
1248
|
-
if (FJ_LIKELY(m10digits <
|
|
1249
|
+
if (FJ_LIKELY(m10digits < 19)) { m10 = m10 * 10 + (uint64_t)(*p - '0'); m10digits++; }
|
|
1249
1250
|
else overflow = 1;
|
|
1250
1251
|
p++;
|
|
1251
1252
|
}
|
|
@@ -1259,7 +1260,7 @@ static int fj_try_member_number(fj_state *st, VALUE *out) {
|
|
|
1259
1260
|
is_float = 1; p++;
|
|
1260
1261
|
for (;;) {
|
|
1261
1262
|
while (*p >= '0' && *p <= '9') {
|
|
1262
|
-
if (FJ_LIKELY(m10digits <
|
|
1263
|
+
if (FJ_LIKELY(m10digits < 19)) { m10 = m10 * 10 + (uint64_t)(*p - '0'); m10digits++; frac++; }
|
|
1263
1264
|
else overflow = 1;
|
|
1264
1265
|
p++;
|
|
1265
1266
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
## What smarter_json uses it for
|
|
7
7
|
|
|
8
|
-
`fj_float_from_parts` (in `smarter_json.c`) routes `m10digits ≤
|
|
8
|
+
`fj_float_from_parts` (in `smarter_json.c`) routes `m10digits ≤ 19 → fj_eisel_lemire_s2d`, and `> 19 / overflow / extreme exponent → strtod` (round-to-odd). Eisel-Lemire is correctly-rounded across the whole ≤19-digit range — every mantissa that fits exactly in a uint64, no round-to-even tie loss — **and** fast on the common short-mantissa case.
|
|
9
9
|
|
|
10
10
|
## These two files are DERIVED, not verbatim copies
|
|
11
11
|
|
data/lib/smarter_json/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smarter_json
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.2.
|
|
4
|
+
version: 1.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tilo Sloboda
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-06-
|
|
10
|
+
date: 2026-06-19 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: bigdecimal
|