mirror of
https://github.com/logos-co/nomos-node.git
synced 2026-08-31 11:31:09 +00:00
424 lines
11 KiB
Rust
424 lines
11 KiB
Rust
use core::ops::{Deref, DerefMut};
|
|
use std::num::NonZeroU32;
|
|
|
|
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, ::serde::Serialize, ::serde::Deserialize)]
|
|
pub struct FiniteF64(#[serde(deserialize_with = "serde::deserialize")] f64);
|
|
|
|
/// A wrapper around [`f64`] that guarantees the value is neither infinite nor
|
|
/// NaN.
|
|
impl FiniteF64 {
|
|
/// The maximum [`u64`] value that can be represented exactly as an [`f64`]
|
|
/// without losing precision.
|
|
const MAX_REPRESENTABLE_U64: u64 = (1u64 << f64::MANTISSA_DIGITS) - 1;
|
|
|
|
#[must_use]
|
|
pub const fn get(self) -> f64 {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
impl Deref for FiniteF64 {
|
|
type Target = f64;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl DerefMut for FiniteF64 {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.0
|
|
}
|
|
}
|
|
|
|
impl TryFrom<f64> for FiniteF64 {
|
|
type Error = ();
|
|
|
|
fn try_from(value: f64) -> Result<Self, Self::Error> {
|
|
if value.is_finite() {
|
|
Ok(Self(value))
|
|
} else {
|
|
Err(())
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<u64> for FiniteF64 {
|
|
type Error = ();
|
|
|
|
/// Converts [`u64`] to [`FiniteF64`] if it can be represented exactly.
|
|
/// Returns an error if precision would be lost.
|
|
fn try_from(value: u64) -> Result<Self, Self::Error> {
|
|
if value > Self::MAX_REPRESENTABLE_U64 {
|
|
return Err(());
|
|
}
|
|
Self::try_from(value as f64)
|
|
}
|
|
}
|
|
|
|
/// A wrapper around [`FiniteF64`] that guarantees the value is >= 0.0.
|
|
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, ::serde::Serialize, ::serde::Deserialize)]
|
|
pub struct NonNegativeF64(#[serde(deserialize_with = "serde::deserialize_non_negative")] FiniteF64);
|
|
|
|
impl NonNegativeF64 {
|
|
#[must_use]
|
|
pub const fn get(self) -> f64 {
|
|
self.0.get()
|
|
}
|
|
}
|
|
|
|
impl Deref for NonNegativeF64 {
|
|
type Target = f64;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl DerefMut for NonNegativeF64 {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.0
|
|
}
|
|
}
|
|
|
|
impl TryFrom<f64> for NonNegativeF64 {
|
|
type Error = ();
|
|
|
|
fn try_from(value: f64) -> Result<Self, Self::Error> {
|
|
FiniteF64::try_from(value)?.try_into()
|
|
}
|
|
}
|
|
|
|
impl TryFrom<FiniteF64> for NonNegativeF64 {
|
|
type Error = ();
|
|
|
|
fn try_from(value: FiniteF64) -> Result<Self, Self::Error> {
|
|
if value.0 >= 0.0 {
|
|
Ok(Self(value))
|
|
} else {
|
|
Err(())
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<u64> for NonNegativeF64 {
|
|
type Error = ();
|
|
|
|
/// Converts [`u64`] to [`NonNegativeF64`] if it can be represented exactly.
|
|
/// Returns an error if precision would be lost or the value is negative.
|
|
fn try_from(value: u64) -> Result<Self, Self::Error> {
|
|
FiniteF64::try_from(value)?.try_into()
|
|
}
|
|
}
|
|
|
|
/// A wrapper around [`NonNegativeF64`] that guarantees the value is > 0.0
|
|
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, ::serde::Serialize, ::serde::Deserialize)]
|
|
pub struct PositiveF64(#[serde(deserialize_with = "serde::deserialize_positive")] NonNegativeF64);
|
|
|
|
impl PositiveF64 {
|
|
#[must_use]
|
|
pub const fn get(self) -> f64 {
|
|
self.0.get()
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn log2(&self) -> FiniteF64 {
|
|
self.0
|
|
.log2()
|
|
.try_into()
|
|
.expect("log2 of positive number must be neither infinite nor NaN")
|
|
}
|
|
}
|
|
|
|
impl Deref for PositiveF64 {
|
|
type Target = f64;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl DerefMut for PositiveF64 {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.0
|
|
}
|
|
}
|
|
|
|
impl TryFrom<f64> for PositiveF64 {
|
|
type Error = ();
|
|
|
|
fn try_from(value: f64) -> Result<Self, Self::Error> {
|
|
let non_negative = NonNegativeF64::try_from(value)?;
|
|
Self::try_from(non_negative)
|
|
}
|
|
}
|
|
|
|
impl TryFrom<NonNegativeF64> for PositiveF64 {
|
|
type Error = ();
|
|
|
|
fn try_from(value: NonNegativeF64) -> Result<Self, Self::Error> {
|
|
if value.get() == 0.0 {
|
|
Err(())
|
|
} else {
|
|
Ok(Self(value))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<u64> for PositiveF64 {
|
|
type Error = ();
|
|
|
|
/// Converts [`u64`] to [`PositiveF64`] if it can be represented exactly.
|
|
/// Returns an error if precision would be lost or the value is < 0.0.
|
|
fn try_from(value: u64) -> Result<Self, Self::Error> {
|
|
NonNegativeF64::try_from(value)?.try_into()
|
|
}
|
|
}
|
|
|
|
/// A wrapper around [`PositiveF64`] that guarantees the value is >= 1.0
|
|
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, ::serde::Serialize, ::serde::Deserialize)]
|
|
pub struct F64Ge1(#[serde(deserialize_with = "serde::deserialize_ge1")] PositiveF64);
|
|
|
|
impl F64Ge1 {
|
|
#[must_use]
|
|
pub const fn get(self) -> f64 {
|
|
self.0.get()
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn log2(&self) -> NonNegativeF64 {
|
|
self.0
|
|
.log2()
|
|
.try_into()
|
|
.expect("log2 of value >= 1.0 must be non-negative")
|
|
}
|
|
}
|
|
|
|
impl Deref for F64Ge1 {
|
|
type Target = f64;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl TryFrom<f64> for F64Ge1 {
|
|
type Error = ();
|
|
|
|
fn try_from(value: f64) -> Result<Self, Self::Error> {
|
|
let positive = PositiveF64::try_from(value)?;
|
|
Self::try_from(positive)
|
|
}
|
|
}
|
|
|
|
impl TryFrom<PositiveF64> for F64Ge1 {
|
|
type Error = ();
|
|
|
|
fn try_from(value: PositiveF64) -> Result<Self, Self::Error> {
|
|
if value.get() >= 1.0 {
|
|
Ok(Self(value))
|
|
} else {
|
|
Err(())
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<u64> for F64Ge1 {
|
|
type Error = ();
|
|
|
|
/// Converts [`u64`] to [`F64Ge1`] if it can be represented exactly.
|
|
/// Returns an error if precision would be lost or the value is < 1.0.
|
|
fn try_from(value: u64) -> Result<Self, Self::Error> {
|
|
PositiveF64::try_from(value)?.try_into()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, ::serde::Serialize, ::serde::Deserialize)]
|
|
pub struct NonNegativeRatio {
|
|
pub numerator: u32,
|
|
pub denominator: NonZeroU32,
|
|
}
|
|
|
|
impl NonNegativeRatio {
|
|
#[must_use]
|
|
pub const fn new(numerator: u32, denominator: NonZeroU32) -> Self {
|
|
Self {
|
|
numerator,
|
|
denominator,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn as_f64(&self) -> f64 {
|
|
self.numerator as f64 / self.denominator.get() as f64
|
|
}
|
|
}
|
|
|
|
mod serde {
|
|
use serde::Deserialize as _;
|
|
|
|
use crate::math::{F64Ge1, FiniteF64, NonNegativeF64, PositiveF64};
|
|
|
|
pub(super) fn deserialize<'de, Deserializer>(
|
|
deserializer: Deserializer,
|
|
) -> Result<f64, Deserializer::Error>
|
|
where
|
|
Deserializer: serde::Deserializer<'de>,
|
|
{
|
|
let inner = f64::deserialize(deserializer)?;
|
|
NonNegativeF64::try_from(inner).map_err(|()| {
|
|
serde::de::Error::custom("Deserialized f64 does not contain a valid value.")
|
|
})?;
|
|
Ok(inner)
|
|
}
|
|
|
|
pub(super) fn deserialize_non_negative<'de, Deserializer>(
|
|
deserializer: Deserializer,
|
|
) -> Result<FiniteF64, Deserializer::Error>
|
|
where
|
|
Deserializer: serde::Deserializer<'de>,
|
|
{
|
|
let inner = FiniteF64::deserialize(deserializer)?;
|
|
NonNegativeF64::try_from(inner)
|
|
.map_err(|()| serde::de::Error::custom("Deserialized f64 must be non-negative."))?;
|
|
Ok(inner)
|
|
}
|
|
|
|
pub(super) fn deserialize_positive<'de, Deserializer>(
|
|
deserializer: Deserializer,
|
|
) -> Result<NonNegativeF64, Deserializer::Error>
|
|
where
|
|
Deserializer: serde::Deserializer<'de>,
|
|
{
|
|
let inner = NonNegativeF64::deserialize(deserializer)?;
|
|
PositiveF64::try_from(inner)
|
|
.map_err(|()| serde::de::Error::custom("Deserialized f64 must be positive."))?;
|
|
Ok(inner)
|
|
}
|
|
|
|
pub(super) fn deserialize_ge1<'de, Deserializer>(
|
|
deserializer: Deserializer,
|
|
) -> Result<PositiveF64, Deserializer::Error>
|
|
where
|
|
Deserializer: serde::Deserializer<'de>,
|
|
{
|
|
let inner = PositiveF64::deserialize(deserializer)?;
|
|
F64Ge1::try_from(inner)
|
|
.map_err(|()| serde::de::Error::custom("Deserialized f64 must be >= 1.0"))?;
|
|
Ok(inner)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::f64;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn finite_basic() {
|
|
assert!(FiniteF64::try_from(0.0).is_ok());
|
|
assert!(FiniteF64::try_from(1.0).is_ok());
|
|
assert!(FiniteF64::try_from(-1.0).is_ok());
|
|
assert!(FiniteF64::try_from(f64::INFINITY).is_err());
|
|
assert!(FiniteF64::try_from(f64::NAN).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn finite_from_u64() {
|
|
assert!(FiniteF64::try_from(0u64).is_ok());
|
|
assert!(FiniteF64::try_from(1u64).is_ok());
|
|
|
|
assert!(FiniteF64::try_from(FiniteF64::MAX_REPRESENTABLE_U64).is_ok());
|
|
assert!(FiniteF64::try_from(FiniteF64::MAX_REPRESENTABLE_U64 + 1).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn non_negative_basic() {
|
|
assert!(NonNegativeF64::try_from(0.0).is_ok());
|
|
assert!(NonNegativeF64::try_from(1.0).is_ok());
|
|
assert!(NonNegativeF64::try_from(-0.1).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn positive_basic() {
|
|
assert!(PositiveF64::try_from(0.1).is_ok());
|
|
assert!(PositiveF64::try_from(0.0).is_err());
|
|
assert!(PositiveF64::try_from(-1.0).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn f64ge1_basic() {
|
|
assert!(F64Ge1::try_from(1.0).is_ok());
|
|
assert!(F64Ge1::try_from(2.0).is_ok());
|
|
assert!(F64Ge1::try_from(0.9).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn f64ge1_from_u64() {
|
|
assert!(F64Ge1::try_from(0u64).is_err());
|
|
assert!(F64Ge1::try_from(1u64).is_ok());
|
|
assert!(F64Ge1::try_from(2u64).is_ok());
|
|
assert!(F64Ge1::try_from(FiniteF64::MAX_REPRESENTABLE_U64).is_ok());
|
|
assert!(F64Ge1::try_from(FiniteF64::MAX_REPRESENTABLE_U64 + 1).is_err());
|
|
}
|
|
|
|
mod serde_tests {
|
|
use ::serde::Deserialize;
|
|
|
|
use super::*;
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct Finite {
|
|
value: FiniteF64,
|
|
}
|
|
#[derive(Debug, Deserialize)]
|
|
struct NonNegative {
|
|
value: NonNegativeF64,
|
|
}
|
|
#[derive(Debug, Deserialize)]
|
|
struct Positive {
|
|
value: PositiveF64,
|
|
}
|
|
#[derive(Debug, Deserialize)]
|
|
struct Ge1 {
|
|
value: F64Ge1,
|
|
}
|
|
|
|
#[test]
|
|
fn deser_finite() {
|
|
let ok: Finite = serde_json::from_str(r#"{"value": 0.1}"#).unwrap();
|
|
assert!((*ok.value - 0.1).abs() < f64::EPSILON);
|
|
|
|
assert!(serde_json::from_str::<Finite>(r#"{"value": 0.0}"#).is_ok());
|
|
assert!(serde_json::from_str::<Finite>(r#"{"value": -1.0}"#).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn deser_non_negativef64() {
|
|
let ok: NonNegative = serde_json::from_str(r#"{"value": 0.1}"#).unwrap();
|
|
assert!((*ok.value - 0.1).abs() < f64::EPSILON);
|
|
|
|
assert!(serde_json::from_str::<NonNegative>(r#"{"value": 0.0}"#).is_ok());
|
|
assert!(serde_json::from_str::<NonNegative>(r#"{"value": -1.0}"#).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn deser_positivef64() {
|
|
let ok: Positive = serde_json::from_str(r#"{"value": 0.1}"#).unwrap();
|
|
assert!((*ok.value - 0.1).abs() < f64::EPSILON);
|
|
|
|
assert!(serde_json::from_str::<Positive>(r#"{"value": 0.0}"#).is_err());
|
|
assert!(serde_json::from_str::<Positive>(r#"{"value": -1.0}"#).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn deser_f64ge1() {
|
|
let ok: Ge1 = serde_json::from_str(r#"{"value": 1.0}"#).unwrap();
|
|
assert!((*ok.value - 1.0).abs() < f64::EPSILON);
|
|
|
|
assert!(serde_json::from_str::<F64Ge1>(r#"{"value": 1.1}"#).is_err());
|
|
}
|
|
}
|
|
}
|