diff --git a/arbi/scripts/size_in_radix.py b/arbi/scripts/size_in_radix.py new file mode 100644 index 0000000..5dddecd --- /dev/null +++ b/arbi/scripts/size_in_radix.py @@ -0,0 +1,50 @@ +############################################################################### +# Copyright 2025 Owain Davies +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# This file is part of "arbi", an Arbitrary Precision Integer library for Rust. +############################################################################### + +from decimal import getcontext, Decimal, ROUND_CEILING + +# Some high enough precision :) +getcontext().prec = 100 + + +# ceil( (log(2) / log(base)) * 2^(digit_bits) ) +# Useful for computing the needed capacity for Arbi::to_string()-type functions +def log2_div_logb(base: int, digit_bits: int = 32) -> int: + x = Decimal(2).ln() / Decimal(base).ln() + scaled = x * Decimal(1 << digit_bits) + + return int(scaled.to_integral_exact(ROUND_CEILING)) + + +# ceil( (log(base) / log(2^(digit_bits))) * 2^(digit_bits) ) +# Useful for computing the needed capacity for Arbi::from_str()-type functions +def logb_div_logab(base: int, digit_bits: int = 32) -> int: + x = Decimal(base).ln() / Decimal(1 << digit_bits).ln() + scaled = x * Decimal(1 << digit_bits) + + return int(scaled.to_integral_exact(ROUND_CEILING)) + + +def print_arrays(): + def print_array(prefix: str, digit_bits: int, func, start_base: int): + print(f"const {prefix}_{digit_bits}: [u{digit_bits}; 37] = [") + + max_hex_digits = digit_bits >> 2 + for base in range(start_base): # Use 0 for bases [0, start_base) + print(f"{' ' * 4}0x{'0' * max_hex_digits},") + for base in range(start_base, 37): + print(f"{' ' * 4}0x{func(base, digit_bits):x},") + + print("];\n") + + for digit_bits in (32, 64): + print_array("SCALED_LOG2_DIV_LOG", digit_bits, log2_div_logb, 3) + print_array("SCALED_LOGB_DIV_LOGAB", digit_bits, logb_div_logab, 2) + + +if __name__ == "__main__": + print_arrays() diff --git a/arbi/src/assign_string.rs b/arbi/src/assign_string.rs index 8cd320f..db27fc5 100644 --- a/arbi/src/assign_string.rs +++ b/arbi/src/assign_string.rs @@ -4,9 +4,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT */ use crate::from_string::{configs::BASE_MBS, BaseMbs, ParseError}; -use crate::uints::UnsignedUtilities; -use crate::Base; -use crate::{Arbi, Digit}; +use crate::{Arbi, Base, Digit}; impl Arbi { /// Assign the integer value the provided string represents to this `Arbi` @@ -137,20 +135,85 @@ impl Arbi { return Ok(()); } - // Get configuration for this base - let base_val = base.value() as u32; - let BaseMbs { mbs, base_pow_mbs } = BASE_MBS[base_val as usize]; + let base_value = base.value() as u32; - // Reserve estimated capacity - let estimate = usize::div_ceil_(base_digits.len(), mbs); - self.vec - .reserve(estimate.saturating_sub(self.vec.capacity())); + let capacity = Self::size_with_size_base_maybe_over_by_one( + base_value, + base_digits.len(), + ); self.vec.clear(); - self.neg = has_minus_sign; + self.vec.reserve(capacity); #[cfg(debug_assertions)] let initial_capacity = self.vec.capacity(); + self.neg = has_minus_sign; + + if base_value.is_power_of_two() { + self.assign_str_radix_algo_pow2(base_digits, base_value)? + } else { + self.assign_str_radix_algo_generic(base_digits, base_value)? + } + + #[cfg(debug_assertions)] + { + debug_assert_eq!(self.vec.capacity(), initial_capacity); + debug_assert!( + self.vec.len() == capacity || self.vec.len() == capacity - 1 + ); + } + + Ok(()) + } + + fn assign_str_radix_algo_pow2( + &mut self, + base_digits: &[u8], + base: u32, + ) -> Result<(), ParseError> { + debug_assert!(!base_digits.is_empty()); + debug_assert!((2..=36).contains(&base) && base.is_power_of_two()); + + // Number of bits in a base-`base` digit. + let bits_in_base_digit: u32 = base.trailing_zeros(); + let mut shift: u32 = 0; + let mut digit: Digit = 0; + for &c in base_digits.iter().rev() { + let base_digit: Digit = match (c as char).to_digit(base) { + Some(base_digit) => base_digit as Digit, + None => return Err(ParseError::InvalidDigit), + }; + digit |= base_digit << shift; + shift += bits_in_base_digit; + if Digit::BITS <= shift { + self.vec.push(digit); + shift -= Digit::BITS; + /* + On Digit::BITS == shift, digit is simply reset to 0. + On Digit::BITS < shift, digit will be set to the remaining + bits in the current base digit that could not fit in the + just pushed Arbi::BASE-digit, so no information is lost. + */ + digit = base_digit >> (bits_in_base_digit - shift); + } + } + if digit != 0 { + self.vec.push(digit); + } + + Ok(()) + } + + /* Generic Algorithm (works for all valid bases) */ + fn assign_str_radix_algo_generic( + &mut self, + base_digits: &[u8], + base: u32, + ) -> Result<(), ParseError> { + debug_assert!(!base_digits.is_empty()); + debug_assert!((2..=36).contains(&base)); + + let BaseMbs { mbs, base_pow_mbs } = BASE_MBS[base as usize]; let n_base = base_digits.len(); let rem_batch_size = n_base % mbs; let mut pos = 0; @@ -162,9 +225,9 @@ impl Arbi { // Convert batch substring to integer value let end = pos + rem_batch_size; while pos < end { - match (base_digits[pos] as char).to_digit(base_val) { - Some(digit) => { - batch = digit + batch * base_val; + match (base_digits[pos] as char).to_digit(base) { + Some(base_digit) => { + batch = base_digit + batch * base; pos += 1; } None => return Err(ParseError::InvalidDigit), @@ -181,9 +244,9 @@ impl Arbi { // Convert batch substring to integer value let end = pos + mbs; while pos < end { - match (base_digits[pos] as char).to_digit(base_val) { - Some(digit) => { - batch = digit + batch * base_val; + match (base_digits[pos] as char).to_digit(base) { + Some(base_digit) => { + batch = base_digit + batch * base; pos += 1; } None => return Err(ParseError::InvalidDigit), @@ -192,10 +255,8 @@ impl Arbi { Self::imul1add1(self, base_pow_mbs, Some(batch)); } - #[cfg(debug_assertions)] - debug_assert_eq!(self.vec.capacity(), initial_capacity); - self.trim(); + Ok(()) } diff --git a/arbi/src/multiplication.rs b/arbi/src/multiplication.rs index a8fb361..95d3f29 100644 --- a/arbi/src/multiplication.rs +++ b/arbi/src/multiplication.rs @@ -26,7 +26,7 @@ impl Arbi { /// /// ## Complexity /// \\( O(n) \\) - #[allow(dead_code)] + #[inline(always)] pub(crate) fn imul1add1(x: &mut Self, v: Digit, k: Option) { let mut k: Digit = k.unwrap_or(0); for d in &mut x.vec { diff --git a/arbi/src/size.rs b/arbi/src/size.rs index c266008..4e7d4cc 100644 --- a/arbi/src/size.rs +++ b/arbi/src/size.rs @@ -16,13 +16,10 @@ impl Arbi { /// # Examples /// ``` /// use arbi::{Arbi, Digit}; - /// /// let zero = Arbi::zero(); /// assert_eq!(zero.size(), 0); - /// /// let mut a = Arbi::from(Digit::MAX); /// assert_eq!(a.size(), 1); - /// /// a += 1; /// assert_eq!(a.size(), 2); /// ``` @@ -42,10 +39,8 @@ impl Arbi { /// # Examples /// ``` /// use arbi::{Arbi, BitCount, Digit}; - /// /// let zero = Arbi::zero(); /// assert_eq!(zero.size_bits(), 0); - /// /// let mut a = Arbi::from(Digit::MAX); /// assert_eq!(a.size_bits(), Digit::BITS as BitCount); /// a += 1; @@ -106,15 +101,12 @@ impl Arbi { /// # Examples /// ``` /// use arbi::Arbi; - /// /// let mut zero = Arbi::zero(); /// assert_eq!(zero.size_base_mut(10), 0); /// assert_eq!(zero, 0); - /// /// let mut one = Arbi::one(); /// assert_eq!(one.size_base_mut(10), 1); /// assert_eq!(one, 1); - /// /// let mut a = Arbi::from_str_radix("123456789", 10).unwrap(); /// assert_eq!(a.size_base_mut(10), 9); /// assert_eq!(a, 9); @@ -148,13 +140,10 @@ impl Arbi { /// # Examples /// ``` /// use arbi::Arbi; - /// /// let zero = Arbi::zero(); /// assert_eq!(zero.size_base_ref(10), 0); - /// /// let one = Arbi::one(); /// assert_eq!(one.size_base_ref(10), 1); - /// /// let a = Arbi::from_str_radix("123456789", 10).unwrap(); /// assert_eq!(a.size_base_ref(10), 9); /// ``` @@ -176,7 +165,7 @@ impl Arbi { pub(crate) fn size_radix_no_check(&mut self, base: u32) -> BitCount { let mut count: BitCount = 0; - while self > 0 { + while self.size() != 0 { Self::div_algo_digit_inplace(self, base as Digit); count += 1; } @@ -192,7 +181,6 @@ impl Arbi { } if base.is_power_of_two() { let bit_length = self.size_bits(); - // let base_log2 = base.ilog2(); let base_log2 = u32::ilog2_(base); return Some(BitCount::div_ceil_( bit_length, diff --git a/arbi/src/util/mul.rs b/arbi/src/util/mul.rs index 2b88551..b0b5537 100644 --- a/arbi/src/util/mul.rs +++ b/arbi/src/util/mul.rs @@ -3,7 +3,58 @@ Copyright 2025 Owain Davies SPDX-License-Identifier: Apache-2.0 OR MIT */ +macro_rules! define_mul2_common { + /* Common parts */ + ($uint:ty) => { + const UINT_BITS: u32 = <$uint>::BITS; + const UINT_H_BITS: u32 = UINT_BITS >> 1; + const UINT_H_BASE: $uint = 1 as $uint << UINT_H_BITS; + const UINT_H_MAX: $uint = UINT_H_BASE - 1; // MASK for low part of UINT + + #[inline(always)] + #[allow(dead_code)] + const fn split_uint(x: $uint) -> ($uint, $uint) { + let hi = x >> UINT_H_BITS; + let lo = x & UINT_H_MAX; + (hi, lo) + } + + #[inline(always)] + #[allow(dead_code)] + pub(crate) const fn mul2(a: $uint, b: $uint) -> ($uint, $uint) { + let (a_hi, a_lo) = split_uint(a); + let (b_hi, b_lo) = split_uint(b); + + let mut ahbh = a_hi * b_hi; + let ahbl = a_hi * b_lo; + let mut albh = a_lo * b_hi; + let albl = a_lo * b_lo; + + let (albl_hi, albl_lo) = split_uint(albl); + + albh += albl_hi; + debug_assert!(albh >= albl_hi); + + let (albh, overflow) = albh.overflowing_add(ahbl); + if overflow { + ahbh = ahbh.wrapping_add(UINT_H_BASE); + } + + ( + ahbh.wrapping_add(albh >> UINT_H_BITS), + albl_lo.wrapping_add(albh << UINT_H_BITS), + ) + } + }; +} + macro_rules! define_mul2 { + // Defines only mul2() + ($uint:ty) => { + define_mul2_common!($uint); + }; + + // Defines both mul2(), mul2_halves() // mul2(), mul2_halves() implement // // a * b -> (hi, lo) @@ -16,80 +67,40 @@ macro_rules! define_mul2 { // uint_h : primitive unsigned integer type with size in bits half that // of uint. ($uint:ty, $uint_h:ty) => { + define_mul2_common!($uint); -const UINT_BITS: u32 = <$uint>::BITS; -const UINT_H_BITS: u32 = UINT_BITS >> 1; -const UINT_H_BASE: $uint = 1 as $uint << UINT_H_BITS; -const UINT_H_MAX: $uint = UINT_H_BASE - 1; // MASK for low part of UINT - -#[inline(always)] -#[allow(dead_code)] -const fn split_uint(x: $uint) -> ($uint, $uint) { - let hi = x >> UINT_H_BITS; - let lo = x & UINT_H_MAX; - (hi, lo) -} - -#[inline(always)] -const fn split_uint_halves(x: $uint) -> ($uint_h, $uint_h) { - let hi = (x >> UINT_H_BITS) as $uint_h; - let lo = (x & UINT_H_MAX) as $uint_h; - (hi, lo) -} - -#[inline] -#[allow(dead_code)] -pub(crate) const fn mul2(a: $uint, b: $uint) -> ($uint, $uint) { - let (a_hi, a_lo) = split_uint(a); - let (b_hi, b_lo) = split_uint(b); - - let mut ahbh = a_hi * b_hi; - let ahbl = a_hi * b_lo; - let mut albh = a_lo * b_hi; - let albl = a_lo * b_lo; - - let (albl_hi, albl_lo) = split_uint(albl); - - albh += albl_hi; - debug_assert!(albh >= albl_hi); - - let (albh, overflow) = albh.overflowing_add(ahbl); - if overflow { - ahbh = ahbh.wrapping_add(UINT_H_BASE); - } - - ( - ahbh.wrapping_add(albh >> UINT_H_BITS), - albl_lo.wrapping_add(albh << UINT_H_BITS), - ) -} + #[inline(always)] + const fn split_uint_halves(x: $uint) -> ($uint_h, $uint_h) { + let hi = (x >> UINT_H_BITS) as $uint_h; + let lo = (x & UINT_H_MAX) as $uint_h; + (hi, lo) + } -#[inline] -pub(crate) const fn mul2_halves(a: $uint, b: $uint) -> ($uint, $uint) { - let (a_hi, a_lo) = split_uint_halves(a); - let (b_hi, b_lo) = split_uint_halves(b); + #[inline(always)] + pub(crate) const fn mul2_halves(a: $uint, b: $uint) -> ($uint, $uint) { + let (a_hi, a_lo) = split_uint_halves(a); + let (b_hi, b_lo) = split_uint_halves(b); - let mut ahbh: $uint = (a_hi as $uint) * (b_hi as $uint); - let ahbl: $uint = (a_hi as $uint) * (b_lo as $uint); - let mut albh: $uint = (a_lo as $uint) * (b_hi as $uint); - let albl: $uint = (a_lo as $uint) * (b_lo as $uint); + let mut ahbh: $uint = (a_hi as $uint) * (b_hi as $uint); + let ahbl: $uint = (a_hi as $uint) * (b_lo as $uint); + let mut albh: $uint = (a_lo as $uint) * (b_hi as $uint); + let albl: $uint = (a_lo as $uint) * (b_lo as $uint); - let (albl_hi, albl_lo) = split_uint_halves(albl); + let (albl_hi, albl_lo) = split_uint_halves(albl); - albh += albl_hi as $uint; - debug_assert!(albh >= albl_hi as $uint); + albh += albl_hi as $uint; + debug_assert!(albh >= albl_hi as $uint); - let (albh, overflow) = albh.overflowing_add(ahbl); - if overflow { - ahbh = ahbh.wrapping_add(UINT_H_BASE); - } - - ( - ahbh.wrapping_add(albh >> UINT_H_BITS), - (albl_lo as $uint).wrapping_add(albh << UINT_H_BITS), - ) -} + let (albh, overflow) = albh.overflowing_add(ahbl); + if overflow { + ahbh = ahbh.wrapping_add(UINT_H_BASE); + } + ( + ahbh.wrapping_add(albh >> UINT_H_BITS), + (albl_lo as $uint).wrapping_add(albh << UINT_H_BITS), + ) + } }; } @@ -102,6 +113,10 @@ mod u64_impl { define_mul2!(u64, u32); } +pub(crate) mod usize_impl { + define_mul2!(usize); +} + /* TODO: clean up and reduce repetition */ #[cfg(test)] mod tests { diff --git a/arbi/src/util/radix_info.rs b/arbi/src/util/radix_info.rs index 6b019f5..0e268a5 100644 --- a/arbi/src/util/radix_info.rs +++ b/arbi/src/util/radix_info.rs @@ -3,8 +3,8 @@ Copyright 2025 Owain Davies SPDX-License-Identifier: Apache-2.0 OR MIT */ -use crate::util::mul::u128_impl; -use crate::{Arbi, BitCount, Digit}; +use crate::util::mul::{u128_impl, usize_impl}; +use crate::{Arbi, BitCount, DDigit, Digit}; /* Given an n-digit integer in base b, the largest integer representable is @@ -42,7 +42,7 @@ impl Arbi { array will be used. The values in both arrays were precomputed via a Python script using - Python `decimal.Decimal` objects. + Python `decimal.Decimal` objects. See scripts/size_in_radix.py. Technically, the results in the former array can be accurately computed simply using f64s. However, f64s won't give us accurate values for the @@ -50,8 +50,8 @@ impl Arbi { help of u128s can be used for the former, but higher width integers (like Arbi integers) would be needed for the latter. */ - /// ceil( (log(2) / log(base)) * 2^(Digit::BITS) ) - const SCALED_LOG2_DIV_LOG: [Digit; 37] = [ + /// ceil( (log(2) / log(base)) * 2^(32) ) + const SCALED_LOG2_DIV_LOG_32: [u32; 37] = [ 0x00000000, 0x00000000, 0x00000000, 0xa1849cc2, 0x80000000, 0x6e40d1a5, 0x6308c91c, 0x5b3064ec, 0x55555556, 0x50c24e61, 0x4d104d43, 0x4a002708, 0x4768ce0e, 0x452e53e4, 0x433cfffc, 0x41867712, 0x40000000, 0x3ea16afe, @@ -110,6 +110,7 @@ impl Arbi { /// # Panic /// This function panics if `base` is not in the half-open interval /// `(2, 36]`, or if `size_bits` is `0`. + #[inline] pub(crate) const fn size_base_with_size_bits_maybe_over_by_one( base: u32, size_bits: BitCount, @@ -117,7 +118,8 @@ impl Arbi { assert!(base > 2 && base <= 36, "base must be in (2, 36]"); assert!(size_bits != 0); - let multiplicand = Self::SCALED_LOG2_DIV_LOG[base as usize] as BitCount; + let multiplicand = + Self::SCALED_LOG2_DIV_LOG_32[base as usize] as BitCount; if let Some(product) = size_bits.checked_mul(multiplicand) { product >> Digit::BITS @@ -133,6 +135,102 @@ impl Arbi { } .wrapping_add(1) } + + /// ceil( (log(base) / log(2^(32))) * 2^(32) ) + const SCALED_LOGB_DIV_LOGAB_32: [u32; 37] = [ + 0x00000000, 0x00000000, 0x8000000, 0xcae00d2, 0x10000000, 0x12934f0a, + 0x14ae00d2, 0x16757680, 0x18000001, 0x195c01a4, 0x1a934f0a, 0x1bacea7d, + 0x1cae00d2, 0x1d9a8024, 0x1e757680, 0x1f414fdc, 0x20000000, 0x20b31fb8, + 0x215c01a4, 0x21fbc16c, 0x22934f0a, 0x23237752, 0x23acea7d, 0x24304141, + 0x24ae00d2, 0x25269e13, 0x259a8024, 0x260a0276, 0x26757680, 0x26dd2524, + 0x27414fdc, 0x27a231ad, 0x28000000, 0x285aeb4e, 0x28b31fb8, 0x2908c589, + 0x295c01a4, + ]; + + #[allow(dead_code)] + /// ceil( (log(base) / log(2^(64))) * 2^(64) ) + const SCALED_LOGB_DIV_LOGAB_64: [u64; 37] = [ + 0x0000000000000000, + 0x0000000000000000, + 0x400000000000000, + 0x6570068e7ef5a1f, + 0x800000000000000, + 0x949a784bcd1b8b0, + 0xa570068e7ef5a1f, + 0xb3abb3faa02166d, + 0xc00000000000001, + 0xcae00d1cfdeb43d, + 0xd49a784bcd1b8b0, + 0xdd6753e032ea0f0, + 0xe570068e7ef5a1f, + 0xecd4011c8f1197a, + 0xf3abb3faa02166d, + 0xfa0a7eda4c112cf, + 0x1000000000000000, + 0x10598fdbeb244c5a, + 0x10ae00d1cfdeb43d, + 0x10fde0b5c8134052, + 0x1149a784bcd1b8b0, + 0x1191bba891f1708c, + 0x11d6753e032ea0f0, + 0x121820a01ac754cc, + 0x12570068e7ef5a1f, + 0x12934f0979a37160, + 0x12cd4011c8f1197a, + 0x1305013ab7ce0e5c, + 0x133abb3faa02166d, + 0x136e9291eaa65b4a, + 0x13a0a7eda4c112cf, + 0x13d118d66c4d4e56, + 0x1400000000000000, + 0x142d75a6eb1dfb0f, + 0x14598fdbeb244c5a, + 0x148462c466d3cf1d, + 0x14ae00d1cfdeb43d, + ]; + + /// Compute the number of base-`Arbi::BASE` digits needed to represent a + /// nonnegative integer with `size_base` `base` digits. The true value or + /// one higher than it will be returned. + /// + /// # Panic + /// This function panics if `base` is not in the closed interval `[2, 36]`, + /// or if `size_base` is `0`. + #[inline] + pub(crate) const fn size_with_size_base_maybe_over_by_one( + base: u32, + size_base: usize, + ) -> usize { + assert!(base >= 2 && base <= 36, "base must be in [2, 36]"); + assert!(size_base != 0); + + /* + TODO: is this even worth it? The code after this block suffices for + this case as well. + */ + if base.is_power_of_two() { + // This will always be exact for base 2, 4, and 16, but may be off + // by 1 for base 8 or 32. + let base_log2 = base.trailing_zeros(); + let bit_length = size_base as BitCount * base_log2 as BitCount; + return crate::uints::div_ceil_bitcount( + bit_length, + Digit::BITS as BitCount, + ) as usize; + } + + let multiplicand: Digit = Self::SCALED_LOGB_DIV_LOGAB_32[base as usize]; + + if Digit::BITS >= usize::BITS { + let product = (size_base as DDigit) * (multiplicand as DDigit); + (product >> Digit::BITS) as usize + } else { + let (hi, lo) = usize_impl::mul2(size_base, multiplicand as usize); + debug_assert!(hi >> Digit::BITS == 0); + (hi << (usize::BITS - Digit::BITS)) | (lo >> Digit::BITS) + } + .wrapping_add(1) + } } #[cfg(test)] @@ -188,4 +286,80 @@ mod tests_size_base_with_size_bits { } } } + + #[test] + fn smoke_size_with_size_base() { + use crate::Arbi; + + let (mut rng, _) = get_seedable_rng(); + let d1 = get_uniform_die(0, Digit::MAX); + let d2 = get_uniform_die(Digit::MAX as DDigit + 1, DDigit::MAX); + let d3 = get_uniform_die(DDigit::MAX as QDigit + 1, QDigit::MAX); + + for _ in 0..i16::MAX { + let nums = [ + d1.sample(&mut rng) as u128, + d2.sample(&mut rng) as u128, + d3.sample(&mut rng) as u128, + ]; + + for num in nums { + if num == 0 { + continue; + } + let arbi = Arbi::from(num); + let actual = arbi.size(); + for base in 2..=36 { + let sz_base = size_base(num, base) as usize; + let estimated = Arbi::size_with_size_base_maybe_over_by_one( + base, sz_base, + ); + assert!( + estimated == actual || estimated == actual + 1, + "arbi = {}, estimate = {}, actual = {}, base = {}", + arbi, + estimated, + actual, + base + ); + } + } + } + } + + #[test] + #[cfg(feature = "rand")] + fn smoke_size_with_size_base_large() { + use crate::{Arbi, RandomArbi}; + + let (mut rng, _) = get_seedable_rng(); + + for _ in 0..1000 { + let arbi = rng.gen_iarbi(2420); + if arbi == 0 { + continue; + } + let actual = arbi.size(); + for base in 2..=36 { + let arbi_string = arbi.to_string_radix(base); + let sz_base = if arbi.is_negative() { + arbi_string.len() - 1 + } else { + arbi_string.len() + }; + /* TODO: this should not be slower than to_string()... */ + // let sz_base = arbi.size_base_ref(base) as usize; + let estimated = + Arbi::size_with_size_base_maybe_over_by_one(base, sz_base); + assert!( + estimated == actual || estimated == actual + 1, + "arbi = {}, estimate = {}, actual = {}, base = {}", + arbi, + estimated, + actual, + base + ); + } + } + } }