1use alloc::format;
13use alloc::string::String;
14use core::fmt;
15
16use crate::sha256;
17
18pub const MANTISSA_BITS: u32 = 33;
20
21pub const COST_FP_BITS: u32 = 8;
23
24pub const COST_FP: u32 = 1 << COST_FP_BITS;
26
27pub const TAB_DIGEST: &str = "55ba3ce1df224895b0dfdc102eb4359fc2f5a7efefee68df0cc90c8a8ac24f48";
29
30pub const PRM_DIGEST: &str = "21436fc98e76a12cad7c52ebeb6ab76c2ae93b4cd768949df7fdd4e2f98c378d";
32
33pub const TAB: [u64; COST_FP as usize] = [
38 0x200000000,
39 0x201635f4b,
40 0x202c7b53f,
41 0x2042d0287,
42 0x2059347ce,
43 0x206fa85c2,
44 0x20862bd0d,
45 0x209cbee5e,
46 0x20b361a62,
47 0x20ca141c7,
48 0x20e0d653b,
49 0x20f7a856e,
50 0x210e8a30e,
51 0x21257becc,
52 0x213c7d958,
53 0x21538f363,
54 0x216ab0d9f,
55 0x2181e28bc,
56 0x21992456e,
57 0x21b076467,
58 0x21c7d865a,
59 0x21df4abfb,
60 0x21f6cd5ff,
61 0x220e6051a,
62 0x222603a02,
63 0x223db756b,
64 0x22557b80d,
65 0x226d5029e,
66 0x2285355d5,
67 0x229d2b269,
68 0x22b531914,
69 0x22cd48a8e,
70 0x22e57078f,
71 0x22fda90d2,
72 0x2315f2711,
73 0x232e4cb06,
74 0x2346b7d6d,
75 0x235f33f02,
76 0x2377c1080,
77 0x23905f2a5,
78 0x23a90e62d,
79 0x23c1cebd6,
80 0x23daa045f,
81 0x23f383087,
82 0x240c7710c,
83 0x24257c6af,
84 0x243e9322f,
85 0x2457bb44f,
86 0x2470f4dce,
87 0x248a3ff70,
88 0x24a39c9f6,
89 0x24bd0ae23,
90 0x24d68acbc,
91 0x24f01c683,
92 0x2509bfc3e,
93 0x252374eb2,
94 0x253d3bea3,
95 0x255714cda,
96 0x2570ffa1b,
97 0x258afc72e,
98 0x25a50b4dc,
99 0x25bf2c3ec,
100 0x25d95f527,
101 0x25f3a4957,
102 0x260dfc146,
103 0x262865dbd,
104 0x2642e1f89,
105 0x265d70775,
106 0x26781164c,
107 0x2692c4cdc,
108 0x26ad8abf2,
109 0x26c86345b,
110 0x26e34e6e7,
111 0x26fe4c463,
112 0x27195cda0,
113 0x27348036e,
114 0x274fb669c,
115 0x276aff7fd,
116 0x27865b862,
117 0x27a1ca89d,
118 0x27bd4c982,
119 0x27d8e1be3,
120 0x27f48a095,
121 0x28104586c,
122 0x282c1443e,
123 0x2847f64e1,
124 0x2863ebb2a,
125 0x287ff47f0,
126 0x289c10c0c,
127 0x28b840854,
128 0x28d483da3,
129 0x28f0dacd1,
130 0x290d456b8,
131 0x2929c3c32,
132 0x294655e1a,
133 0x2962fbd4d,
134 0x297fb5aa6,
135 0x299c83702,
136 0x29b96533f,
137 0x29d65b03b,
138 0x29f364ed3,
139 0x2a1082fe8,
140 0x2a2db5459,
141 0x2a4afbd07,
142 0x2a6856ad3,
143 0x2a85c5e9e,
144 0x2aa34994b,
145 0x2ac0e1bbd,
146 0x2ade8e6d6,
147 0x2afc4fb7c,
148 0x2b1a25a92,
149 0x2b38104ff,
150 0x2b560fba9,
151 0x2b7423f75,
152 0x2b924d14b,
153 0x2bb08b213,
154 0x2bcede2b5,
155 0x2bed4641b,
156 0x2c0bc372e,
157 0x2c2a55cd9,
158 0x2c48fd607,
159 0x2c67ba3a3,
160 0x2c868c699,
161 0x2ca573fd7,
162 0x2cc47104a,
163 0x2ce3838e1,
164 0x2d02aba89,
165 0x2d21e9633,
166 0x2d413cccf,
167 0x2d60a5f4e,
168 0x2d8024ea1,
169 0x2d9fb9bba,
170 0x2dbf6478c,
171 0x2ddf2530b,
172 0x2dfefbf2a,
173 0x2e1ee8cde,
174 0x2e3eebd1d,
175 0x2e5f050dd,
176 0x2e7f34914,
177 0x2e9f7a6ba,
178 0x2ebfd6ac8,
179 0x2ee049635,
180 0x2f00d29fb,
181 0x2f2172715,
182 0x2f4228e7d,
183 0x2f62f612e,
184 0x2f83da026,
185 0x2fa4d4c5f,
186 0x2fc5e66d9,
187 0x2fe70f092,
188 0x30084ea87,
189 0x3029a55ba,
190 0x304b13329,
191 0x306c983d7,
192 0x308e348c4,
193 0x30afe82f3,
194 0x30d1b3368,
195 0x30f395b26,
196 0x31158fb31,
197 0x3137a148f,
198 0x3159ca845,
199 0x317c0b75a,
200 0x319e642d6,
201 0x31c0d4bc1,
202 0x31e35d322,
203 0x3205fda05,
204 0x3228b6172,
205 0x324b86a75,
206 0x326e6f619,
207 0x32917056b,
208 0x32b489979,
209 0x32d7bb34e,
210 0x32fb053fb,
211 0x331e67c8f,
212 0x3341e2e19,
213 0x3365769aa,
214 0x338923054,
215 0x33ace8329,
216 0x33d0c633c,
217 0x33f4bd1a0,
218 0x3418ccf6b,
219 0x343cf5db1,
220 0x346137d89,
221 0x348593008,
222 0x34aa07647,
223 0x34ce9515e,
224 0x34f33c266,
225 0x3517fca78,
226 0x353cd6aaf,
227 0x3561ca426,
228 0x3586d77fa,
229 0x35abfe747,
230 0x35d13f32b,
231 0x35f699cc4,
232 0x361c0e531,
233 0x36419cd93,
234 0x366745709,
235 0x368d082b6,
236 0x36b2e51bc,
237 0x36d8dc53e,
238 0x36feede5f,
239 0x372519e44,
240 0x374b60614,
241 0x3771c16f3,
242 0x37983d209,
243 0x37bed387e,
244 0x37e584b7a,
245 0x380c50c28,
246 0x383337bb0,
247 0x385a39b3f,
248 0x388156bff,
249 0x38a88ef1f,
250 0x38cfe25ca,
251 0x38f751131,
252 0x391edb280,
253 0x394680aea,
254 0x396e41b9d,
255 0x39961e5cd,
256 0x39be16aab,
257 0x39e62ab6b,
258 0x3a0e5a940,
259 0x3a36a6561,
260 0x3a5f0e101,
261 0x3a8791d59,
262 0x3ab031b9f,
263 0x3ad8edd0c,
264 0x3b01c62d9,
265 0x3b2abae3f,
266 0x3b53cc07b,
267 0x3b7cf9ac7,
268 0x3ba643e60,
269 0x3bcfaac83,
270 0x3bf92e66f,
271 0x3c22ced63,
272 0x3c4c8c29e,
273 0x3c7666762,
274 0x3ca05dcf1,
275 0x3cca7248c,
276 0x3cf4a3f78,
277 0x3d1ef2ef9,
278 0x3d495f454,
279 0x3d73e90cf,
280 0x3d9e905b1,
281 0x3dc955443,
282 0x3df437dcc,
283 0x3e1f38396,
284 0x3e4a566ed,
285 0x3e759291b,
286 0x3ea0ecb6d,
287 0x3ecc64f31,
288 0x3ef7fb5b3,
289 0x3f23b0044,
290 0x3f4f83033,
291 0x3f7b746d2,
292 0x3fa784571,
293 0x3fd3b2d65,
294];
295
296const MAX_FP: u32 = 256 * COST_FP;
298
299#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
301pub struct StampValue(u32);
302
303impl StampValue {
304 pub const ZERO: StampValue = StampValue(0);
305
306 pub const fn from_fp(fp: u32) -> Self {
308 StampValue(if fp > MAX_FP { MAX_FP } else { fp })
309 }
310
311 pub const fn from_whole(value: u32) -> Self {
313 StampValue::from_fp(value.saturating_mul(COST_FP))
314 }
315
316 pub fn from_f64(value: f64) -> Self {
320 if !value.is_finite() || value <= 0.0 {
321 return StampValue::ZERO;
322 }
323 let fp = value * COST_FP as f64;
324 if fp >= MAX_FP as f64 {
325 StampValue(MAX_FP)
326 } else {
327 StampValue(fp as u32)
328 }
329 }
330
331 pub const fn fp(self) -> u32 {
333 self.0
334 }
335
336 pub const fn whole(self) -> u32 {
338 self.0 >> COST_FP_BITS
339 }
340
341 pub fn as_f64(self) -> f64 {
343 f64::from(self.0) / f64::from(COST_FP)
344 }
345
346 pub const fn meets(self, cost: StampCost) -> bool {
351 self.0 >= cost.0
352 }
353}
354
355impl fmt::Display for StampValue {
356 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357 fmt::Display::fmt(&self.as_f64(), f)
358 }
359}
360
361pub const STAMP_COST_CAP: u32 = 100;
363
364const MAX_COST_FP: u32 = STAMP_COST_CAP * COST_FP;
365
366const _: () = assert!((MAX_FP - MAX_COST_FP) >> COST_FP_BITS >= MANTISSA_BITS);
367
368#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
373pub struct StampCost(u32);
374
375impl StampCost {
376 pub const ZERO: StampCost = StampCost(0);
377
378 pub const fn from_fp(fp: u32) -> Self {
380 StampCost(if fp > MAX_COST_FP { MAX_COST_FP } else { fp })
381 }
382
383 pub const fn from_fp_checked(fp: u32) -> Option<Self> {
386 if fp > MAX_COST_FP {
387 None
388 } else {
389 Some(StampCost(fp))
390 }
391 }
392
393 pub const fn from_whole(cost: u32) -> Self {
395 StampCost::from_fp(cost.saturating_mul(COST_FP))
396 }
397
398 pub fn from_f64(cost: f64) -> Self {
401 if !cost.is_finite() || cost <= 0.0 {
402 return StampCost::ZERO;
403 }
404 let fp = cost * COST_FP as f64 + 0.5;
406 if fp >= MAX_COST_FP as f64 {
407 StampCost(MAX_COST_FP)
408 } else {
409 StampCost(fp as u32)
410 }
411 }
412
413 pub const fn fp(self) -> u32 {
415 self.0
416 }
417
418 pub const fn whole(self) -> u32 {
420 self.0 >> COST_FP_BITS
421 }
422
423 pub fn as_f64(self) -> f64 {
425 f64::from(self.0) / f64::from(COST_FP)
426 }
427
428 pub fn target(self) -> Option<[u8; 32]> {
432 if self.0 == 0 {
433 return None;
434 }
435 let rest = MAX_FP - self.0;
439 let exponent = rest >> COST_FP_BITS;
440 let mantissa = TAB[(rest & (COST_FP - 1)) as usize];
441 let shift = exponent - MANTISSA_BITS;
443 let (limb, offset) = ((shift / 64) as usize, shift % 64);
444 let mut limbs = [0u64; 4];
445 limbs[limb] = mantissa << offset;
446 if offset > 0 && limb + 1 < limbs.len() {
447 limbs[limb + 1] = mantissa >> (64 - offset);
448 }
449 let mut target = [0u8; 32];
450 for (i, limb) in limbs.iter().rev().enumerate() {
451 target[i * 8..i * 8 + 8].copy_from_slice(&limb.to_be_bytes());
452 }
453 Some(target)
454 }
455
456 pub fn admits(self, product: &[u8; 32]) -> bool {
459 match self.target() {
460 None => true,
461 Some(target) => product[..] <= target[..],
462 }
463 }
464}
465
466impl fmt::Display for StampCost {
467 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
468 fmt::Display::fmt(&self.as_f64(), f)
469 }
470}
471
472pub fn pretty_cost(value: f64) -> String {
477 format!("{:.3}", StampCost::from_f64(value).as_f64())
478}
479
480pub fn value_of_product(product: &[u8; 32]) -> StampValue {
482 let lz = leading_zeros(product);
483 if lz == 256 {
484 return StampValue(MAX_FP);
485 }
486 let start = (lz / 8) as usize;
490 let mut window = [0u8; 16];
491 let available = (32 - start).min(16);
492 window[..available].copy_from_slice(&product[start..start + available]);
493 let aligned = u128::from_be_bytes(window) << (lz % 8);
494 let mantissa = (aligned >> (128 - (MANTISSA_BITS + 1))) as u64;
495 let climb = TAB.partition_point(|&entry| entry <= mantissa) as u32;
498 StampValue((lz + 1) * COST_FP - climb)
499}
500
501pub fn stamp_value(workblock: &[u8], stamp: &[u8]) -> StampValue {
503 let mut input = alloc::vec::Vec::with_capacity(workblock.len() + stamp.len());
504 input.extend_from_slice(workblock);
505 input.extend_from_slice(stamp);
506 value_of_product(&sha256::sha256(&input))
507}
508
509pub fn leading_zeros(product: &[u8; 32]) -> u32 {
511 let mut count = 0u32;
512 for &byte in product {
513 if byte == 0 {
514 count += 8;
515 } else {
516 count += byte.leading_zeros();
517 break;
518 }
519 }
520 count
521}
522
523#[cfg(test)]
524mod tests {
525 use super::*;
526 use alloc::format;
527 use alloc::string::String;
528 use alloc::vec::Vec;
529
530 fn hex(bytes: &[u8]) -> String {
531 bytes.iter().map(|b| format!("{b:02x}")).collect()
532 }
533
534 fn unhex(s: &str) -> Vec<u8> {
535 (0..s.len())
536 .step_by(2)
537 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
538 .collect()
539 }
540
541 #[test]
542 fn table_matches_upstream_digests() {
543 assert_eq!(TAB[0], 1 << MANTISSA_BITS);
544 assert!(TAB.windows(2).all(|w| w[1] > w[0]));
545 let joined: Vec<String> = TAB.iter().map(|v| format!("{v}")).collect();
546 let tab_digest = hex(&sha256::sha256(joined.join(",").as_bytes()));
547 assert_eq!(tab_digest, TAB_DIGEST);
548 let params = format!(
549 "master|{TAB_DIGEST}|{MANTISSA_BITS}|{}|{COST_FP_BITS}|{COST_FP}|",
550 TAB.len()
551 );
552 assert_eq!(hex(&sha256::sha256(params.as_bytes())), PRM_DIGEST);
553 }
554
555 const UPSTREAM_WORKBLOCK: &[u8] = b"rns-rs fractional stamp vectors";
559 const UPSTREAM_VECTORS: &[(&str, u32)] = &[
560 ("af5570f5a1810b7af78caf4bc70a", 236),
561 ("cd2662154e6d76b2b2b92e70c0ca", 668),
562 ("cd04a4754498e06db5a13c5f371f", 88),
563 ("d5688a52d55a02ec4aea5ec1eadf", 112),
564 ("8005f02d43fa06e7d0585fb64c96", 276),
565 ("5dee4dd60ff8d0ba9900fe91e90e", 188),
566 ("14ac577cdb2ef6d986078b4054cc", 886),
567 ("a3eb8db89fc5123ccfd49585059f", 160),
568 ("4c0e071832d527694adea57b50dd", 349),
569 ("5924513516a5993435ec4a240610", 111),
570 ("7a42e3892368f826928202014a6c", 620),
571 ("561f627b4213258dc8863498bb9b", 742),
572 ("42f28a46039f894d3a0179d09085", 577),
573 ("1d7968ffc995d6a495071668f4a9", 730),
574 ("539b4c4c41a13f1f0452f5a35a67", 393),
575 ("ea676268c71eb961cae5f1a68dc8", 597),
576 ("e9a5f5201eb3c3c856e0a224527a", 511),
577 ("6a339ff6defc6e73cf17dea3ea81", 392),
578 ("0db5549dd341d8a11a21a653574f", 438),
579 ("dd677f19feae7d8e78334c0096d7", 1125),
580 ("76a872016c45a5edd4d933a2e45c", 1613),
581 ("23768e05b10a7f0335d3e35a9d30", 1477),
582 ("dc9684c3cd2e0715b1f151fda6d7", 1975),
583 ("d279f9164d2bf5ad26307e1a1f86", 2268),
584 ("d6050af84e83c2183c1a3005633f", 2693),
585 ("e6570450c0e7e92eb09bb2fb732c", 2426),
586 ("3e5bdd07714a45a792e880eeef89", 3027),
587 ("1d726593f982f447e8a65d82f51b", 4271),
588 ("cd3ff1775c55c55f6c5494b8eaa1", 3266),
589 ("f15bb2b358c7de7fbbd7a5280908", 3426),
590 ];
591
592 #[test]
593 fn values_match_upstream_vectors() {
594 for &(stamp, fp) in UPSTREAM_VECTORS {
595 let stamp = unhex(stamp);
596 assert_eq!(stamp_value(UPSTREAM_WORKBLOCK, &stamp).fp(), fp);
597 }
598 }
599
600 #[test]
601 fn value_stays_within_its_legacy_integer_step() {
602 for i in 0u32..4096 {
603 let product = sha256::sha256(&i.to_be_bytes());
604 let legacy = leading_zeros(&product);
605 let value = value_of_product(&product);
606 assert_eq!(value.whole(), legacy);
607 assert!(value.fp() >= legacy * COST_FP);
608 assert!(value.fp() < (legacy + 1) * COST_FP);
609 assert!(value.meets(StampCost::from_whole(legacy)));
610 assert!(!value.meets(StampCost::from_whole(legacy + 1)));
611 }
612 }
613
614 #[test]
615 fn value_is_monotone_in_the_product() {
616 let mut product = [0u8; 32];
618 product[3] = 0x01;
619 let mut last = value_of_product(&product);
620 for top in (0x01u8..=0xff).step_by(3) {
621 product[4] = top;
622 let value = value_of_product(&product);
623 assert!(value <= last);
624 last = value;
625 }
626 }
627
628 #[test]
629 fn extreme_products() {
630 assert_eq!(value_of_product(&[0u8; 32]).as_f64(), 256.0);
631 assert_eq!(value_of_product(&[0xff; 32]).fp(), 0);
632 let mut one = [0u8; 32];
633 one[31] = 1;
634 assert_eq!(value_of_product(&one).fp(), 256 * COST_FP - 1);
638 }
639
640 const UPSTREAM_TARGETS: &[(f64, u32, &str)] = &[
643 (
644 23.5,
645 6016,
646 "0000016a09e66780000000000000000000000000000000000000000000000000",
647 ),
648 (
649 25.0,
650 6400,
651 "0000008000000000000000000000000000000000000000000000000000000000",
652 ),
653 (
654 19.8,
655 5069,
656 "0000125e85711800000000000000000000000000000000000000000000000000",
657 ),
658 (
659 19.2,
660 4915,
661 "00001bdf69c3f000000000000000000000000000000000000000000000000000",
662 ),
663 (
664 0.01,
665 3,
666 "fdedd1b480000000000000000000000000000000000000000000000000000000",
667 ),
668 (
669 1.0,
670 256,
671 "8000000000000000000000000000000000000000000000000000000000000000",
672 ),
673 (
674 8.0,
675 2048,
676 "0100000000000000000000000000000000000000000000000000000000000000",
677 ),
678 (
679 99.99,
680 25597,
681 "0000000000000000000000001021681438000000000000000000000000000000",
682 ),
683 (
684 100.0,
685 25600,
686 "0000000000000000000000001000000000000000000000000000000000000000",
687 ),
688 ];
689
690 const UPSTREAM_FP_TARGETS: &[(u32, &str)] = &[
692 (
693 1,
694 "ff4ecb5940000000000000000000000000000000000000000000000000000000",
695 ),
696 (
697 255,
698 "8058d7d2c0000000000000000000000000000000000000000000000000000000",
699 ),
700 (
701 257,
702 "7fa765aca0000000000000000000000000000000000000000000000000000000",
703 ),
704 (
705 8447,
706 "000000008058d7d2c00000000000000000000000000000000000000000000000",
707 ),
708 (
709 8448,
710 "0000000080000000000000000000000000000000000000000000000000000000",
711 ),
712 (
713 8455,
714 "000000007d98c9e6200000000000000000000000000000000000000000000000",
715 ),
716 ];
717
718 #[test]
719 fn costs_and_targets_match_upstream() {
720 for &(cost, fp, target) in UPSTREAM_TARGETS {
721 let cost = StampCost::from_f64(cost);
722 assert_eq!(cost.fp(), fp);
723 assert_eq!(hex(&cost.target().unwrap()), target);
724 }
725 for &(fp, target) in UPSTREAM_FP_TARGETS {
726 assert_eq!(hex(&StampCost::from_fp(fp).target().unwrap()), target);
727 }
728 assert_eq!(StampCost::ZERO.target(), None);
729 assert!(StampCost::ZERO.admits(&[0xff; 32]));
730 assert_eq!(
731 StampCost::from_f64(1000.0),
732 StampCost::from_whole(STAMP_COST_CAP)
733 );
734 assert_eq!(StampCost::from_f64(-1.0), StampCost::ZERO);
735 assert_eq!(StampCost::from_f64(f64::NAN), StampCost::ZERO);
736 assert_eq!(StampCost::from_f64(19.8).fp(), 5069);
738 assert_eq!(StampValue::from_f64(19.8).fp(), 5068);
739 }
740
741 #[test]
742 fn whole_cost_targets_are_leading_zero_counts() {
743 for i in 0u32..4096 {
744 let product = sha256::sha256(&i.to_be_bytes());
745 let lz = leading_zeros(&product);
746 for cost in [0, 1, lz.saturating_sub(1), lz, lz + 1, 8, 100] {
747 let cost = StampCost::from_whole(cost);
748 assert_eq!(cost.admits(&product), lz >= cost.whole(), "{i} {cost}");
749 }
750 }
751 }
752
753 #[test]
754 fn target_agrees_with_the_value_off_the_boundary() {
755 for i in 0u32..4096 {
758 let product = sha256::sha256(&i.to_le_bytes());
759 let value = value_of_product(&product);
760 for fp in [1, 37, 255, 256, 300, value.fp(), value.fp() + 1, 6016] {
761 let cost = StampCost::from_fp(fp);
762 assert_eq!(cost.admits(&product), value.meets(cost), "{i} {fp}");
763 }
764 }
765 let cost = StampCost::from_fp(6016);
766 let target = cost.target().unwrap();
767 assert!(cost.admits(&target));
768 assert!(!value_of_product(&target).meets(cost));
769 }
770
771 #[test]
772 fn float_conversions_round_trip_on_the_grid() {
773 for fp in [0, 1, 128, 255, 256, 23 * 256 + 187, MAX_FP] {
774 let value = StampValue::from_fp(fp);
775 assert_eq!(StampValue::from_f64(value.as_f64()), value);
776 }
777 assert_eq!(StampValue::from_f64(8.5).fp(), 8 * 256 + 128);
778 assert_eq!(StampValue::from_f64(-1.0), StampValue::ZERO);
779 assert_eq!(StampValue::from_f64(f64::NAN), StampValue::ZERO);
780 assert_eq!(StampValue::from_f64(1e9).fp(), MAX_FP);
781 assert_eq!(StampValue::from_whole(24).as_f64(), 24.0);
782 assert_eq!(format!("{}", StampValue::from_fp(8 * 256 + 128)), "8.5");
783 assert_eq!(format!("{}", StampValue::from_whole(8)), "8");
784 }
785
786 #[test]
787 fn stamp_cost_displays_the_cost_not_its_fixed_point() {
788 let cost = StampCost::from_fp(4915);
791 assert_eq!(format!("{cost}"), "19.19921875");
792 assert_eq!(format!("{}", StampCost::from_whole(8)), "8");
793 assert_eq!(format!("{}", StampCost::from_fp(8 * COST_FP + 128)), "8.5");
794 }
795
796 #[test]
797 fn pretty_cost_matches_upstream_prettycost() {
798 assert_eq!(pretty_cost(23.5), "23.500");
800 assert_eq!(pretty_cost(24.73046875), "24.730");
801 assert_eq!(pretty_cost(19.2), "19.199");
802 assert_eq!(pretty_cost(0.0625), "0.062");
803 assert_eq!(pretty_cost(0.1875), "0.188");
804 assert_eq!(pretty_cost(256.0), "100.000");
805 assert_eq!(pretty_cost(-1.0), "0.000");
806 }
807}