• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tari-project / tari / 17581942656

09 Sep 2025 12:03PM UTC coverage: 60.905% (-0.01%) from 60.917%
17581942656

push

github

web-flow
docs: http api documentation (#7484)

Description
---

Documentation for the various API calls for HTTP.

Motivation and Context
---

Provide adequate documentation for the use of the HTTP API calls.

How Has This Been Tested?
---
All commands have been run on actual data, aside from
`sync_utxos_by_block`.

What process can a PR reviewer use to test or verify this change?
---

Breaking Changes
---

- [x] None
- [ ] Requires data directory on base node to be deleted
- [ ] Requires hard fork
- [ ] Other - Please specify


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- Documentation
  - Added a comprehensive HTTP API guide for the Minotari Base Node.
- Describes base URL and default port, plus eight endpoints with
methods, paths, parameters, and response schemas.
- Includes detailed JSON and curl examples, testing instructions, and
notes on hex-encoded byte fields and nested structures.
- Documents pagination for syncing UTXOs by block and states that no
authentication is required.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

73900 of 121337 relevant lines covered (60.9%)

295677.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

44.35
/base_layer/core/src/base_node/metrics.rs
1
//  Copyright 2022, The Tari Project
2
//
3
//  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4
//  following conditions are met:
5
//
6
//  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7
//  disclaimer.
8
//
9
//  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
10
//  following disclaimer in the documentation and/or other materials provided with the distribution.
11
//
12
//  3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
13
//  products derived from this software without specific prior written permission.
14
//
15
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16
//  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
//  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18
//  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19
//  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20
//  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
21
//  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22

23
use once_cell::sync::Lazy;
24
use primitive_types::U512;
25
use tari_common_types::types::FixedHash;
26
use tari_metrics::{Gauge, IntCounter, IntCounterVec, IntGauge, IntGaugeVec};
27
use tari_utilities::hex::Hex;
28

29
pub fn tip_height() -> &'static IntGauge {
×
30
    static METER: Lazy<IntGauge> = Lazy::new(|| {
×
31
        tari_metrics::register_int_gauge("base_node::blockchain::tip_height", "The current tip height").unwrap()
×
32
    });
×
33

34
    &METER
×
35
}
×
36

37
pub fn target_difficulty_sha() -> &'static IntGauge {
×
38
    static METER: Lazy<IntGauge> = Lazy::new(|| {
×
39
        tari_metrics::register_int_gauge(
×
40
            "base_node::blockchain::target_difficulty_sha",
×
41
            "The current miner target difficulty for the sha3 PoW algo",
×
42
        )
×
43
        .unwrap()
×
44
    });
×
45

46
    &METER
×
47
}
×
48

49
pub fn target_difficulty_monero_randomx() -> &'static IntGauge {
×
50
    static METER: Lazy<IntGauge> = Lazy::new(|| {
×
51
        tari_metrics::register_int_gauge(
×
52
            "base_node::blockchain::target_difficulty_monero",
×
53
            "The current miner target difficulty for the monero PoW algo",
×
54
        )
×
55
        .unwrap()
×
56
    });
×
57

58
    &METER
×
59
}
×
60
pub fn target_difficulty_tari_randomx() -> &'static IntGauge {
×
61
    static METER: Lazy<IntGauge> = Lazy::new(|| {
×
62
        tari_metrics::register_int_gauge(
×
63
            "base_node::blockchain::target_difficulty_tari_rx",
×
64
            "The current miner target difficulty for the tari rx PoW algo",
×
65
        )
×
66
        .unwrap()
×
67
    });
×
68

69
    &METER
×
70
}
×
71

72
pub fn target_difficulty_cuckaroo() -> &'static IntGauge {
×
73
    static METER: Lazy<IntGauge> = Lazy::new(|| {
×
74
        tari_metrics::register_int_gauge(
×
75
            "base_node::blockchain::target_difficulty_cuckaroo",
×
76
            "The current miner target difficulty for the cuckaroo PoW algo",
×
77
        )
×
78
        .unwrap()
×
79
    });
×
80

81
    &METER
×
82
}
×
83

84
/// The target difficulty at the given height
85
pub fn target_difficulty() -> &'static IntGauge {
×
86
    static METER: Lazy<IntGauge> = Lazy::new(|| {
×
87
        tari_metrics::register_int_gauge("base_node::blockchain::target_diff", "target_difficulty at height").unwrap()
×
88
    });
×
89

90
    &METER
×
91
}
×
92

93
/// The accumulated difficulty indicator (log_2 scale) at the given height
94
pub fn accumulated_difficulty_indicator() -> &'static IntGauge {
×
95
    static METER: Lazy<IntGauge> = Lazy::new(|| {
×
96
        tari_metrics::register_int_gauge(
×
97
            "base_node::blockchain::acc_diff_indicator",
×
98
            "log2(accumulated_difficulty at height) * 1000",
×
99
        )
×
100
        .unwrap()
×
101
    });
×
102

103
    &METER
×
104
}
×
105

106
/// The target difficulty indicator (log_2 scale) at the given height
107
pub fn target_difficulty_indicator() -> &'static IntGauge {
×
108
    static METER: Lazy<IntGauge> = Lazy::new(|| {
×
109
        tari_metrics::register_int_gauge(
×
110
            "base_node::blockchain::target_diff_indicator",
×
111
            "log2(target_difficulty at height) * 1000",
×
112
        )
×
113
        .unwrap()
×
114
    });
×
115

116
    &METER
×
117
}
×
118

119
/// The block height associated with the current difficulty indicators
120
pub fn difficulty_indicator_height() -> &'static IntGauge {
×
121
    static METER: Lazy<IntGauge> = Lazy::new(|| {
×
122
        tari_metrics::register_int_gauge(
×
123
            "base_node::blockchain::diff_indicator_height",
×
124
            "block height associated with difficulty indicators",
×
125
        )
×
126
        .unwrap()
×
127
    });
×
128
    &METER
×
129
}
×
130

131
/// floor(log2(total_accumulated_difficulty)) at height [reconstruction: (acc_diff_sig53 / 2^52) * 2^acc_diff_exp2]
132
pub fn accumulated_difficulty_exp2() -> &'static IntGauge {
×
133
    static METER: Lazy<IntGauge> = Lazy::new(|| {
×
134
        tari_metrics::register_int_gauge(
×
135
            "base_node::blockchain::acc_diff_exp2",
×
136
            "floor(log2(total_accumulated_difficulty)) at height [reconstruction: (acc_diff_sig53 / 2^52) * \
×
137
             2^acc_diff_exp2]",
×
138
        )
×
139
        .unwrap()
×
140
    });
×
141
    &METER
×
142
}
×
143

144
/// Top 53 bits of total_accumulated_difficulty at height [reconstruction: (acc_diff_sig53 / 2^52) * 2^acc_diff_exp2]
145
pub fn accumulated_difficulty_sig53() -> &'static IntGauge {
×
146
    static METER: Lazy<IntGauge> = Lazy::new(|| {
×
147
        tari_metrics::register_int_gauge(
×
148
            "base_node::blockchain::acc_diff_sig53",
×
149
            "Top 53 bits of total_accumulated_difficulty at height [reconstruction: (acc_diff_sig53 / 2^52) * \
×
150
             2^acc_diff_exp2]",
×
151
        )
×
152
        .unwrap()
×
153
    });
×
154
    &METER
×
155
}
×
156

157
/// Approximate total_accumulated_difficulty at height as an f64 [reconstruction: (acc_diff_sig53 / 2^52) *
158
/// 2^acc_diff_exp2] Note: This is less accurate than doing the computation directly in the client, but is provided for
159
/// convenience.
160
pub fn accumulated_difficulty_as_f64() -> &'static Gauge {
×
161
    static METER: Lazy<Gauge> = Lazy::new(|| {
×
162
        tari_metrics::register_gauge(
×
163
            "base_node::blockchain::acc_diff_as_f64",
×
164
            "Approximate total_accumulated_difficulty at height as an f64 [approximation: (acc_diff_sig53 / 2^52) * \
×
165
             2^acc_diff_exp2]",
×
166
        )
×
167
        .unwrap()
×
168
    });
×
169
    &METER
×
170
}
×
171

172
pub fn reorg(fork_height: u64, num_added: usize, num_removed: usize) -> IntGauge {
×
173
    static METER: Lazy<IntGaugeVec> = Lazy::new(|| {
×
174
        tari_metrics::register_int_gauge_vec("base_node::blockchain::reorgs", "Reorg stats", &[
×
175
            "fork_height",
×
176
            "num_added",
×
177
            "num_removed",
×
178
        ])
×
179
        .unwrap()
×
180
    });
×
181

182
    METER.with_label_values(&[
×
183
        &fork_height.to_string(),
×
184
        &num_added.to_string(),
×
185
        &num_removed.to_string(),
×
186
    ])
×
187
}
×
188

189
pub fn compact_block_tx_misses(height: u64) -> IntGauge {
×
190
    static METER: Lazy<IntGaugeVec> = Lazy::new(|| {
×
191
        tari_metrics::register_int_gauge_vec(
×
192
            "base_node::blockchain::compact_block_unknown_transactions",
×
193
            "Number of unknown transactions from the incoming compact block",
×
194
            &["height"],
×
195
        )
×
196
        .unwrap()
×
197
    });
×
198

199
    METER.with_label_values(&[&height.to_string()])
×
200
}
×
201

202
pub fn compact_block_full_misses(height: u64) -> IntCounter {
×
203
    static METER: Lazy<IntCounterVec> = Lazy::new(|| {
×
204
        tari_metrics::register_int_counter_vec(
×
205
            "base_node::blockchain::compact_block_miss",
×
206
            "Number of full blocks that had to be requested",
×
207
            &["height"],
×
208
        )
×
209
        .unwrap()
×
210
    });
×
211

212
    METER.with_label_values(&[&height.to_string()])
×
213
}
×
214

215
pub fn compact_block_mmr_mismatch(height: u64) -> IntCounter {
×
216
    static METER: Lazy<IntCounterVec> = Lazy::new(|| {
×
217
        tari_metrics::register_int_counter_vec(
×
218
            "base_node::blockchain::compact_block_mmr_mismatch",
×
219
            "Number of full blocks that had to be requested because of MMR mismatch",
×
220
            &["height"],
×
221
        )
×
222
        .unwrap()
×
223
    });
×
224

225
    METER.with_label_values(&[&height.to_string()])
×
226
}
×
227

228
pub fn orphaned_blocks() -> IntCounter {
×
229
    static METER: Lazy<IntCounter> = Lazy::new(|| {
×
230
        tari_metrics::register_int_counter(
×
231
            "base_node::blockchain::orphaned_blocks",
×
232
            "Number of valid orphan blocks accepted by the base node",
×
233
        )
×
234
        .unwrap()
×
235
    });
×
236

237
    METER.clone()
×
238
}
×
239

240
pub fn rejected_blocks(height: u64, hash: &FixedHash) -> IntCounter {
×
241
    static METER: Lazy<IntCounterVec> = Lazy::new(|| {
×
242
        tari_metrics::register_int_counter_vec(
×
243
            "base_node::blockchain::rejected_blocks",
×
244
            "Number of block rejected by the base node",
×
245
            &["height", "block_hash"],
×
246
        )
×
247
        .unwrap()
×
248
    });
×
249

250
    METER.with_label_values(&[&height.to_string(), &hash.to_hex()])
×
251
}
×
252

253
pub fn active_sync_peers() -> &'static IntGauge {
4✔
254
    static METER: Lazy<IntGauge> = Lazy::new(|| {
1✔
255
        tari_metrics::register_int_gauge(
1✔
256
            "base_node::sync::active_peers",
1✔
257
            "Number of active peers syncing from this node",
1✔
258
        )
1✔
259
        .unwrap()
1✔
260
    });
1✔
261

262
    &METER
4✔
263
}
4✔
264

265
pub fn utxo_set_size() -> &'static IntGauge {
×
266
    static METER: Lazy<IntGauge> = Lazy::new(|| {
×
267
        tari_metrics::register_int_gauge(
×
268
            "base_node::blockchain::utxo_set_size",
×
269
            "The number of UTXOs in the current UTXO set",
×
270
        )
×
271
        .unwrap()
×
272
    });
×
273

274
    &METER
×
275
}
×
276

277
// Ref: IEEE 754-2008, binary64 format.
278
// f64 has a 53-bit significand (52 explicit + 1 implicit bit).
279
// Scaling by 1/2^52 ensures we map the 53-bit integer into [1, 2),
280
// which is exactly representable in f64 without rounding.
281
const INV_2P52: f64 = 1.0 / ((1u64 << 52) as f64);
282

283
/// Computes log₂(value) as f64 for a U512 using a 53-bit normalized significand.
284
/// Uses IEEE 754 binary64 rules to avoid precision loss:
285
/// - Exact powers of two return an integer result.
286
/// - Non-powers-of-two are guaranteed to be strictly less than the next integer, preventing rounding-up artifacts.
287
#[allow(clippy::cast_possible_truncation)]
288
pub fn log2_u512(value_u512: &U512) -> Option<f64> {
14✔
289
    if value_u512.is_zero() {
14✔
290
        return None;
1✔
291
    }
13✔
292

13✔
293
    let (total_bits, sig53) = u512_into_parts(value_u512);
13✔
294

13✔
295
    // Converts the 53-bit integer significand into a floating-point number in the range [1, 2)
13✔
296
    let x = (sig53 as f64) * INV_2P52;
13✔
297

13✔
298
    let frac = x.log2();
13✔
299
    let mut res = (f64::from(total_bits) - 1.0) + frac;
13✔
300

13✔
301
    // If not an exact power of two (sig53 > 2^52), ensure we never round up to the next integer.
13✔
302
    if sig53 > (1u64 << 52) {
13✔
303
        res = next_down(res);
8✔
304
    }
8✔
305
    Some(res)
13✔
306
}
14✔
307

308
// Returns (exp2, sig53) where:
309
//   - exp2 = floor(log2(value)) (u32)
310
//   - sig53 = top 53 bits (u64)
311
#[allow(clippy::cast_possible_truncation)]
312
fn u512_into_parts(value_u512: &U512) -> (u32, u64) {
17✔
313
    let total_bits: u32 = value_u512.bits() as u32; // total bits
17✔
314

315
    // Build exact 53-bit significand with MSB at bit 52 into u64
316
    let sig53: u64 = if total_bits > 53 {
17✔
317
        // Keep only the top 53 bits
318
        (value_u512 >> (total_bits - 53)).as_u64()
13✔
319
    } else {
320
        // Move the most significant bit to position 52
321
        (value_u512 << (53 - total_bits)).as_u64()
4✔
322
    };
323
    debug_assert!(((1u64 << 52)..(1u64 << 53)).contains(&sig53));
17✔
324
    (total_bits, sig53)
17✔
325
}
17✔
326

327
/// Returns (exp2, sig53) where:
328
///   - exp2 = floor(log2(value)) (i64)
329
///   - sig53 = top 53 bits (i64, always positive, safe up to 2^53-1)
330
///   - Returns None if value == 0.
331
#[allow(clippy::cast_possible_truncation)]
332
pub fn u512_exp2_sig53(value: &U512) -> Option<(i64, i64)> {
4✔
333
    if value.is_zero() {
4✔
334
        return None;
×
335
    }
4✔
336
    let (total_bits, sig53) = u512_into_parts(value);
4✔
337
    Some((i64::from(total_bits) - 1, i64::try_from(sig53).unwrap_or(i64::MAX)))
4✔
338
}
4✔
339

340
/// Approximate a U512 as f64 using a 53-bit significand and exponent as `(sig53 / 2^52) * 2^exp2`.
341
///   - exp2 = floor(log2(value)) (i64)
342
///   - sig53 = top 53 bits (i64, always positive, safe up to 2^53-1)
343
///   - Returns None if value == 0.
344
#[allow(clippy::cast_possible_truncation)]
345
pub fn approximate_u512_with_f64(value: &U512) -> Option<f64> {
1✔
346
    if value.is_zero() {
1✔
347
        return None;
×
348
    }
1✔
349
    let (exp2, sig53) = u512_exp2_sig53(value).unwrap();
1✔
350

351
    // Grafana-style reconstruction: (sig53 / 2^52) * 2^exp2
352
    // This is not exact (limited by f64), but should be within a tiny relative error.
353
    const TWO_P52: f64 = 4503599627370496.0; // 2^52
354
                                             // Build 2^exp2 by setting the exponent (bias 1023), mantissa 0
355
    let two_pow_exp2 = f64::from_bits(((exp2 + 1023) as u64) << 52);
1✔
356
    Some((sig53 as f64 / TWO_P52) * two_pow_exp2)
1✔
357
}
1✔
358

359
// Nudge one floating point unit (ULP) down to counter rounding-up at integer boundaries.
360
#[inline]
361
fn next_down(x: f64) -> f64 {
12✔
362
    // Move to the next representable float toward -∞
12✔
363
    f64::from_bits(x.to_bits().saturating_sub(1))
12✔
364
}
12✔
365

366
/// Computes log₂(value) as f64 for a u128 using a 53-bit normalized significand.
367
/// Uses IEEE 754 binary64 rules to avoid precision loss:
368
/// - Exact powers of two return an integer result.
369
/// - Non-powers-of-two are guaranteed to be strictly less than the next integer, preventing rounding-up artifacts.
370
#[inline]
371
#[allow(clippy::cast_possible_truncation)]
372
pub fn log2_u128(value_u128: u128) -> Option<f64> {
10✔
373
    if value_u128 == 0 {
10✔
374
        return None;
1✔
375
    }
9✔
376
    let total_bits: u32 = 128 - value_u128.leading_zeros(); // In the range 1..=128
9✔
377

378
    // Build exact 53-bit significand with MSB at bit 52 into u64
379
    let sig53: u64 = if total_bits > 53 {
9✔
380
        // Keep only the top 53 bits
381
        (value_u128 >> (total_bits - 53)) as u64
5✔
382
    } else {
383
        // Move the most significant bit to position 52
384
        (value_u128 << (53 - total_bits)) as u64
4✔
385
    };
386
    debug_assert!(((1u64 << 52)..(1u64 << 53)).contains(&sig53));
9✔
387

388
    // Converts the 53-bit integer significand into a floating-point number in the range [1, 2)
389
    let x = (sig53 as f64) * INV_2P52;
9✔
390

9✔
391
    let frac = x.log2();
9✔
392
    let mut res = (f64::from(total_bits) - 1.0) + frac;
9✔
393

9✔
394
    // If not an exact power of two (sig53 > 2^52), ensure we never round up to the next integer.
9✔
395
    if sig53 > (1u64 << 52) {
9✔
396
        res = next_down(res);
4✔
397
    }
5✔
398
    Some(res)
9✔
399
}
10✔
400

401
/// Converts a floating point number of bits into an integer number of milli-bits (1/1000 bits).
402
#[inline]
403
#[allow(clippy::cast_possible_truncation)]
404
pub fn milli_bits(x: f64) -> i64 {
4✔
405
    (x * 1000.0).round() as i64
4✔
406
}
4✔
407

408
#[cfg(test)]
409
mod tests {
410
    use primitive_types::U512;
411

412
    use super::*;
413

414
    #[test]
415
    fn test_log2_u512() {
1✔
416
        // log2(1) == 0
1✔
417
        assert_eq!(log2_u512(&U512::from(1u64)), Some(0.0));
1✔
418

419
        // Exact powers of two
420
        assert_eq!(log2_u512(&(U512::from(2u64))), Some(1.0));
1✔
421
        assert_eq!(log2_u512(&(U512::from(8u64))), Some(3.0));
1✔
422
        assert_eq!(log2_u512(&(U512::from(1024u64))), Some(10.0));
1✔
423

424
        // 2^200 exactly
425
        let value = U512::from(1u64) << 200;
1✔
426
        assert_eq!(log2_u512(&value), Some(200.0));
1✔
427

428
        // 2^200 - 1  => strictly less than 200
429
        let value = (U512::from(1u64) << 200) - U512::from(1u64);
1✔
430
        assert!(log2_u512(&value).unwrap() < 200.0);
1✔
431

432
        // u128::MAX = 2^128 - 1  => strictly less than 128
433
        assert!(log2_u512(&U512::from(u128::MAX)).unwrap() < 128.0);
1✔
434

435
        // Test U512 max value = 2^512 - 1  => strictly less than 512
436
        let u512_max = U512::MAX;
1✔
437
        let log2_u512_max = log2_u512(&u512_max).unwrap();
1✔
438
        assert!(log2_u512_max < 512.0);
1✔
439
        assert!(log2_u512_max > 511.0);
1✔
440

441
        // log2(0) == None
442
        assert!(log2_u512(&U512::from(0u64)).is_none());
1✔
443
    }
1✔
444

445
    #[test]
446
    fn test_log2_u128() {
1✔
447
        // log2(1) == 0
1✔
448
        assert_eq!(log2_u128(1), Some(0.0));
1✔
449

450
        // exact powers of two
451
        assert_eq!(log2_u128(2), Some(1.0));
1✔
452
        assert_eq!(log2_u128(8), Some(3.0));
1✔
453
        assert_eq!(log2_u128(1024), Some(10.0));
1✔
454

455
        // 2^100 exactly
456
        let value = 1u128 << 100;
1✔
457
        assert_eq!(log2_u128(value), Some(100.0));
1✔
458

459
        // 2^100 - 1  => strictly less than 100
460
        let value = (1u128 << 100) - 1;
1✔
461
        assert!(log2_u128(value).unwrap() < 100.0);
1✔
462

463
        // u128::MAX = 2^128 - 1  => strictly less than 128
464
        assert!(log2_u128(u128::MAX).unwrap() < 128.0);
1✔
465

466
        // log2(0) == None
467
        assert!(log2_u128(0).is_none());
1✔
468
    }
1✔
469

470
    #[test]
471
    fn millibit_correctly_handles_small_difficulty_growth() {
1✔
472
        // Accumulated difficulty (U512)
1✔
473
        let acc_diff_v1 = U512::from_dec_str("3872628503165662556508806093911347954645375156922").unwrap();
1✔
474
        // Target difficulty (fits in u128)
1✔
475
        let target_diff_v1: u128 = 33_208_643_413_617_919;
1✔
476

1✔
477
        // --- Baseline milli-bits ---
1✔
478
        let acc_diff_v1_mbits = milli_bits(log2_u512(&acc_diff_v1).unwrap());
1✔
479
        let target_diff_v1_mbits = milli_bits(log2_u128(target_diff_v1).unwrap());
1✔
480

1✔
481
        // --- Apply +0.15% change (δ = 0.0015) --- (1/0.0015 = 666.666666667 ~= 667)
1✔
482
        let acc_diff_v2 = acc_diff_v1 + acc_diff_v1 / U512::from(667u64);
1✔
483
        let target_diff_v2 = target_diff_v1 + (target_diff_v1 / 667);
1✔
484

1✔
485
        let acc_diff_v2_mbits = milli_bits(log2_u512(&acc_diff_v2).unwrap());
1✔
486
        let target_diff_v2_mbits = milli_bits(log2_u128(target_diff_v2).unwrap());
1✔
487

1✔
488
        assert!(acc_diff_v2_mbits > acc_diff_v1_mbits);
1✔
489
        assert!(target_diff_v2_mbits > target_diff_v1_mbits);
1✔
490
    }
1✔
491

492
    #[test]
493
    fn exp2_sig53_power_of_two_and_neighbors() {
1✔
494
        // 2^200
1✔
495
        let v = U512::from(1u64) << 200;
1✔
496
        let (e, s) = u512_exp2_sig53(&v).unwrap();
1✔
497
        assert_eq!(e, 200);
1✔
498
        assert_eq!(s, 1i64 << 52, "exact power of two => normalized sig53 == 2^52");
1✔
499

500
        // 2^200 - 1  (just below)
501
        let v = (U512::from(1u64) << 200) - U512::from(1u64);
1✔
502
        let (e, s) = u512_exp2_sig53(&v).unwrap();
1✔
503
        assert_eq!(e, 199, "floor(log2(2^200-1)) = 199");
1✔
504
        assert!(((1i64 << 52)..(1i64 << 53)).contains(&s));
1✔
505

506
        // 2^200 + 1  (just above)
507
        let v = (U512::from(1u64) << 200) + U512::from(1u64);
1✔
508
        let (e, s) = u512_exp2_sig53(&v).unwrap();
1✔
509
        assert_eq!(e, 200);
1✔
510
        assert!(((1i64 << 52)..(1i64 << 53)).contains(&s));
1✔
511
    }
1✔
512

513
    #[test]
514
    fn it_can_reconstruct_u512_from_exp2_sig53_parts_as_f64() {
1✔
515
        use primitive_types::U512;
516

517
        let original_u512 = U512::from_dec_str("3872628503165662556508806093911347954645375156922").unwrap();
1✔
518
        let approximate_u512 = approximate_u512_with_f64(&original_u512).unwrap();
1✔
519

1✔
520
        // Expected bits using our robust log2 (with next_down ULP guard)
1✔
521
        let expected_bits = log2_u512(&original_u512).unwrap();
1✔
522

1✔
523
        // The two log2 values should match within a tiny epsilon (a few ULPs)
1✔
524
        let approx_bits = approximate_u512.log2();
1✔
525
        assert!(
1✔
526
            (approx_bits - expected_bits).abs() < 1e-9,
1✔
527
            "reconstructed log2 mismatch: approx={}, expected={}",
×
528
            approx_bits,
529
            expected_bits
530
        );
531

532
        // Relative error via logs (safer for huge values):
533
        let relative_err = (approximate_u512.log2() - log2_u512(&original_u512).unwrap()).abs() /
1✔
534
            log2_u512(&original_u512).unwrap().abs();
1✔
535
        assert!(relative_err < 1e-12);
1✔
536

537
        // println!("approx f64 = {}\noriginal   = {}", reconstructed_u512, original_u512);
538
    }
1✔
539
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc