1mod decimal;
16#[cfg(not(all(feature = "intl", target_arch = "wasm32", target_os = "unknown")))]
17mod display;
18#[cfg(all(feature = "intl", target_arch = "wasm32", target_os = "unknown"))]
19mod intl;
20mod measure;
21mod options;
22mod request;
23
24#[cfg(not(all(feature = "intl", target_arch = "wasm32", target_os = "unknown")))]
25use display as backend;
26#[cfg(all(feature = "intl", target_arch = "wasm32", target_os = "unknown"))]
27use intl as backend;
28
29use crate::error::FormatError;
30use crate::function::{FnContext, Options};
31use crate::host::Host;
32use crate::plural::{self, Category, OperandsBuilder};
33use crate::sink::{ErrorSink, Sink, SubPartSink};
34use crate::value::Value;
35
36use decimal::{Decimal, ParseError, split_literal};
37use options::{
38 CUR, FracDefaults, INT, Kind, NUM, NumOpts, OPTIONS, PCT, Select, UNIT, apply, digit_plan,
39 select_named,
40};
41
42pub(crate) use options::digit_size;
43
44pub use decimal::RoundingMode;
45pub use measure::{Measure, MeasureUnit};
46pub use options::{Grouping, RoundingPriority, SignDisplay};
47pub use request::{
48 CurrencyDisplay, DigitOptions, NumberOut, NumberRequest, NumberStyle, UnitDisplay,
49};
50
51#[derive(Clone)]
55pub struct Number {
56 pub(crate) value: Decimal,
59 resolved: Option<Resolved>,
61}
62
63#[derive(Clone)]
65struct Resolved {
66 opts: NumOpts,
67 frac: FracDefaults,
68 scale: i16,
70 selectable: bool,
72 shown: backend::Shown,
75}
76
77#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
79pub enum Sign {
80 None,
82 Minus,
84 Plus,
86}
87
88#[derive(Clone, Copy)]
92pub struct Digits<'n> {
93 dec: &'n Decimal,
94 lo: i16,
95 hi: i16,
96 sign: Sign,
97}
98
99impl Digits<'_> {
100 pub fn sign(&self) -> Sign {
102 self.sign
103 }
104
105 pub fn integer_count(&self) -> u16 {
108 u16::try_from(i32::from(self.hi) + 1).unwrap_or(1)
109 }
110
111 pub fn fraction_count(&self) -> u16 {
113 u16::try_from(-i32::from(self.lo)).unwrap_or(0)
114 }
115
116 pub fn digit(&self, magnitude: i16) -> u8 {
120 if magnitude < self.lo || magnitude > self.hi {
121 return 0;
122 }
123 self.dec.digit_at(magnitude)
124 }
125
126 pub fn is_zero(&self) -> bool {
128 self.dec.is_zero()
129 }
130
131 pub fn write_neutral(&self, out: &mut dyn Sink) {
134 match self.sign {
135 Sign::Minus => out.push_str("-"),
136 Sign::Plus => out.push_str("+"),
137 Sign::None => {}
138 }
139 self.dec.write_digits(self.hi, self.lo, out);
140 }
141
142 pub fn neutral_parts(&self, out: &mut dyn SubPartSink) {
145 write_parts(self.dec, self.sign, self.hi, self.lo, out);
146 }
147
148 pub fn operands(&self) -> plural::Operands {
152 let mut b = OperandsBuilder::default();
153 let mut m = self.hi;
154 while m >= self.lo {
155 b.digit(self.dec.digit_at(m), m >= 0);
156 m -= 1;
157 }
158 b.finish()
159 }
160}
161
162impl Number {
163 pub fn parse(number_literal: &str) -> Option<Number> {
166 Decimal::parse(number_literal.as_bytes())
167 .ok()
168 .map(Number::bare_of)
169 }
170
171 pub fn from_i64(n: i64) -> Number {
173 let mut d = Decimal::from_u64(n.unsigned_abs());
174 d.set_negative(n < 0);
175 Number::bare_of(d)
176 }
177
178 pub fn from_f64(x: f64, host: &dyn Host) -> Option<Number> {
181 if !x.is_finite() {
182 return None;
183 }
184 if x == 0.0 {
185 let mut d = Decimal::from_u64(0);
186 d.set_negative(x.is_sign_negative());
187 return Some(Number::bare_of(d));
188 }
189 let mut buf = [0u8; 32];
190 let text = host.f64_to_text(x, &mut buf)?;
191 Number::parse(text)
192 }
193
194 pub fn is_negative(&self) -> bool {
196 self.value.negative()
197 }
198
199 pub fn is_integer(&self) -> bool {
201 self.value.is_integer()
202 }
203
204 pub fn to_i64(&self) -> Option<i64> {
206 self.value.to_i64()
207 }
208
209 pub fn write_plain(&self, out: &mut dyn Sink) {
212 self.value.write_plain(out);
213 }
214
215 pub fn resolve(
221 spec: NumberSpec,
222 cx: &FnContext<'_>,
223 operand: Option<&Value<'_>>,
224 options: &Options<'_, '_>,
225 errs: &mut dyn ErrorSink,
226 ) -> Option<Number> {
227 resolve(spec, cx, operand, *options, errs)
228 }
229
230 pub fn digits(&self) -> Option<Digits<'_>> {
235 self.resolved.as_ref().and_then(backend::digits)
236 }
237
238 pub fn exact_digits(&self) -> Digits<'_> {
241 let d = &self.value;
242 Digits {
243 dec: d,
244 lo: d.low().min(0),
245 hi: d.high().max(0),
246 sign: if d.negative() {
247 Sign::Minus
248 } else {
249 Sign::None
250 },
251 }
252 }
253
254 pub fn grouping(&self) -> Option<Grouping> {
257 self.resolved.as_ref().and_then(|r| r.opts.use_grouping)
258 }
259
260 pub fn is_selectable(&self) -> bool {
263 self.selectable()
264 }
265
266 pub fn matches(&self, cx: &FnContext<'_>, key: &str, errs: &mut dyn ErrorSink) -> bool {
270 matches(self, cx, key, errs)
271 }
272
273 pub fn better_than(key1: &str, key2: &str) -> bool {
276 better_than(key1, key2)
277 }
278
279 fn bare_of(value: Decimal) -> Number {
280 Number {
281 value,
282 resolved: None,
283 }
284 }
285
286 pub(crate) fn bare(&self) -> Number {
288 Number::bare_of(self.value.clone())
289 }
290
291 pub(crate) fn plain_parts(&self, out: &mut dyn SubPartSink) {
294 self.exact_digits().neutral_parts(out);
295 }
296
297 pub(crate) fn selectable(&self) -> bool {
299 self.resolved.as_ref().is_some_and(|r| r.selectable)
300 }
301
302 pub(crate) fn write_display(&self, cx: &FnContext<'_>, out: &mut dyn Sink) {
305 backend::write_neutral(self, cx, out);
306 }
307
308 pub(crate) fn display_parts(&self, cx: &FnContext<'_>, out: &mut dyn SubPartSink) {
310 backend::neutral_parts(self, cx, out);
311 }
312}
313
314fn write_parts(d: &Decimal, sign: Sign, hi: i16, lo: i16, out: &mut dyn SubPartSink) {
317 match sign {
318 Sign::Minus => out.sub_part("minusSign", "-"),
319 Sign::Plus => out.sub_part("plusSign", "+"),
320 Sign::None => {}
321 }
322 digits_part(d, hi, 0, "integer", out);
323 if lo < 0 {
324 out.sub_part("decimal", ".");
325 digits_part(d, -1, lo, "fraction", out);
326 }
327}
328
329fn digits_part(d: &Decimal, hi: i16, lo: i16, kind: &str, out: &mut dyn SubPartSink) {
330 let mut buf = [0u8; 64];
331 let mut n = 0;
332 let mut m = hi;
333 while m >= lo {
334 if let Some(b) = buf.get_mut(n) {
335 *b = b'0' + d.digit_at(m);
336 n += 1;
337 }
338 if n == buf.len() {
339 out.sub_part(kind, core::str::from_utf8(&buf).unwrap_or(""));
340 n = 0;
341 }
342 m -= 1;
343 }
344 if n > 0 {
345 out.sub_part(
346 kind,
347 core::str::from_utf8(buf.get(..n).unwrap_or(&[])).unwrap_or(""),
348 );
349 }
350}
351
352pub(crate) fn literal_error(s: &str) -> Option<FormatError> {
356 match Decimal::parse(s.as_bytes()) {
357 Ok(_) => None,
358 Err(ParseError::Syntax) => Some(FormatError::BadOperand),
359 Err(ParseError::Limit) => Some(FormatError::UnsupportedOperation),
360 }
361}
362
363fn is_number_literal(key: &str) -> bool {
366 !matches!(split_literal(key.as_bytes()), Err(ParseError::Syntax))
367}
368
369#[derive(Clone, Copy, PartialEq, Eq, Debug)]
376pub struct NumberSpec {
377 bit: u8,
379 frac: Option<FracDefaults>,
381 integer: bool,
383 selectable: bool,
385 scale: i16,
387}
388
389impl NumberSpec {
390 pub const NUMBER: NumberSpec = NumberSpec {
392 bit: NUM,
393 frac: Some(FracDefaults { min: 0, max: 3 }),
394 integer: false,
395 selectable: true,
396 scale: 0,
397 };
398
399 pub const INTEGER: NumberSpec = NumberSpec {
401 bit: INT,
402 frac: Some(FracDefaults { min: 0, max: 0 }),
403 integer: true,
404 selectable: true,
405 scale: 0,
406 };
407
408 pub const OFFSET: NumberSpec = NumberSpec {
410 bit: 0,
411 frac: None,
412 integer: false,
413 selectable: true,
414 scale: 0,
415 };
416
417 pub const PERCENT: NumberSpec = NumberSpec {
420 bit: PCT,
421 frac: Some(FracDefaults { min: 0, max: 0 }),
422 integer: false,
423 selectable: true,
424 scale: 2,
425 };
426
427 pub const UNIT: NumberSpec = NumberSpec {
429 bit: UNIT,
430 frac: Some(FracDefaults { min: 0, max: 3 }),
431 integer: false,
432 selectable: false,
433 scale: 0,
434 };
435
436 pub const fn currency(fraction_digits: u8) -> NumberSpec {
440 NumberSpec {
441 bit: CUR,
442 frac: Some(FracDefaults {
443 min: fraction_digits,
444 max: fraction_digits,
445 }),
446 integer: false,
447 selectable: false,
448 scale: 0,
449 }
450 }
451}
452
453pub(crate) fn resolve(
455 spec: NumberSpec,
456 cx: &FnContext<'_>,
457 operand: Option<&Value<'_>>,
458 options: Options<'_, '_>,
459 errs: &mut dyn ErrorSink,
460) -> Option<Number> {
461 let is_offset = spec.bit == 0;
462 let Some(operand) = operand else {
463 errs.error(FormatError::BadOperand);
464 return None;
465 };
466 let resolved_operand = match operand {
467 Value::Number(n) | Value::Measure(Measure { number: n, .. }) => {
468 n.resolved.as_ref().map(|r| (&n.value, r))
469 }
470 _ => None,
471 };
472 let (mut value, mut o, inherited, mut selectable) = match resolved_operand {
473 Some((value, r)) => {
474 let mut o = r.opts;
475 let mut selectable = r.selectable;
476 if o.select.is_some() {
477 if spec.bit & (NUM | INT) != 0 {
481 errs.error(FormatError::BadOption);
482 selectable = false;
483 }
484 if !is_offset {
485 o.select = None;
486 }
487 }
488 if spec.bit == INT {
489 o.min_frac = None;
490 o.max_frac = None;
491 o.min_sig = None;
492 }
493 if spec.bit == PCT {
494 o.min_int = None;
495 o.rounding_increment = None;
496 }
497 if spec.bit == CUR {
498 o.min_frac = None;
500 o.max_frac = None;
501 }
502 (value.clone(), o, Some((r.frac, r.scale)), selectable)
503 }
504 None => match numeric_operand(operand, cx.host()) {
505 Ok(d) => (d, NumOpts::default(), None, true),
506 Err(e) => {
507 errs.error(e);
508 return None;
509 }
510 },
511 };
512 let mut offset: Option<(bool, u8)> = None;
513 let mut offset_bad = false;
514 for (name, v) in options.iter() {
515 if is_offset {
516 let sub = match name {
517 "add" => false,
518 "subtract" => true,
519 _ => continue, };
521 match (digit_size(v.value), offset) {
522 (Some(d), None) => offset = Some((sub, d)),
523 _ => offset_bad = true,
524 }
525 continue;
526 }
527 let Some(kind) = OPTIONS
528 .iter()
529 .find(|(n, mask, _)| *n == name && mask & spec.bit != 0)
530 .map(|&(_, _, k)| k)
531 else {
532 continue; };
534 if kind == Kind::Select {
535 if v.literal {
537 match v.value.as_str().and_then(select_named) {
538 Some(x) => o.select = Some(x),
539 None => errs.error(FormatError::BadOption),
540 }
541 } else {
542 errs.error(FormatError::BadOption);
543 selectable = false;
544 }
545 continue;
546 }
547 if !apply(&mut o, kind, v.value) {
548 errs.error(FormatError::BadOption);
549 }
550 }
551 if spec.integer {
552 let mode = o.rounding_mode.unwrap_or(RoundingMode::HalfExpand);
554 backend::round_to_integer(&mut value, mode, cx);
555 }
556 let (frac, scale) = match (spec.frac, inherited) {
557 (Some(f), _) => (f, spec.scale),
558 (None, Some(inherited)) => inherited,
559 (None, None) => (FracDefaults { min: 0, max: 3 }, 0),
560 };
561 if is_offset {
562 let Some((sub, d)) = offset.filter(|_| !offset_bad) else {
563 errs.error(FormatError::BadOption);
564 return None;
565 };
566 let mut delta = Decimal::from_u64(u64::from(d));
567 delta.set_negative(sub);
568 let Some(sum) = value.add(&delta) else {
569 errs.error(FormatError::UnsupportedOperation);
571 return None;
572 };
573 value = sum;
574 }
575 backend::limit_digit_sizes(&mut o, errs);
576 let plan = digit_plan(&o, frac, errs);
577 let shown = backend::shown(
578 &value,
579 scale,
580 &plan,
581 o.sign_display.unwrap_or(SignDisplay::Auto),
582 );
583 backend::check_host(cx, errs);
584 Some(Number {
585 value,
586 resolved: Some(Resolved {
587 opts: o,
588 frac,
589 scale,
590 selectable: selectable && spec.selectable,
591 shown,
592 }),
593 })
594}
595
596fn numeric_operand(v: &Value<'_>, host: &dyn Host) -> Result<Decimal, FormatError> {
598 match v {
599 Value::Str(s) | Value::Decimal(s) => Decimal::parse(s.as_bytes()).map_err(|e| match e {
600 ParseError::Syntax => FormatError::BadOperand,
601 ParseError::Limit => FormatError::UnsupportedOperation,
602 }),
603 Value::Int(n) => {
604 let mut d = Decimal::from_u64(n.unsigned_abs());
605 d.set_negative(*n < 0);
606 Ok(d)
607 }
608 _ => v
609 .to_number(host)
610 .map(|n| n.value)
611 .ok_or(FormatError::BadOperand),
612 }
613}
614
615struct Cmp<'k> {
619 key: &'k [u8],
620 pos: usize,
621 ok: bool,
622}
623
624impl Sink for Cmp<'_> {
625 fn push_str(&mut self, s: &str) {
626 let end = self.pos + s.len();
627 self.ok &= self.key.get(self.pos..end) == Some(s.as_bytes());
628 self.pos = end;
629 }
630}
631
632impl Cmp<'_> {
633 fn done(&self) -> bool {
634 self.ok && self.pos == self.key.len()
635 }
636}
637
638pub(crate) fn equals(key: &str, write: impl FnOnce(&mut dyn Sink)) -> bool {
640 let mut c = Cmp {
641 key: key.as_bytes(),
642 pos: 0,
643 ok: true,
644 };
645 write(&mut c);
646 c.done()
647}
648
649fn exact_matches(n: &Number, r: &Resolved, cx: &FnContext<'_>, key: &str) -> bool {
654 let o = &r.opts;
655 let plain =
656 o.min_frac.is_none() && o.min_int.is_none() && o.min_sig.is_none() && o.max_sig.is_none();
657 let mut value = n.value.clone();
658 if r.scale != 0 {
659 value.shift(r.scale);
660 }
661 if plain && value.is_integer() {
662 if value.is_zero() {
663 value.set_negative(false);
664 }
665 equals(key, |s| value.write_plain(s))
666 } else {
667 equals(key, |s| backend::write_selected(n, r, cx, s))
668 }
669}
670
671pub(crate) fn matches(n: &Number, cx: &FnContext<'_>, key: &str, errs: &mut dyn ErrorSink) -> bool {
673 let Some(r) = &n.resolved else {
674 return false;
675 };
676 if is_number_literal(key) {
677 return exact_matches(n, r, cx, key);
678 }
679 let Some(keyword) = Category::from_keyword(key) else {
680 errs.error(FormatError::BadVariantKey);
681 return false;
682 };
683 if r.opts.select == Some(Select::Exact) {
684 return false;
685 }
686 backend::category(n, r, cx) == keyword
687}
688
689pub(crate) fn better_than(key1: &str, key2: &str) -> bool {
692 is_number_literal(key1) && !is_number_literal(key2)
693}
694
695#[cfg(test)]
696mod tests;