Skip to content

Commit

Permalink
expand serde support
Browse files Browse the repository at this point in the history
  • Loading branch information
starkat99 committed Dec 14, 2022
1 parent ebc0b1f commit 4a74ba9
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 2 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Add `serialize_as_f32` and `serialize_as_string` functions when `serde` cargo feature is enabled.
They allowing customizing the serialization by using
`#[serde(serialize_with="f16::serialize_as_f32")]` attribute in serde derive macros.
- Deserialize now supports deserializing from `f32`, `f64`, and string values in addition to its
previous default deserialization.

## [2.1.0] - 2022-07-18 <a name="2.1.0"></a>
### Added
Expand Down
109 changes: 108 additions & 1 deletion src/bfloat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub(crate) mod convert;
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Default)]
#[repr(transparent)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[cfg_attr(feature = "bytemuck", derive(Zeroable, Pod))]
#[cfg_attr(feature = "zerocopy", derive(AsBytes, FromBytes))]
pub struct bf16(u16);
Expand Down Expand Up @@ -642,6 +642,61 @@ impl bf16 {
left.cmp(&right)
}

/// Alternate serialize adapter for serializing as a float.
///
/// By default, [`bf16`] serializes as a newtype of [`u16`]. This is an alternate serialize
/// implementation that serializes as an [`f32`] value. It is designed for use with
/// `serialize_with` serde attributes. Deserialization from `f32` values is already supported by
/// the default deserialize implementation.
///
/// # Examples
///
/// A demonstration on how to use this adapater:
///
/// ```
/// use serde::{Serialize, Deserialize};
/// use half::bf16;
///
/// #[derive(Serialize, Deserialize)]
/// struct MyStruct {
/// #[serde(serialize_with = "bf16::serialize_as_f32")]
/// value: bf16 // Will be serialized as f32 instead of u16
/// }
/// ```
#[cfg(feature = "serde")]
pub fn serialize_as_f32<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_f32(self.to_f32())
}

/// Alternate serialize adapter for serializing as a string.
///
/// By default, [`bf16`] serializes as a newtype of [`u16`]. This is an alternate serialize
/// implementation that serializes as a string value. It is designed for use with
/// `serialize_with` serde attributes. Deserialization from string values is already supported
/// by the default deserialize implementation.
///
/// # Examples
///
/// A demonstration on how to use this adapater:
///
/// ```
/// use serde::{Serialize, Deserialize};
/// use half::bf16;
///
/// #[derive(Serialize, Deserialize)]
/// struct MyStruct {
/// #[serde(serialize_with = "bf16::serialize_as_string")]
/// value: bf16 // Will be serialized as a string instead of u16
/// }
/// ```
#[cfg(feature = "serde")]
pub fn serialize_as_string<S: serde::Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
}

/// Approximate number of [`bf16`] significant digits in base 10
pub const DIGITS: u32 = 2;
/// [`bf16`]
Expand Down Expand Up @@ -1209,6 +1264,58 @@ impl<'a> Sum<&'a bf16> for bf16 {
}
}

#[cfg(feature = "serde")]
struct Visitor;

#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for bf16 {
fn deserialize<D>(deserializer: D) -> Result<bf16, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_newtype_struct("bf16", Visitor)
}
}

#[cfg(feature = "serde")]
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = bf16;

fn expecting(&self, formatter: &mut alloc::fmt::Formatter) -> alloc::fmt::Result {
write!(formatter, "tuple struct bf16")
}

fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(bf16(<u16 as Deserialize>::deserialize(deserializer)?))
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v.parse().map_err(|_| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &"a float string")
})?)
}

fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(bf16::from_f32(v))
}

fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(bf16::from_f64(v))
}
}

#[allow(
clippy::cognitive_complexity,
clippy::float_cmp,
Expand Down
109 changes: 108 additions & 1 deletion src/binary16.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub(crate) mod convert;
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Default)]
#[repr(transparent)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[cfg_attr(feature = "bytemuck", derive(Zeroable, Pod))]
#[cfg_attr(feature = "zerocopy", derive(AsBytes, FromBytes))]
pub struct f16(u16);
Expand Down Expand Up @@ -651,6 +651,61 @@ impl f16 {
left.cmp(&right)
}

/// Alternate serialize adapter for serializing as a float.
///
/// By default, [`f16`] serializes as a newtype of [`u16`]. This is an alternate serialize
/// implementation that serializes as an [`f32`] value. It is designed for use with
/// `serialize_with` serde attributes. Deserialization from `f32` values is already supported by
/// the default deserialize implementation.
///
/// # Examples
///
/// A demonstration on how to use this adapater:
///
/// ```
/// use serde::{Serialize, Deserialize};
/// use half::f16;
///
/// #[derive(Serialize, Deserialize)]
/// struct MyStruct {
/// #[serde(serialize_with = "f16::serialize_as_f32")]
/// value: f16 // Will be serialized as f32 instead of u16
/// }
/// ```
#[cfg(feature = "serde")]
pub fn serialize_as_f32<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_f32(self.to_f32())
}

/// Alternate serialize adapter for serializing as a string.
///
/// By default, [`f16`] serializes as a newtype of [`u16`]. This is an alternate serialize
/// implementation that serializes as a string value. It is designed for use with
/// `serialize_with` serde attributes. Deserialization from string values is already supported
/// by the default deserialize implementation.
///
/// # Examples
///
/// A demonstration on how to use this adapater:
///
/// ```
/// use serde::{Serialize, Deserialize};
/// use half::f16;
///
/// #[derive(Serialize, Deserialize)]
/// struct MyStruct {
/// #[serde(serialize_with = "f16::serialize_as_string")]
/// value: f16 // Will be serialized as a string instead of u16
/// }
/// ```
#[cfg(feature = "serde")]
pub fn serialize_as_string<S: serde::Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
}

/// Approximate number of [`f16`] significant digits in base 10
pub const DIGITS: u32 = 3;
/// [`f16`]
Expand Down Expand Up @@ -1224,6 +1279,58 @@ impl<'a> Sum<&'a f16> for f16 {
}
}

#[cfg(feature = "serde")]
struct Visitor;

#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for f16 {
fn deserialize<D>(deserializer: D) -> Result<f16, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_newtype_struct("f16", Visitor)
}
}

#[cfg(feature = "serde")]
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = f16;

fn expecting(&self, formatter: &mut alloc::fmt::Formatter) -> alloc::fmt::Result {
write!(formatter, "tuple struct f16")
}

fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(f16(<u16 as Deserialize>::deserialize(deserializer)?))
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v.parse().map_err(|_| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &"a float string")
})?)
}

fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(f16::from_f32(v))
}

fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(f16::from_f64(v))
}
}

#[allow(
clippy::cognitive_complexity,
clippy::float_cmp,
Expand Down
16 changes: 16 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@
//!
//! A [`prelude`] module is provided for easy importing of available utility traits.
//!
//! # Serialization
//!
//! When the `serde` feature is enabled, [`f16`] and [`bf16`] will be serialized as a newtype of
//! [`u16`] by default. In binary formats this is ideal, as it will generally use just two bytes for
//! storage. For string formats like JSON, however, this isn't as useful, and due to design
//! limitations of serde, it's not possible for the default `Serialize` implementation to support
//! different serialization for different formats.
//!
//! Instead, it's up to the containter type of the floats to control how it is serialized. This can
//! easily be controlled when using the derive macros using `#[serde(serialize_with="")]`
//! attributes. For both [`f16`] and [`bf16`] a `serialize_as_f32` and `serialize_as_string` are
//! provided for use with this attribute.
//!
//! Deserialization of both float types supports deserializing from the default serialization,
//! strings, and `f32`/`f64` values, so no additional work is required.
//!
//! # Cargo Features
//!
//! This crate supports a number of optional cargo features. None of these features are enabled by
Expand Down

0 comments on commit 4a74ba9

Please sign in to comment.