Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ rust-version = "1.80"

[dependencies]
byteorder = "1.5.0"
ethnum = "1.5.1"
ethnum = "1.5.2"
fast-float2 = "0.2.3"
itoa = "1.0"
jiff = "0.2.10"
nom = "8.0.0"
num-traits = "0.2.19"
ordered-float = { version = "5.0", default-features = false }
rand = { version = "0.9.0", features = ["small_rng"] }
ordered-float = { version = "5.1.0", default-features = false }
rand = { version = "0.9.2", features = ["small_rng"] }
ryu = "1.0"
serde = "1.0"
serde_json = { version = "1.0", default-features = false, features = ["std"] }
Expand Down
212 changes: 195 additions & 17 deletions src/from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,24 @@
use core::iter::FromIterator;
use std::borrow::Cow;

#[cfg(feature = "arbitrary_precision")]
use ethnum::i256;
use ordered_float::OrderedFloat;
use serde_json::Map as JsonMap;
use serde_json::Number as JsonNumber;
use serde_json::Value as JsonValue;

use super::number::Number;
use super::value::Object;
use super::value::Value;
#[cfg(feature = "arbitrary_precision")]
use crate::constants::DECIMAL128_MAX;
#[cfg(feature = "arbitrary_precision")]
use crate::constants::DECIMAL128_MIN;
use crate::value::Object;
use crate::value::Value;
#[cfg(feature = "arbitrary_precision")]
use crate::Decimal128;
#[cfg(feature = "arbitrary_precision")]
use crate::Decimal256;
use crate::Number;

macro_rules! from_signed_integer {
($($ty:ident)*) => {
Expand Down Expand Up @@ -154,12 +164,37 @@ impl From<&JsonValue> for Value<'_> {
JsonValue::Null => Value::Null,
JsonValue::Bool(v) => Value::Bool(*v),
JsonValue::Number(v) => {
if v.is_u64() {
Value::Number(Number::UInt64(v.as_u64().unwrap()))
} else if v.is_i64() {
Value::Number(Number::Int64(v.as_i64().unwrap()))
if let Some(n) = v.as_u64() {
return Value::Number(Number::UInt64(n));
} else if let Some(n) = v.as_i64() {
return Value::Number(Number::Int64(n));
}
#[cfg(feature = "arbitrary_precision")]
{
if let Some(n) = v.as_i128() {
if (DECIMAL128_MIN..=DECIMAL128_MAX).contains(&n) {
return Value::Number(Number::Decimal128(Decimal128 {
value: n,
scale: 0,
}));
} else {
return Value::Number(Number::Decimal256(Decimal256 {
value: n.into(),
scale: 0,
}));
}
} else if let Some(n) = v.as_u128() {
return Value::Number(Number::Decimal256(Decimal256 {
value: n.into(),
scale: 0,
}));
}
}
if let Some(n) = v.as_f64() {
Value::Number(Number::Float64(n))
} else {
Value::Number(Number::Float64(v.as_f64().unwrap()))
// If the value is NaN or Infinity, fallback to NULL
Value::Null
}
}
JsonValue::String(v) => Value::String(v.clone().into()),
Expand Down Expand Up @@ -194,17 +229,43 @@ impl<'a> From<Value<'a>> for JsonValue {
Value::Null => JsonValue::Null,
Value::Bool(v) => JsonValue::Bool(v),
Value::Number(v) => match v {
Number::Int64(v) => JsonValue::Number(v.into()),
Number::UInt64(v) => JsonValue::Number(v.into()),
Number::Float64(v) => JsonValue::Number(JsonNumber::from_f64(v).unwrap()),
Number::Decimal64(v) => {
JsonValue::Number(JsonNumber::from_f64(v.to_float64()).unwrap())
Number::Int64(n) => JsonValue::Number(n.into()),
Number::UInt64(n) => JsonValue::Number(n.into()),
Number::Decimal64(d) if d.scale == 0 => JsonValue::Number(d.value.into()),
#[cfg(feature = "arbitrary_precision")]
Number::Decimal128(ref d) if d.scale == 0 => {
if let Some(n) = JsonNumber::from_i128(d.value) {
JsonValue::Number(n)
} else if let Some(n) = JsonNumber::from_f64(v.as_f64()) {
JsonValue::Number(n)
} else {
JsonValue::Null
}
}
Number::Decimal128(v) => {
JsonValue::Number(JsonNumber::from_f64(v.to_float64()).unwrap())
#[cfg(feature = "arbitrary_precision")]
Number::Decimal256(ref d) if d.scale == 0 => {
if d.value >= i256::ZERO && d.value <= i256::from(u128::MAX) {
if let Some(n) = JsonNumber::from_u128(d.value.as_u128()) {
return JsonValue::Number(n);
}
} else if d.value >= i256::from(i128::MIN) && d.value < i256::ZERO {
if let Some(n) = JsonNumber::from_i128(d.value.as_i128()) {
return JsonValue::Number(n);
}
}
if let Some(n) = JsonNumber::from_f64(v.as_f64()) {
JsonValue::Number(n)
} else {
JsonValue::Null
}
}
Number::Decimal256(v) => {
JsonValue::Number(JsonNumber::from_f64(v.to_float64()).unwrap())
_ => {
if let Some(n) = JsonNumber::from_f64(v.as_f64()) {
JsonValue::Number(n)
} else {
// If the value is NaN or Infinity, fallback to NULL
JsonValue::Null
}
}
},
Value::String(v) => JsonValue::String(v.to_string()),
Expand Down Expand Up @@ -249,3 +310,120 @@ impl<'a> From<Value<'a>> for JsonValue {
}
}
}

#[cfg(test)]
mod tests {
#[cfg(feature = "arbitrary_precision")]
use super::i256;
use super::*;
use serde_json::json;
#[cfg(feature = "arbitrary_precision")]
use serde_json::Number as JsonNumber;

fn run_float_conversion_suite() {
let finite_samples = [0.0, -1.5, 42.4242, 1.0e-10, 9_007_199_254_740_992.0];

for sample in finite_samples {
let json_from_value = JsonValue::from(Value::from(sample));
match &json_from_value {
JsonValue::Number(num) => {
assert_eq!(num.as_f64(), Some(sample), "failed for {sample}");
}
other => panic!("expected number for {sample}, got {other:?}"),
}

match Value::from(&json_from_value) {
Value::Number(Number::Float64(value)) => {
assert_eq!(value, sample, "round-trip mismatch for {sample}");
}
other => panic!("expected float number for {sample}, got {other:?}"),
}

// Cover the direct JsonValue -> Value path using serde_json's json! macro.
match Value::from(&json!(sample)) {
Value::Number(Number::Float64(value)) => {
assert_eq!(value, sample, "json! conversion mismatch for {sample}");
}
other => panic!("expected float number for {sample}, got {other:?}"),
}
}

for edge in [f64::INFINITY, f64::NEG_INFINITY, f64::NAN] {
let json_value = JsonValue::from(Value::from(edge));
assert_eq!(
json_value,
JsonValue::Null,
"non-finite value should map to null"
);
}
}

#[test]
#[cfg(feature = "arbitrary_precision")]
fn float_conversions_with_arbitrary_precision() {
run_float_conversion_suite();
}

#[test]
#[cfg(not(feature = "arbitrary_precision"))]
fn float_conversions_without_arbitrary_precision() {
run_float_conversion_suite();
}

#[test]
#[cfg(feature = "arbitrary_precision")]
fn big_integer_conversion_suite() {
let i128_samples = [DECIMAL128_MIN, DECIMAL128_MAX];
for sample in i128_samples {
let json_value = JsonValue::Number(JsonNumber::from_i128(sample).unwrap());
match Value::from(&json_value) {
Value::Number(Number::Decimal128(decimal)) => {
assert_eq!(
decimal.value, sample,
"Decimal128 value mismatch for {sample}"
);
assert_eq!(decimal.scale, 0, "Decimal128 scale mismatch for {sample}");
}
other => panic!("expected Decimal128 for {sample}, got {other:?}"),
}

let json_from_value = JsonValue::from(Value::Number(Number::Decimal128(Decimal128 {
value: sample,
scale: 0,
})));

assert_eq!(
json_from_value.to_string(),
sample.to_string(),
"precise JSON mismatch for {sample}"
);
}

let u128_samples = [i128::MAX as u128, u128::MAX];
for sample in u128_samples {
let json_value = JsonValue::Number(JsonNumber::from_u128(sample).unwrap());
match Value::from(&json_value) {
Value::Number(Number::Decimal256(decimal)) => {
assert_eq!(
decimal.value,
i256::from(sample),
"Decimal256 value mismatch for {sample}"
);
assert_eq!(decimal.scale, 0, "Decimal256 scale mismatch for {sample}");
}
other => panic!("expected Decimal256 for {sample}, got {other:?}"),
}

let json_from_value = JsonValue::from(Value::Number(Number::Decimal256(Decimal256 {
value: i256::from(sample),
scale: 0,
})));

assert_eq!(
json_from_value.to_string(),
sample.to_string(),
"precise JSON mismatch for {sample}"
);
}
}
}
2 changes: 1 addition & 1 deletion src/functions/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ impl RawJsonb<'_> {
buf.push(depth);
buf.push(NUMBER_LEVEL);
let num = num_item.as_number()?;
let n = num.as_f64().unwrap();
let n = num.as_f64();
// https://github.com/rust-lang/rust/blob/9c20b2a8cc7588decb6de25ac6a7912dcef24d65/library/core/src/num/f32.rs#L1176-L1260
let s = n.to_bits() as i64;
let v = s ^ (((s >> 63) as u64) >> 1) as i64;
Expand Down
7 changes: 3 additions & 4 deletions src/functions/scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1023,7 +1023,7 @@ impl RawJsonb<'_> {
match jsonb_item {
JsonbItem::Number(num) => {
let value = num.as_number()?;
Ok(value.as_f64())
Ok(Some(value.as_f64()))
}
_ => Ok(None),
}
Expand Down Expand Up @@ -1106,9 +1106,8 @@ impl RawJsonb<'_> {
}
JsonbItem::Number(num) => {
let value = num.as_number()?;
if let Some(v) = value.as_f64() {
return Ok(v);
}
let v = value.as_f64();
return Ok(v);
}
JsonbItem::String(s) => {
if let Ok(v) = s.parse::<f64>() {
Expand Down
Loading