Repository: microsoft/regorus (language: python)

Issue:
Try out different arbitrary precision library
Switch to [scientific](https://docs.rs/scientific/latest/scientific/) crate. 


Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 410da745..09cdd2a7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -43,17 +43,16 @@ md-5 = {version = "0.10.6", optional = true}
 
 data-encoding = { version = "2.4.0", optional = true }
 jsonschema = { version = "0.17.1", optional = true }
+scientific = { version = "0.5.2" }
 
 regex = {version = "1.10.2", optional = true}
 semver = {version = "1.0.20", optional = true}
 wax = { version = "0.6.0", features = [], default-features = false, optional = true }
 url = { version = "2.5.0", optional = true }
-dashu-float = { version = "0.4.1", features = ["num-traits"] }
-num-traits = "0.2.17"
-dashu-base = "0.4.0"
 uuid = { version = "1.6.1", features = ["v4", "fast-rng"], optional = true }
 
 
+
 [dev-dependencies]
 clap = { version = "4.4.7", features = ["derive"] }
 colored-diff = "0.2.3"
diff --git a/build.rs b/build.rs
index 5cce5a3a..f2d9c3f7 100644
--- a/build.rs
+++ b/build.rs
@@ -2,10 +2,14 @@
 // Licensed under the MIT License.
 
 use anyhow::Result;
+use std::path::Path;
 
 fn main() -> Result<()> {
     // Copy hooks to appropriate location so that git will run them.
-    std::fs::copy("./scripts/pre-commit", "./.git/hooks/pre-commit")?;
-    std::fs::copy("./scripts/pre-push", "./.git/hooks/pre-push")?;
+    // In git worktrees, .git is a symlink and the following commands fail.
+    if Path::new(".git").is_dir() {
+        std::fs::copy("./scripts/pre-commit", "./.git/hooks/pre-commit")?;
+        std::fs::copy("./scripts/pre-push", "./.git/hooks/pre-push")?;
+    }
     Ok(())
 }
diff --git a/src/builtins/encoding.rs b/src/builtins/encoding.rs
index 8ce83f0f..e2326b57 100644
--- a/src/builtins/encoding.rs
+++ b/src/builtins/encoding.rs
@@ -3,13 +3,15 @@
 
 use crate::ast::{Expr, Ref};
 use crate::builtins;
-use crate::builtins::utils::{ensure_args_count, ensure_string};
+use crate::builtins::utils::{
+    ensure_args_count, ensure_object, ensure_string, ensure_string_collection,
+};
 use crate::lexer::Span;
 use crate::value::Value;
 
 use std::collections::{BTreeMap, HashMap};
 
-use anyhow::{bail, Context, Result};
+use anyhow::{anyhow, bail, Context, Result};
 
 pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
     #[cfg(feature = "base64")]
@@ -31,7 +33,10 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
     }
     #[cfg(feature = "urlquery")]
     {
+        m.insert("urlquery.decode", (urlquery_decode, 1));
         m.insert("urlquery.decode_object", (urlquery_decode_object, 1));
+        m.insert("urlquery.encode", (urlquery_encode, 1));
+        m.insert("urlquery.encode_object", (urlquery_encode_object, 1));
     }
     m.insert("json.is_valid", (json_is_valid, 1));
     m.insert("json.marshal", (json_marshal, 1));
@@ -115,11 +120,13 @@ fn base64url_decode(
         Err(_) => {
             #[cfg(feature = "base64url")]
             {
-                data_encoding::BASE64URL_NOPAD.decode(encoded_str.as_bytes())?
+                data_encoding::BASE64URL_NOPAD
+                    .decode(encoded_str.as_bytes())
+                    .map_err(|_| anyhow!(params[0].span().error("not a valid url")))?
             }
             #[cfg(not(feature = "base64url"))]
             {
-                bail!(params[0].span().error("nor a valid url"));
+                bail!(params[0].span().error("not a valid url"));
             }
         }
     };
@@ -188,6 +195,34 @@ fn hex_encode(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool)
     ))
 }
 
+#[cfg(feature = "urlquery")]
+fn urlquery_decode(
+    span: &Span,
+    params: &[Ref<Expr>],
+    args: &[Value],
+    _strict: bool,
+) -> Result<Value> {
+    let name = "urlquery.decode";
+    ensure_args_count(span, name, params, args, 1)?;
+
+    let string = ensure_string(name, &params[0], &args[0])?;
+    let url_string = "https://non-existent?".to_owned() + string.as_ref();
+    let url = match url::Url::parse(&url_string) {
+        Ok(v) => v,
+        Err(_) => bail!(params[0].span().error("not a valid url query")),
+    };
+
+    let mut query_str = "".to_owned();
+    for (k, v) in url.query_pairs() {
+        query_str += &k;
+        if v != "" {
+            query_str += "=";
+            query_str += &v;
+        }
+    }
+    Ok(Value::String(query_str.into()))
+}
+
 #[cfg(feature = "urlquery")]
 fn urlquery_decode_object(
     span: &Span,
@@ -195,7 +230,7 @@ fn urlquery_decode_object(
     args: &[Value],
     _strict: bool,
 ) -> Result<Value> {
-    let name = "urlquery.encode";
+    let name = "urlquery.decode_object";
     ensure_args_count(span, name, params, args, 1)?;
 
     let string = ensure_string(name, &params[0], &args[0])?;
@@ -215,7 +250,7 @@ fn urlquery_decode_object(
     }
     Ok(Value::from_map(map))
 }
-/*
+
 #[cfg(feature = "urlquery")]
 fn urlquery_encode(
     span: &Span,
@@ -226,17 +261,54 @@ fn urlquery_encode(
     let name = "urlquery.encode";
     ensure_args_count(span, name, params, args, 1)?;
 
-    let string = ensure_string(name, &params[0], &args[0])?;
-    let url_string = "https://non-existent?" + string;
-    let url = url::Url::parse(&url_string)
-        .map_err(|_| bail!(params[0].span().error("not a valid url query")))?;
-
-    Ok(Value::from_object(
-        url.query_pairs()
-            .map(|(k, v)| (Value::from(k.clone()), Value::from(v.clone())))
-            .collect(),
-    ))
-}*/
+    let s = ensure_string(name, &params[0], &args[0])?;
+    let mut url = match url::Url::parse("https://non-existent") {
+        Ok(v) => v,
+        Err(_) => bail!(params[0].span().error("not a valid url query")),
+    };
+
+    url.query_pairs_mut().append_pair(&s, "");
+    let query_str = url.query().unwrap_or("");
+    if query_str.is_empty() {
+        Ok(Value::String("".into()))
+    } else {
+        Ok(Value::String(query_str[..query_str.len() - 1].into()))
+    }
+}
+
+#[cfg(feature = "urlquery")]
+fn urlquery_encode_object(
+    span: &Span,
+    params: &[Ref<Expr>],
+    args: &[Value],
+    _strict: bool,
+) -> Result<Value> {
+    let name = "urlquery.encode_object";
+    ensure_args_count(span, name, params, args, 1)?;
+
+    let obj = ensure_object(name, &params[0], args[0].clone())?;
+    let mut url = match url::Url::parse("https://non-existent") {
+        Ok(v) => v,
+        Err(_) => bail!(params[0].span().error("not a valid url query")),
+    };
+
+    for (key, value) in obj.iter() {
+        let key = ensure_string(name, &params[0], key)?;
+        match value {
+            Value::String(v) => {
+                url.query_pairs_mut().append_pair(key.as_ref(), v.as_ref());
+            }
+            _ => {
+                let values = ensure_string_collection(name, &params[0], value)?;
+                for v in values {
+                    url.query_pairs_mut().append_pair(key.as_ref(), v);
+                }
+            }
+        }
+    }
+
+    Ok(Value::String(url.query().unwrap_or("").into()))
+}
 
 #[cfg(feature = "yaml")]
 fn yaml_is_valid(
diff --git a/src/builtins/strings.rs b/src/builtins/strings.rs
index cb37f145..c83ceb8a 100644
--- a/src/builtins/strings.rs
+++ b/src/builtins/strings.rs
@@ -21,8 +21,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
     m.insert("endswith", (endswith, 2));
     m.insert("format_int", (format_int, 2));
     m.insert("indexof", (indexof, 2));
-    // TODO: implement this correctly.
-    //m.insert("indexof_n", (indexof_n, 2));
+    m.insert("indexof_n", (indexof_n, 2));
     m.insert("lower", (lower, 1));
     m.insert("replace", (replace, 3));
     m.insert("split", (split, 2));
@@ -32,7 +31,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
     m.insert("strings.any_suffix_match", (any_suffix_match, 2));
     m.insert("strings.replace_n", (replace_n, 2));
     m.insert("strings.reverse", (reverse, 1));
-    m.insert("strings.substring", (substring, 3));
+    m.insert("substring", (substring, 3));
     m.insert("trim", (trim, 2));
     m.insert("trim_left", (trim_left, 2));
     m.insert("trim_prefix", (trim_prefix, 2));
@@ -102,13 +101,14 @@ fn indexof(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) ->
     ensure_args_count(span, name, params, args, 2)?;
     let s1 = ensure_string(name, &params[0], &args[0])?;
     let s2 = ensure_string(name, &params[1], &args[1])?;
-    Ok(Value::from(Number::from(match s1.find(s2.as_ref()) {
-        Some(pos) => pos as i64,
-        _ => -1,
-    })))
+    for (pos, (idx, _)) in s1.char_indices().enumerate() {
+        if s1[idx..].starts_with(s2.as_ref()) {
+            return Ok(Value::from(Number::from(pos)));
+        }
+    }
+    Ok(Value::from(Number::from(-1i64)))
 }
 
-#[allow(dead_code)]
 fn indexof_n(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
     let name = "indexof_n";
     ensure_args_count(span, name, params, args, 2)?;
@@ -116,13 +116,9 @@ fn indexof_n(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -
     let s2 = ensure_string(name, &params[1], &args[1])?;
 
     let mut positions = vec![];
-    let mut idx = 0;
-    while idx < s1.len() {
-        if let Some(pos) = s1.find(s2.as_ref()) {
-            positions.push(Value::from(pos as u64));
-            idx = pos + 1;
-        } else {
-            break;
+    for (pos, (idx, _)) in s1.char_indices().enumerate() {
+        if s1[idx..].starts_with(s2.as_ref()) {
+            positions.push(Value::from(Number::from(pos)));
         }
     }
     Ok(Value::from_array(positions))
@@ -192,6 +188,21 @@ fn to_string(v: &Value, unescape: bool) -> String {
     }
 }
 
+enum Width {
+    None,
+    LeadingZeros(usize),
+    Cell(usize),
+    Decimals(usize),
+}
+
+fn apply_width(w: Width, s: String) -> String {
+    match w {
+        Width::LeadingZeros(n) if n > s.len() => "0".repeat(n - s.len()) + &s,
+        Width::Cell(n) if n > s.len() => " ".repeat(n - s.len()) + &s,
+        _ => s,
+    }
+}
+
 fn sprintf(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
     let name = "sprintf";
     ensure_args_count(span, name, params, args, 2)?;
@@ -203,13 +214,43 @@ fn sprintf(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) ->
     let mut chars = fmt.chars().peekable();
     let args_span = params[1].span();
     loop {
-        let verb = match chars.next() {
+        let (verb, width) = match chars.next() {
             Some('%') => match chars.next() {
                 Some('%') => {
                     s.push('%');
                     continue;
                 }
-                Some(c) => c,
+                Some(c) if c == '.' || c.is_numeric() => {
+                    let first_char = c;
+                    let mut w = 0;
+                    if c != '.' {
+                        w = c.to_digit(10).expect("could not get digit from char");
+                    }
+
+                    while chars.peek().map(|c| c.is_numeric()) == Some(true) {
+                        w = w * 10
+                            + chars
+                                .next()
+                                .expect("could not get next digit")
+                                .to_digit(10)
+                                .expect("could not get digit from char");
+                    }
+                    let width = match first_char {
+                        '0' => Width::LeadingZeros(w as usize),
+                        '.' => Width::Decimals(w as usize),
+                        _ => Width::Cell(w as usize),
+                    };
+                    match chars.next() {
+                        Some(c) => (c, width),
+                        _ => {
+                            let span = params[0].span();
+                            bail!(span.error(
+                                "missing format verb after `%width` at end of format string"
+                            ));
+                        }
+                    }
+                }
+                Some(c) => (c, Width::None),
                 None => {
                     let span = params[0].span();
                     bail!(span.error("missing format verb after `%` at end of format string"));
@@ -257,7 +298,7 @@ fn sprintf(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) ->
             ('s', Value::String(sv)) => s += sv.as_ref(),
             ('s', v) => s += &to_string(v, false),
 
-            ('v', _) => s += format!("{arg}").as_str(),
+            ('v', _) => s += &to_string(arg, false),
             ('b', Value::Number(f)) if f.is_integer() => {
                 let (sign, v) = get_sign_value(f);
                 s += sign;
@@ -279,43 +320,36 @@ fn sprintf(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) ->
             ('d', Value::Number(f)) if f.is_integer() => {
                 let (sign, v) = get_sign_value(f);
                 s += sign;
-                s += v.format_decimal().as_str()
+                s += apply_width(width, v.format_decimal()).as_str()
             }
             ('o', Value::Number(f)) if f.is_integer() => {
                 let (sign, v) = get_sign_value(f);
                 s += sign;
-                s += ("0O".to_owned() + &v.format_octal()).as_str()
+                s += apply_width(width, "0O".to_owned() + &v.format_octal()).as_str()
             }
             ('O', Value::Number(f)) if f.is_integer() => {
                 let (sign, v) = get_sign_value(f);
                 s += sign;
-                s += ("0o".to_owned() + &v.format_octal()).as_str()
+                s += apply_width(width, "0o".to_owned() + &v.format_octal()).as_str()
             }
             ('x', Value::Number(f)) if f.is_integer() => {
                 let (sign, v) = get_sign_value(f);
                 s += sign;
-                s += v.format_hex().as_str()
+                s += apply_width(width, v.format_hex()).as_str()
             }
             ('X', Value::Number(f)) if f.is_integer() => {
                 let (sign, v) = get_sign_value(f);
                 s += sign;
-                s += v.format_big_hex().as_str()
+                s += apply_width(width, v.format_big_hex()).as_str()
             }
-            ('e', Value::Number(f)) => {
-                s += match f.as_f64() {
-                    Some(f) => format!("{:e}", f),
-                    _ => bail!(span.error("cannot print large float using e format specifier")),
+            ('e', Value::Number(f)) => s += &f.format_scientific(),
+            ('E', Value::Number(f)) => s += &f.format_scientific().replace('e', "E"),
+            ('f' | 'F', Value::Number(f)) => {
+                s += &match width {
+                    Width::Decimals(d) => f.format_decimal_with_width(d as u32),
+                    _ => apply_width(width, f.format_decimal()),
                 }
-                .as_str()
             }
-            ('E', Value::Number(f)) => {
-                s += match f.as_f64() {
-                    Some(f) => format!("{:E}", f),
-                    _ => bail!(span.error("cannot print large float using E format specifier")),
-                }
-                .as_str()
-            }
-            ('f' | 'F', Value::Number(f)) => s += f.format_decimal().as_str(),
             ('g', Value::Number(f)) => {
                 let (sign, v) = get_sign_value(f);
                 let v = match v.as_f64() {
@@ -349,8 +383,7 @@ fn sprintf(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) ->
                 }
             }
             (_, Value::Number(_)) => {
-                // TODO: binary for floating point.
-                bail!(args_span.error("floating-point number specified for format verb {verb}."));
+                bail!(args_span.error(&format!("number specified for format verb {verb}.")));
             }
 
             ('+', _) if chars.next() == Some('v') => {
@@ -382,31 +415,45 @@ fn any_prefix_match(
     span: &Span,
     params: &[Ref<Expr>],
     args: &[Value],
-    _strict: bool,
+    strict: bool,
 ) -> Result<Value> {
     let name = "strings.any_prefix_match";
     ensure_args_count(span, name, params, args, 2)?;
 
     let search = match &args[0] {
         Value::String(s) => vec![s.as_ref()],
-        Value::Array(_) | Value::Set(_) => ensure_string_collection(name, &params[0], &args[0])?,
-        _ => {
+        Value::Array(_) | Value::Set(_) => {
+            match ensure_string_collection(name, &params[0], &args[0]) {
+                Ok(c) => c,
+                Err(e) if strict => return Err(e),
+                _ => return Ok(Value::Undefined),
+            }
+        }
+        _ if strict => {
             let span = params[0].span();
             bail!(span.error(
                 format!("`{name}` expects string/array[string]/set[string] argument.").as_str()
             ));
         }
+        _ => return Ok(Value::Undefined),
     };
 
     let base = match &args[1] {
         Value::String(s) => vec![s.as_ref()],
-        Value::Array(_) | Value::Set(_) => ensure_string_collection(name, &params[1], &args[1])?,
-        _ => {
+        Value::Array(_) | Value::Set(_) => {
+            match ensure_string_collection(name, &params[1], &args[1]) {
+                Ok(c) => c,
+                Err(e) if strict => return Err(e),
+                _ => return Ok(Value::Undefined),
+            }
+        }
+        _ if strict => {
             let span = params[0].span();
             bail!(span.error(
                 format!("`{name}` expects string/array[string]/set[string] argument.").as_str()
             ));
         }
+        _ => return Ok(Value::Undefined),
     };
 
     Ok(Value::Bool(
@@ -418,31 +465,45 @@ fn any_suffix_match(
     span: &Span,
     params: &[Ref<Expr>],
     args: &[Value],
-    _strict: bool,
+    strict: bool,
 ) -> Result<Value> {
     let name = "strings.any_suffix_match";
     ensure_args_count(span, name, params, args, 2)?;
 
     let search = match &args[0] {
         Value::String(s) => vec![s.as_ref()],
-        Value::Array(_) | Value::Set(_) => ensure_string_collection(name, &params[0], &args[0])?,
-        _ => {
+        Value::Array(_) | Value::Set(_) => {
+            match ensure_string_collection(name, &params[0], &args[0]) {
+                Ok(c) => c,
+                Err(e) if strict => return Err(e),
+                _ => return Ok(Value::Undefined),
+            }
+        }
+        _ if strict => {
             let span = params[0].span();
             bail!(span.error(
                 format!("`{name}` expects string/array[string]/set[string] argument.").as_str()
             ));
         }
+        _ => return Ok(Value::Undefined),
     };
 
     let base = match &args[1] {
         Value::String(s) => vec![s.as_ref()],
-        Value::Array(_) | Value::Set(_) => ensure_string_collection(name, &params[1], &args[1])?,
-        _ => {
+        Value::Array(_) | Value::Set(_) => {
+            match ensure_string_collection(name, &params[1], &args[1]) {
+                Ok(c) => c,
+                Err(e) if strict => return Err(e),
+                _ => return Ok(Value::Undefined),
+            }
+        }
+        _ if strict => {
             let span = params[0].span();
             bail!(span.error(
                 format!("`{name}` expects string/array[string]/set[string] argument.").as_str()
             ));
         }
+        _ => return Ok(Value::Undefined),
     };
 
     Ok(Value::Bool(
@@ -488,28 +549,24 @@ fn reverse(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) ->
     Ok(Value::String(s.chars().rev().collect::<String>().into()))
 }
 
-fn substring(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
+fn substring(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
     let name = "substring";
     ensure_args_count(span, name, params, args, 3)?;
     let s = ensure_string(name, &params[0], &args[0])?;
     let offset = ensure_numeric(name, &params[1], &args[1])?;
     let length = ensure_numeric(name, &params[2], &args[2])?;
 
-    // TODO: distinguish between 20.0 and 20
-    // Also: behavior of
-    // x = substring("hello", 20 + 0.0, 25)
-    match (offset.as_u64(), length.as_u64()) {
+    match (offset.as_i64(), length.as_i64()) {
+        (Some(offset), _) if offset < 0 && strict => {
+            bail!(params[1].span().error("negative offset"))
+        }
+        (Some(offset), _) if offset < 0 => Ok(Value::Undefined),
         (Some(offset), Some(length)) => {
-            let offset = offset as usize;
-            let length = length as usize;
-
-            if offset > s.len() || length <= offset {
-                return Ok(Value::String("".into()));
-            }
-
-            Ok(Value::String(s[offset..offset + length].into()))
+            let start = s.chars().skip(offset as usize);
+            let length = if length < 0 { s.len() } else { length as usize };
+            Ok(Value::String(start.take(length).collect::<String>().into()))
         }
-        _ => Ok(Value::Undefined),
+        _ => Ok(Value::String("".into())),
     }
 }
 
diff --git a/src/builtins/units.rs b/src/builtins/units.rs
index 6598c652..d3ea4b60 100644
--- a/src/builtins/units.rs
+++ b/src/builtins/units.rs
@@ -102,10 +102,10 @@ fn parse(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
     };
 
     if let Some(e) = ten_exp(suffix) {
-        n.mul_assign(&Number::ten_pow(e))?;
+        n.mul_assign(&Number::ten_pow(e)?)?;
         Ok(Value::from(n))
     } else if let Some(e) = two_exp(suffix) {
-        n.mul_assign(&Number::two_pow(e))?;
+        n.mul_assign(&Number::two_pow(e)?)?;
         Ok(Value::from(n))
     } else {
         return Ok(Value::Undefined);
@@ -182,10 +182,10 @@ fn parse_bytes(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool)
     };
 
     if let Some(e) = twob_exp(suffix) {
-        n.mul_assign(&Number::two_pow(e))?;
+        n.mul_assign(&Number::two_pow(e)?)?;
         Ok(Value::from(n.round()))
     } else if let Some(e) = tenb_exp(suffix) {
-        n.mul_assign(&Number::ten_pow(e))?;
+        n.mul_assign(&Number::ten_pow(e)?)?;
         Ok(Value::from(n.round()))
     } else {
         Ok(Value::Undefined)
diff --git a/src/number.rs b/src/number.rs
index c2977d7d..237e72ff 100644
--- a/src/number.rs
+++ b/src/number.rs
@@ -3,21 +3,18 @@
 
 use core::fmt::{Debug, Formatter};
 use std::cmp::{Ord, Ordering};
-use std::ops::{AddAssign, Div, MulAssign, SubAssign};
 use std::rc::Rc;
 use std::str::FromStr;
 
-use anyhow::{bail, Result};
-use dashu_float;
-use num_traits::cast::ToPrimitive;
+use anyhow::{anyhow, bail, Result};
 
 use serde::ser::Serializer;
 use serde::Serialize;
 
 pub type BigInt = i128;
 
-type BigFloat = dashu_float::DBig;
-const PRECISION: usize = 100;
+type BigFloat = scientific::Scientific;
+const PRECISION: scientific::Precision = scientific::Precision::Digits(100);
 
 #[derive(Clone, Debug, PartialEq)]
 pub struct BigDecimal {
@@ -45,28 +42,14 @@ impl From<BigFloat> for BigDecimal {
 impl From<i128> for BigDecimal {
     fn from(value: i128) -> Self {
         BigDecimal {
-            d: Into::<BigFloat>::into(value)
-                .with_precision(PRECISION)
-                .value(),
+            d: Into::<BigFloat>::into(value),
         }
     }
 }
 
-impl Serialize for BigDecimal {
-    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: Serializer,
-    {
-        let s = self.d.to_string();
-        let v = serde_json::Number::from_str(&s)
-            .map_err(|_| serde::ser::Error::custom("could not serialize big number"))?;
-        v.serialize(serializer)
-    }
-}
-
 impl BigDecimal {
     fn is_integer(&self) -> bool {
-        self.d.floor() == self.d
+        self.d.decimals() <= 0
     }
 }
 
@@ -90,22 +73,11 @@ impl Serialize for Number {
         S: Serializer,
     {
         match self {
-            Big(b) => {
-                if let Some(n) = self.as_u64() {
-                    n.serialize(serializer)
-                } else if let Some(n) = self.as_i64() {
-                    n.serialize(serializer)
-                } else {
-                    if let Some(f) = self.as_f64() {
-                        if b.d.digits() <= 15 {
-                            return f.serialize(serializer);
-                        }
-                    }
-                    let s = b.d.to_string();
-                    let v = serde_json::Number::from_str(&s)
-                        .map_err(|_| serde::ser::Error::custom("could not serialize big number"))?;
-                    v.serialize(serializer)
-                }
+            Big(_) => {
+                let s = self.format_decimal();
+                let v = serde_json::Number::from_str(&s)
+                    .map_err(|_| serde::ser::Error::custom("could not serialize big number"))?;
+                v.serialize(serializer)
             }
         }
     }
@@ -115,7 +87,7 @@ use Number::*;
 
 impl From<BigFloat> for Number {
     fn from(n: BigFloat) -> Self {
-        Self::Big(BigDecimal::from(n.with_precision(PRECISION).value()).into())
+        Self::Big(BigDecimal::from(n).into())
     }
 }
 
@@ -162,21 +134,33 @@ impl From<f64> for Number {
 impl Number {
     pub fn as_u64(&self) -> Option<u64> {
         match self {
-            Big(b) if b.is_integer() => b.d.to_u64(),
+            Big(b) if b.is_integer() => match u64::try_from(&b.d) {
+                Ok(v) => Some(v),
+                _ => None,
+            },
             _ => None,
         }
     }
 
     pub fn as_i64(&self) -> Option<i64> {
         match self {
-            Big(b) if b.is_integer() => b.d.to_i64(),
+            Big(b) if b.is_integer() => match i64::try_from(&b.d) {
+                Ok(v) => Some(v),
+                _ => None,
+            },
             _ => None,
         }
     }
 
     pub fn as_f64(&self) -> Option<f64> {
         match self {
-            Big(b) => Some(b.d.to_binary().value().to_f64().value()),
+            Big(b) => {
+                let f = f64::from(&b.d);
+                match BigFloat::try_from(f) {
+                    Ok(bf) if bf == b.d => Some(f),
+                    _ => None,
+                }
+            }
         }
     }
 
@@ -234,61 +218,56 @@ impl PartialOrd for Number {
 
 impl Number {
     pub fn add_assign(&mut self, rhs: &Self) -> Result<()> {
-        match (self, rhs) {
-            (Big(ref mut a), Big(b)) => {
-                Rc::make_mut(a).d.add_assign(&b.d);
-            }
-        }
+        *self = self.add(rhs)?;
         Ok(())
     }
 
     pub fn add(&self, rhs: &Self) -> Result<Number> {
-        let mut c = self.clone();
-        c.add_assign(rhs)?;
-        Ok(c)
+        match (self, rhs) {
+            (Big(a), Big(b)) => Ok(Big(BigDecimal::from(&a.d + &b.d).into())),
+        }
     }
 
     pub fn sub_assign(&mut self, rhs: &Self) -> Result<()> {
-        match (self, rhs) {
-            (Big(ref mut a), Big(b)) => {
-                Rc::make_mut(a).d.sub_assign(&b.d);
-            }
-        }
+        *self = self.sub(rhs)?;
         Ok(())
     }
 
     pub fn sub(&self, rhs: &Self) -> Result<Number> {
-        let mut c = self.clone();
-        c.sub_assign(rhs)?;
-        Ok(c)
+        match (self, rhs) {
+            (Big(a), Big(b)) => Ok(Big(BigDecimal::from(&a.d - &b.d).into())),
+        }
     }
 
     pub fn mul_assign(&mut self, rhs: &Self) -> Result<()> {
-        match (self, rhs) {
-            (Big(ref mut a), Big(b)) => {
-                Rc::make_mut(a).d.mul_assign(&b.d);
-            }
-        }
+        *self = self.mul(rhs)?;
         Ok(())
     }
 
     pub fn mul(&self, rhs: &Self) -> Result<Number> {
-        let mut c = self.clone();
-        c.mul_assign(rhs)?;
-        Ok(c)
+        match (self, rhs) {
+            (Big(a), Big(b)) => Ok(Big(BigDecimal::from(&a.d * &b.d).into())),
+        }
     }
 
     pub fn divide(self, rhs: &Self) -> Result<Number> {
-        Ok(match (self, rhs) {
-            (Big(a), Big(b)) => a.d.clone().div(&b.d).into(),
-        })
+        match (self, rhs) {
+            (Big(a), Big(b)) => {
+                let c =
+                    a.d.div_truncate(&b.d, PRECISION)
+                        .map_err(|e| anyhow!("{e}"))?;
+                Ok(Big(BigDecimal::from(c).into()))
+            }
+        }
     }
 
     pub fn modulo(self, rhs: &Self) -> Result<Number> {
-        use dashu_base::RemEuclid;
-        Ok(match (self, rhs) {
-            (Big(a), Big(b)) => a.d.clone().rem_euclid(&b.d).into(),
-        })
+        match (self, rhs) {
+            (Big(a), Big(b)) => {
+                let (_, c) = a.d.div_rem(&b.d).map_err(|e| anyhow!("{e}"))?;
+                Ok(Big(BigDecimal::from(c).into()))
+            }
+        }
     }
 
     pub fn is_integer(&self) -> bool {
@@ -299,15 +278,15 @@ impl Number {
 
     pub fn is_positive(&self) -> bool {
         match self {
-            Big(b) => b.d.sign() == dashu_base::Sign::Positive,
+            Big(b) => b.d.is_sign_positive(),
         }
     }
 
     fn ensure_integers(a: &Number, b: &Number) -> Option<(BigInt, BigInt)> {
         match (a, b) {
             (Big(a), Big(b)) if a.is_integer() && b.is_integer() => {
-                match (a.d.to_i128(), b.d.to_i128()) {
-                    (Some(a), Some(b)) => Some((a, b)),
+                match (BigInt::try_from(&a.d), BigInt::try_from(&b.d)) {
+                    (Ok(a), Ok(b)) => Some((a, b)),
                     _ => None,
                 }
             }
@@ -317,7 +296,10 @@ impl Number {
 
     fn ensure_integer(&self) -> Option<BigInt> {
         match self {
-            Big(a) if a.is_integer() => a.d.to_i128(),
+            Big(a) if a.is_integer() => match BigInt::try_from(&a.d) {
+                Ok(v) => Some(v),
+                _ => None,
+            },
             _ => None,
         }
     }
@@ -362,7 +344,6 @@ impl Number {
     }
 
     pub fn abs(&self) -> Number {
-        use dashu_base::Abs;
         match self {
             Big(b) => b.d.clone().abs().into(),
         }
@@ -370,38 +351,48 @@ impl Number {
 
     pub fn floor(&self) -> Number {
         match self {
-            Big(b) => b.d.floor().into(),
+            Big(b) => Big(BigDecimal::from(b.d.round(
+                scientific::Precision::Decimals(0),
+                scientific::Rounding::RoundDown,
+            ))
+            .into()),
         }
     }
 
     pub fn ceil(&self) -> Number {
         match self {
-            Big(b) => b.d.ceil().into(),
+            Big(b) => Big(BigDecimal::from(b.d.round(
+                scientific::Precision::Decimals(0),
+                scientific::Rounding::RoundUp,
+            ))
+            .into()),
         }
     }
 
     pub fn round(&self) -> Number {
         match self {
-            Big(b) => b.d.round().into(),
+            Big(b) => Big(BigDecimal::from(b.d.round(
+                scientific::Precision::Decimals(0),
+                scientific::Rounding::RoundHalfAwayFromZero,
+            ))
+            .into()),
         }
     }
 
-    pub fn two_pow(e: i32) -> Number {
-        use num_traits::Pow;
-        BigFloat::from(2)
-            .with_precision(80)
-            .value()
-            .pow(&BigFloat::from(e))
-            .into()
+    pub fn two_pow(e: i32) -> Result<Number> {
+        if e >= 0 {
+            Ok(BigFloat::from(2).powi(e as usize).into())
+        } else {
+            Number::from(1u64).divide(&BigFloat::from(2).powi(-e as usize).into())
+        }
     }
 
-    pub fn ten_pow(e: i32) -> Number {
-        use num_traits::Pow;
-        BigFloat::from(10)
-            .with_precision(80)
-            .value()
-            .pow(&BigFloat::from(e))
-            .into()
+    pub fn ten_pow(e: i32) -> Result<Number> {
+        if e >= 0 {
+            Ok(BigFloat::from(10).powi(e as usize).into())
+        } else {
+            Number::from(1u64).divide(&BigFloat::from(10).powi(-e as usize).into())
+        }
     }
 
     pub fn format_bin(&self) -> String {
@@ -416,10 +407,58 @@ impl Number {
             .unwrap_or("".to_string())
     }
 
+    pub fn format_scientific(&self) -> String {
+        match self {
+            Big(b) => format!("{}", b.d),
+        }
+    }
+
     pub fn format_decimal(&self) -> String {
-        self.ensure_integer()
-            .map(|a| format!("{}", a))
-            .unwrap_or("".to_string())
+        if let Some(u) = self.as_u64() {
+            u.to_string()
+        } else if let Some(i) = self.as_i64() {
+            i.to_string()
+        } else if let Some(f) = self.as_f64() {
+            f.to_string()
+        } else {
+            let s = match self {
+                Big(b) => format!("{}", b.d),
+            };
+
+            // Remove trailing e0
+            if s.ends_with("e0") {
+                return s[..s.len() - 2].to_string();
+            }
+
+            // Avoid e notation if full mantissa is written out.
+            let parts: Vec<&str> = s.split('e').collect();
+            match self {
+                Big(b) => {
+                    if b.d.is_sign_positive() {
+                        if parts[0].len() == b.d.exponent1() as usize + 2 {
+                            return parts[0].replace('.', "");
+                        }
+                    } else if parts[0].len() == b.d.exponent1() as usize + 3 {
+                        return parts[0].replace('.', "");
+                    }
+                }
+            }
+
+            s
+        }
+    }
+
+    pub fn format_decimal_with_width(&self, d: u32) -> String {
+        match self {
+            Big(b) => {
+                let n = Big(BigDecimal::from(b.d.round(
+                    scientific::Precision::Decimals(d as isize),
+                    scientific::Rounding::RoundHalfAwayFromZero,
+                ))
+                .into());
+                n.format_decimal()
+            }
+        }
     }
 
     pub fn format_hex(&self) -> String {


Test patch:
diff --git a/tests/opa.passing b/tests/opa.passing
index 03e19305..7b11c7ef 100644
--- a/tests/opa.passing
+++ b/tests/opa.passing
@@ -88,6 +88,7 @@ semvercompare
 semverisvalid
 sets
 sprintf
+strings
 subset
 toarray
 topdowndynamicdispatch
@@ -104,6 +105,7 @@ typenamebuiltin
 undos
 union
 units
+urlbuiltins
 uuid
 varreferences
 virtualdocs
\ No newline at end of file