1mod decimal;
17#[cfg(not(all(
18 feature = "web-number-intl",
19 not(feature = "web-number-builtin"),
20 target_arch = "wasm32",
21 target_os = "unknown"
22)))]
23mod display;
24#[cfg(all(
25 feature = "web-number-intl",
26 not(feature = "web-number-builtin"),
27 target_arch = "wasm32",
28 target_os = "unknown"
29))]
30mod intl;
31mod measure;
32mod options;
33mod request;
34
35#[cfg(not(all(
36 feature = "web-number-intl",
37 not(feature = "web-number-builtin"),
38 target_arch = "wasm32",
39 target_os = "unknown"
40)))]
41use display as backend;
42#[cfg(all(
43 feature = "web-number-intl",
44 not(feature = "web-number-builtin"),
45 target_arch = "wasm32",
46 target_os = "unknown"
47))]
48use intl as backend;
49
50use crate::error::FormatError;
51use crate::function::{FnContext, Options};
52use crate::host::Host;
53use crate::plural::{self, Category, OperandsBuilder};
54use crate::sink::{ErrorSink, Sink, SubPartSink};
55use crate::value::Value;
56
57use decimal::{Decimal, ParseError, split_literal};
58use options::{
59 CUR, FracDefaults, INT, Kind, NUM, NumOpts, OPTIONS, PCT, Select, UNIT, apply, digit_plan,
60 select_named,
61};
62
63pub(crate) use options::digit_size;
64
65pub use decimal::RoundingMode;
66pub use measure::{Measure, MeasureUnit};
67pub use options::{Grouping, RoundingPriority, SignDisplay};
68pub use request::{
69 CurrencyDisplay, DigitOptions, NumberOut, NumberRequest, NumberStyle, UnitDisplay,
70};
71
72pub(crate) const BY_HOST: bool = backend::BY_HOST;
75
76#[derive(Clone)]
80pub struct Number {
81 pub(crate) value: Decimal,
84 resolved: Option<Resolved>,
86}
87
88#[derive(Clone)]
90struct Resolved {
91 opts: NumOpts,
92 frac: FracDefaults,
93 scale: i16,
95 selectable: bool,
97 shown: backend::Shown,
100}
101
102impl core::fmt::Debug for Number {
106 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
107 f.debug_struct("Number")
108 .field("negative", &self.value.negative())
109 .field("resolved", &self.resolved.is_some())
110 .finish_non_exhaustive()
111 }
112}
113
114#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
116pub enum Sign {
117 None,
119 Minus,
121 Plus,
123}
124
125#[derive(Clone, Copy)]
129pub struct Digits<'n> {
130 dec: &'n Decimal,
131 lo: i16,
132 hi: i16,
133 sign: Sign,
134}
135
136impl Digits<'_> {
137 pub fn sign(&self) -> Sign {
139 self.sign
140 }
141
142 pub fn integer_count(&self) -> u16 {
145 u16::try_from(i32::from(self.hi) + 1).unwrap_or(1)
146 }
147
148 pub fn fraction_count(&self) -> u16 {
150 u16::try_from(-i32::from(self.lo)).unwrap_or(0)
151 }
152
153 pub fn digit(&self, magnitude: i16) -> u8 {
157 if magnitude < self.lo || magnitude > self.hi {
158 return 0;
159 }
160 self.dec.digit_at(magnitude)
161 }
162
163 pub fn is_zero(&self) -> bool {
165 self.dec.is_zero()
166 }
167
168 pub fn write_neutral(&self, out: &mut dyn Sink) {
171 match self.sign {
172 Sign::Minus => out.push_str("-"),
173 Sign::Plus => out.push_str("+"),
174 Sign::None => {}
175 }
176 self.dec.write_digits(self.hi, self.lo, out);
177 }
178
179 pub fn neutral_parts(&self, out: &mut dyn SubPartSink) {
182 write_parts(self.dec, self.sign, self.hi, self.lo, out);
183 }
184
185 pub fn operands(&self) -> plural::Operands {
189 let mut b = OperandsBuilder::default();
190 let mut m = self.hi;
191 while m >= self.lo {
192 b.digit(self.dec.digit_at(m), m >= 0);
193 m -= 1;
194 }
195 b.finish()
196 }
197}
198
199impl Number {
200 pub fn parse(number_literal: &str) -> Option<Number> {
203 Decimal::parse(number_literal.as_bytes())
204 .ok()
205 .map(Number::bare_of)
206 }
207
208 pub fn from_i64(n: i64) -> Number {
210 let mut d = Decimal::from_u64(n.unsigned_abs());
211 d.set_negative(n < 0);
212 Number::bare_of(d)
213 }
214
215 pub fn from_f64(x: f64, host: &dyn Host) -> Option<Number> {
218 if !x.is_finite() {
219 return None;
220 }
221 if x == 0.0 {
222 let mut d = Decimal::from_u64(0);
223 d.set_negative(x.is_sign_negative());
224 return Some(Number::bare_of(d));
225 }
226 let mut buf = [0u8; 32];
227 let text = host.f64_to_text(x, &mut buf)?;
228 Number::parse(text)
229 }
230
231 pub fn is_negative(&self) -> bool {
233 self.value.negative()
234 }
235
236 pub fn is_integer(&self) -> bool {
238 self.value.is_integer()
239 }
240
241 pub fn to_i64(&self) -> Option<i64> {
243 self.value.to_i64()
244 }
245
246 pub fn write_plain(&self, out: &mut dyn Sink) {
249 self.value.write_plain(out);
250 }
251
252 pub fn resolve(
258 spec: NumberSpec,
259 cx: &FnContext<'_>,
260 operand: Option<&Value<'_>>,
261 options: &Options<'_, '_>,
262 errs: &mut dyn ErrorSink,
263 ) -> Option<Number> {
264 resolve(spec, cx, operand, *options, errs)
265 }
266
267 pub fn digits(&self) -> Option<Digits<'_>> {
272 self.resolved.as_ref().and_then(backend::digits)
273 }
274
275 pub fn exact_digits(&self) -> Digits<'_> {
278 let d = &self.value;
279 Digits {
280 dec: d,
281 lo: d.low().min(0),
282 hi: d.high().max(0),
283 sign: if d.negative() {
284 Sign::Minus
285 } else {
286 Sign::None
287 },
288 }
289 }
290
291 pub fn grouping(&self) -> Option<Grouping> {
294 self.resolved.as_ref().and_then(|r| r.opts.use_grouping)
295 }
296
297 pub fn is_selectable(&self) -> bool {
300 self.selectable()
301 }
302
303 pub fn matches(&self, cx: &FnContext<'_>, key: &str, errs: &mut dyn ErrorSink) -> bool {
307 matches(self, cx, key, errs)
308 }
309
310 pub fn better_than(key1: &str, key2: &str) -> bool {
313 better_than(key1, key2)
314 }
315
316 fn bare_of(value: Decimal) -> Number {
317 Number {
318 value,
319 resolved: None,
320 }
321 }
322
323 pub(crate) fn bare(&self) -> Number {
325 Number::bare_of(self.value.clone())
326 }
327
328 pub(crate) fn plain_parts(&self, out: &mut dyn SubPartSink) {
331 self.exact_digits().neutral_parts(out);
332 }
333
334 pub(crate) fn selectable(&self) -> bool {
336 self.resolved.as_ref().is_some_and(|r| r.selectable)
337 }
338
339 pub(crate) fn write_display(&self, cx: &FnContext<'_>, out: &mut dyn Sink) {
342 backend::write_neutral(self, cx, out);
343 }
344
345 pub(crate) fn display_parts(&self, cx: &FnContext<'_>, out: &mut dyn SubPartSink) {
347 backend::neutral_parts(self, cx, out);
348 }
349}
350
351fn write_parts(d: &Decimal, sign: Sign, hi: i16, lo: i16, out: &mut dyn SubPartSink) {
354 match sign {
355 Sign::Minus => out.sub_part("minusSign", "-"),
356 Sign::Plus => out.sub_part("plusSign", "+"),
357 Sign::None => {}
358 }
359 digits_part(d, hi, 0, "integer", out);
360 if lo < 0 {
361 out.sub_part("decimal", ".");
362 digits_part(d, -1, lo, "fraction", out);
363 }
364}
365
366fn digits_part(d: &Decimal, hi: i16, lo: i16, kind: &str, out: &mut dyn SubPartSink) {
367 let mut buf = [0u8; 64];
368 let mut n = 0;
369 let mut m = hi;
370 while m >= lo {
371 if let Some(b) = buf.get_mut(n) {
372 *b = b'0' + d.digit_at(m);
373 n += 1;
374 }
375 if n == buf.len() {
376 out.sub_part(kind, core::str::from_utf8(&buf).unwrap_or(""));
377 n = 0;
378 }
379 m -= 1;
380 }
381 if n > 0 {
382 out.sub_part(
383 kind,
384 core::str::from_utf8(buf.get(..n).unwrap_or(&[])).unwrap_or(""),
385 );
386 }
387}
388
389pub(crate) fn literal_error(s: &str) -> Option<FormatError> {
393 match Decimal::parse(s.as_bytes()) {
394 Ok(_) => None,
395 Err(ParseError::Syntax) => Some(FormatError::BadOperand),
396 Err(ParseError::Limit) => Some(FormatError::UnsupportedOperation),
397 }
398}
399
400fn is_number_literal(key: &str) -> bool {
403 !matches!(split_literal(key.as_bytes()), Err(ParseError::Syntax))
404}
405
406#[derive(Clone, Copy, PartialEq, Eq, Debug)]
413pub struct NumberSpec {
414 bit: u8,
416 frac: Option<FracDefaults>,
418 integer: bool,
420 selectable: bool,
422 scale: i16,
424}
425
426impl NumberSpec {
427 pub const NUMBER: NumberSpec = NumberSpec {
429 bit: NUM,
430 frac: Some(FracDefaults { min: 0, max: 3 }),
431 integer: false,
432 selectable: true,
433 scale: 0,
434 };
435
436 pub const INTEGER: NumberSpec = NumberSpec {
438 bit: INT,
439 frac: Some(FracDefaults { min: 0, max: 0 }),
440 integer: true,
441 selectable: true,
442 scale: 0,
443 };
444
445 pub const OFFSET: NumberSpec = NumberSpec {
447 bit: 0,
448 frac: None,
449 integer: false,
450 selectable: true,
451 scale: 0,
452 };
453
454 pub const PERCENT: NumberSpec = NumberSpec {
457 bit: PCT,
458 frac: Some(FracDefaults { min: 0, max: 0 }),
459 integer: false,
460 selectable: true,
461 scale: 2,
462 };
463
464 pub const UNIT: NumberSpec = NumberSpec {
466 bit: UNIT,
467 frac: Some(FracDefaults { min: 0, max: 3 }),
468 integer: false,
469 selectable: false,
470 scale: 0,
471 };
472
473 pub const fn currency(fraction_digits: u8) -> NumberSpec {
477 NumberSpec {
478 bit: CUR,
479 frac: Some(FracDefaults {
480 min: fraction_digits,
481 max: fraction_digits,
482 }),
483 integer: false,
484 selectable: false,
485 scale: 0,
486 }
487 }
488}
489
490pub(crate) fn resolve(
492 spec: NumberSpec,
493 cx: &FnContext<'_>,
494 operand: Option<&Value<'_>>,
495 options: Options<'_, '_>,
496 errs: &mut dyn ErrorSink,
497) -> Option<Number> {
498 let is_offset = spec.bit == 0;
499 let Some(operand) = operand else {
500 errs.error(FormatError::BadOperand);
501 return None;
502 };
503 let resolved_operand = match operand {
504 Value::Number(n) | Value::Measure(Measure { number: n, .. }) => {
505 n.resolved.as_ref().map(|r| (&n.value, r))
506 }
507 _ => None,
508 };
509 let (mut value, mut o, inherited, mut selectable) = match resolved_operand {
510 Some((value, r)) => {
511 let mut o = r.opts;
512 let mut selectable = r.selectable;
513 if o.select.is_some() {
514 if spec.bit & (NUM | INT) != 0 {
518 errs.error(FormatError::BadOption);
519 selectable = false;
520 }
521 if !is_offset {
522 o.select = None;
523 }
524 }
525 if spec.bit == INT {
526 o.min_frac = None;
527 o.max_frac = None;
528 o.min_sig = None;
529 }
530 if spec.bit == PCT {
531 o.min_int = None;
532 o.rounding_increment = None;
533 }
534 if spec.bit == CUR {
535 o.min_frac = None;
537 o.max_frac = None;
538 }
539 (value.clone(), o, Some((r.frac, r.scale)), selectable)
540 }
541 None => match numeric_operand(operand, cx.host()) {
542 Ok(d) => (d, NumOpts::default(), None, true),
543 Err(e) => {
544 errs.error(e);
545 return None;
546 }
547 },
548 };
549 let mut offset: Option<(bool, u8)> = None;
550 let mut offset_bad = false;
551 for (name, v) in options.iter() {
552 if is_offset {
553 let sub = match name {
554 "add" => false,
555 "subtract" => true,
556 _ => continue, };
558 match (digit_size(v.value), offset) {
559 (Some(d), None) => offset = Some((sub, d)),
560 _ => offset_bad = true,
561 }
562 continue;
563 }
564 let Some(kind) = OPTIONS
565 .iter()
566 .find(|(n, mask, _)| *n == name && mask & spec.bit != 0)
567 .map(|&(_, _, k)| k)
568 else {
569 continue; };
571 if kind == Kind::Select {
572 if v.literal {
574 match v.value.as_str().and_then(select_named) {
575 Some(x) => o.select = Some(x),
576 None => errs.error(FormatError::BadOption),
577 }
578 } else {
579 errs.error(FormatError::BadOption);
580 selectable = false;
581 }
582 continue;
583 }
584 if !apply(&mut o, kind, v.value) {
585 errs.error(FormatError::BadOption);
586 }
587 }
588 if spec.integer {
589 let mode = o.rounding_mode.unwrap_or(RoundingMode::HalfExpand);
591 backend::round_to_integer(&mut value, mode, cx);
592 }
593 let (frac, scale) = match (spec.frac, inherited) {
594 (Some(f), _) => (f, spec.scale),
595 (None, Some(inherited)) => inherited,
596 (None, None) => (FracDefaults { min: 0, max: 3 }, 0),
597 };
598 if is_offset {
599 let Some((sub, d)) = offset.filter(|_| !offset_bad) else {
600 errs.error(FormatError::BadOption);
601 return None;
602 };
603 let mut delta = Decimal::from_u64(u64::from(d));
604 delta.set_negative(sub);
605 let Some(sum) = value.add(&delta) else {
606 errs.error(FormatError::UnsupportedOperation);
608 return None;
609 };
610 value = sum;
611 }
612 backend::limit_digit_sizes(&mut o, errs);
613 let plan = digit_plan(&o, frac, errs);
614 let shown = backend::shown(
615 &value,
616 scale,
617 &plan,
618 o.sign_display.unwrap_or(SignDisplay::Auto),
619 );
620 backend::check_host(cx, errs);
621 Some(Number {
622 value,
623 resolved: Some(Resolved {
624 opts: o,
625 frac,
626 scale,
627 selectable: selectable && spec.selectable,
628 shown,
629 }),
630 })
631}
632
633fn numeric_operand(v: &Value<'_>, host: &dyn Host) -> Result<Decimal, FormatError> {
635 match v {
636 Value::Str(s) | Value::Decimal(s) => Decimal::parse(s.as_bytes()).map_err(|e| match e {
637 ParseError::Syntax => FormatError::BadOperand,
638 ParseError::Limit => FormatError::UnsupportedOperation,
639 }),
640 Value::Int(n) => {
641 let mut d = Decimal::from_u64(n.unsigned_abs());
642 d.set_negative(*n < 0);
643 Ok(d)
644 }
645 _ => v
646 .to_number(host)
647 .map(|n| n.value)
648 .ok_or(FormatError::BadOperand),
649 }
650}
651
652struct Cmp<'k> {
656 key: &'k [u8],
657 pos: usize,
658 ok: bool,
659}
660
661impl Sink for Cmp<'_> {
662 fn push_str(&mut self, s: &str) {
663 let end = self.pos + s.len();
664 self.ok &= self.key.get(self.pos..end) == Some(s.as_bytes());
665 self.pos = end;
666 }
667}
668
669impl Cmp<'_> {
670 fn done(&self) -> bool {
671 self.ok && self.pos == self.key.len()
672 }
673}
674
675pub(crate) fn equals(key: &str, write: impl FnOnce(&mut dyn Sink)) -> bool {
677 let mut c = Cmp {
678 key: key.as_bytes(),
679 pos: 0,
680 ok: true,
681 };
682 write(&mut c);
683 c.done()
684}
685
686fn exact_matches(n: &Number, r: &Resolved, cx: &FnContext<'_>, key: &str) -> bool {
691 let o = &r.opts;
692 let plain =
693 o.min_frac.is_none() && o.min_int.is_none() && o.min_sig.is_none() && o.max_sig.is_none();
694 let mut value = n.value.clone();
695 if r.scale != 0 {
696 value.shift(r.scale);
697 }
698 if plain && value.is_integer() {
699 if value.is_zero() {
700 value.set_negative(false);
701 }
702 equals(key, |s| value.write_plain(s))
703 } else {
704 equals(key, |s| backend::write_selected(n, r, cx, s))
705 }
706}
707
708pub(crate) fn matches(n: &Number, cx: &FnContext<'_>, key: &str, errs: &mut dyn ErrorSink) -> bool {
710 let Some(r) = &n.resolved else {
711 return false;
712 };
713 if is_number_literal(key) {
714 return exact_matches(n, r, cx, key);
715 }
716 let Some(keyword) = Category::from_keyword(key) else {
717 errs.error(FormatError::BadVariantKey);
718 return false;
719 };
720 if r.opts.select == Some(Select::Exact) {
721 return false;
722 }
723 backend::category(n, r, cx) == keyword
724}
725
726pub(crate) fn better_than(key1: &str, key2: &str) -> bool {
729 is_number_literal(key1) && !is_number_literal(key2)
730}
731
732impl core::fmt::Debug for Digits<'_> {
735 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
736 f.debug_struct("Digits")
737 .field("sign", &self.sign)
738 .field("magnitudes", &(self.lo..=self.hi))
739 .finish_non_exhaustive()
740 }
741}
742
743#[cfg(test)]
744mod tests;