use super::dta::release::Release;
use super::missing_value::MissingValue;
use super::stata_byte::StataByte;
use super::stata_error::{Result, StataError};
use super::stata_float::StataFloat;
use super::stata_int::StataInt;
use super::stata_long::StataLong;
const MISSING_DOUBLE_SYSTEM_113: u64 = 0x7FE0_0000_0000_0000;
const MISSING_DOUBLE_A_113: u64 = 0x7FE0_0100_0000_0000;
const MISSING_DOUBLE_STRIDE: u64 = 0x0100_0000_0000;
const MISSING_DOUBLE_SYSTEM_V104: u64 = 0x54C0_0000_0000_0000;
const PRE_113_DOUBLE_MAX_VALID_BITS: u64 = 0x7FDF_FFFF_FFFF_FFFF;
const MISSING_DOUBLE_SYSTEM_V106_V112: u64 = 0x7FEF_FFFF_FFFF_FFFF;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum StataDouble {
Present(f64),
Missing(MissingValue),
}
impl StataDouble {
pub(crate) fn from_raw(raw: f64, release: Release) -> Result<Self> {
let bits = raw.to_bits();
let is_positive = bits & 0x8000_0000_0000_0000 == 0;
if release.supports_tagged_missing() {
if is_positive && bits >= MISSING_DOUBLE_SYSTEM_113 {
return Ok(Self::Missing(MissingValue::try_from(raw)?));
}
return Ok(Self::Present(raw));
}
if release.uses_magic_double_missing() && bits == MISSING_DOUBLE_SYSTEM_V104 {
return Ok(Self::Missing(MissingValue::System));
}
if is_positive && bits > PRE_113_DOUBLE_MAX_VALID_BITS {
Ok(Self::Missing(MissingValue::System))
} else {
Ok(Self::Present(raw))
}
}
pub(crate) fn to_raw(self, release: Release) -> Result<f64> {
match self {
Self::Present(v) => Ok(v),
Self::Missing(mv) => {
if release.supports_tagged_missing() {
let offset = u64::from(mv.code());
let bits = if offset == 0 {
MISSING_DOUBLE_SYSTEM_113
} else {
MISSING_DOUBLE_A_113 + (offset - 1) * MISSING_DOUBLE_STRIDE
};
Ok(f64::from_bits(bits))
} else if mv == MissingValue::System {
let bits = if release.uses_magic_double_missing() {
MISSING_DOUBLE_SYSTEM_V104
} else {
MISSING_DOUBLE_SYSTEM_V106_V112
};
Ok(f64::from_bits(bits))
} else {
Err(StataError::TaggedMissingUnsupported)
}
}
}
}
#[must_use]
#[inline]
pub fn present(self) -> Option<f64> {
match self {
Self::Present(v) => Some(v),
Self::Missing(_) => None,
}
}
}
impl From<StataByte> for StataDouble {
fn from(value: StataByte) -> Self {
match value {
StataByte::Present(v) => Self::Present(f64::from(v)),
StataByte::Missing(mv) => Self::Missing(mv),
}
}
}
impl From<StataInt> for StataDouble {
fn from(value: StataInt) -> Self {
match value {
StataInt::Present(v) => Self::Present(f64::from(v)),
StataInt::Missing(mv) => Self::Missing(mv),
}
}
}
impl From<StataLong> for StataDouble {
fn from(value: StataLong) -> Self {
match value {
StataLong::Present(v) => Self::Present(f64::from(v)),
StataLong::Missing(mv) => Self::Missing(mv),
}
}
}
impl From<StataFloat> for StataDouble {
fn from(value: StataFloat) -> Self {
match value {
StataFloat::Present(v) => Self::Present(f64::from(v)),
StataFloat::Missing(mv) => Self::Missing(mv),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use float_cmp::assert_approx_eq;
#[test]
fn v113_present_zero() {
assert_eq!(
StataDouble::from_raw(0.0_f64, Release::V113).unwrap(),
StataDouble::Present(0.0),
);
}
#[test]
fn v113_present_negative_zero() {
assert_eq!(
StataDouble::from_raw(-0.0_f64, Release::V113).unwrap(),
StataDouble::Present(-0.0),
);
}
#[test]
fn v113_present_one() {
assert_eq!(
StataDouble::from_raw(1.0_f64, Release::V113).unwrap(),
StataDouble::Present(1.0),
);
}
#[test]
fn v113_present_negative() {
assert_eq!(
StataDouble::from_raw(-1.5_f64, Release::V113).unwrap(),
StataDouble::Present(-1.5),
);
}
#[test]
fn v113_present_large_just_below_missing_range() {
let val = f64::from_bits(MISSING_DOUBLE_SYSTEM_113 - 1);
assert_eq!(
StataDouble::from_raw(val, Release::V113).unwrap(),
StataDouble::Present(val),
);
}
#[test]
fn v113_present_negative_infinity() {
assert_eq!(
StataDouble::from_raw(f64::NEG_INFINITY, Release::V113).unwrap(),
StataDouble::Present(f64::NEG_INFINITY),
);
}
#[test]
fn v113_error_non_stata_nan() {
let val = f64::from_bits(0x7FE0_0000_0000_0001);
assert_eq!(
StataDouble::from_raw(val, Release::V113),
Err(StataError::NotMissingValue),
);
}
#[test]
fn v113_error_positive_infinity() {
assert_eq!(
StataDouble::from_raw(f64::INFINITY, Release::V113),
Err(StataError::NotMissingValue),
);
}
#[test]
fn v113_missing_system() {
assert_eq!(
StataDouble::from_raw(f64::from_bits(0x7FE0_0000_0000_0000), Release::V113).unwrap(),
StataDouble::Missing(MissingValue::System),
);
}
#[test]
fn v113_missing_a() {
assert_eq!(
StataDouble::from_raw(f64::from_bits(0x7FE0_0100_0000_0000), Release::V113).unwrap(),
StataDouble::Missing(MissingValue::A),
);
}
#[test]
fn v113_missing_z() {
assert_eq!(
StataDouble::from_raw(f64::from_bits(0x7FE0_1A00_0000_0000), Release::V113).unwrap(),
StataDouble::Missing(MissingValue::Z),
);
}
#[test]
fn v104_missing_system_magic() {
assert_eq!(
StataDouble::from_raw(f64::from_bits(0x54C0_0000_0000_0000), Release::V104).unwrap(),
StataDouble::Missing(MissingValue::System),
);
}
#[test]
fn v105_missing_system_magic() {
assert_eq!(
StataDouble::from_raw(f64::from_bits(0x54C0_0000_0000_0000), Release::V105).unwrap(),
StataDouble::Missing(MissingValue::System),
);
}
#[test]
fn v104_present_value_just_below_magic() {
let val = f64::from_bits(0x54C0_0000_0000_0001);
assert_eq!(
StataDouble::from_raw(val, Release::V104).unwrap(),
StataDouble::Present(val),
);
}
#[test]
fn v104_present_normal() {
assert_eq!(
StataDouble::from_raw(2.5_f64, Release::V104).unwrap(),
StataDouble::Present(2.5),
);
}
#[test]
fn v106_present_normal() {
assert_eq!(
StataDouble::from_raw(42.0_f64, Release::V106).unwrap(),
StataDouble::Present(42.0),
);
}
#[test]
fn v106_present_max_valid() {
let val = f64::from_bits(PRE_113_DOUBLE_MAX_VALID_BITS);
assert_eq!(
StataDouble::from_raw(val, Release::V106).unwrap(),
StataDouble::Present(val),
);
}
#[test]
fn v106_missing_system_above_max() {
let val = f64::from_bits(PRE_113_DOUBLE_MAX_VALID_BITS + 1);
assert_eq!(
StataDouble::from_raw(val, Release::V106).unwrap(),
StataDouble::Missing(MissingValue::System),
);
}
#[test]
fn v106_missing_system_at_max_double() {
assert_eq!(
StataDouble::from_raw(
f64::from_bits(MISSING_DOUBLE_SYSTEM_V106_V112),
Release::V106
)
.unwrap(),
StataDouble::Missing(MissingValue::System),
);
}
#[test]
fn v106_present_magic_v104_value_is_data() {
let val = f64::from_bits(MISSING_DOUBLE_SYSTEM_V104);
assert_eq!(
StataDouble::from_raw(val, Release::V106).unwrap(),
StataDouble::Present(val),
);
}
#[test]
fn v112_missing_system_positive_infinity() {
assert_eq!(
StataDouble::from_raw(f64::INFINITY, Release::V112).unwrap(),
StataDouble::Missing(MissingValue::System),
);
}
#[test]
fn v112_present_negative_infinity() {
assert_eq!(
StataDouble::from_raw(f64::NEG_INFINITY, Release::V112).unwrap(),
StataDouble::Present(f64::NEG_INFINITY),
);
}
#[test]
fn v113_to_raw_present_zero() {
assert_approx_eq!(
f64,
StataDouble::Present(0.0).to_raw(Release::V113).unwrap(),
0.0
);
}
#[test]
fn v113_to_raw_present_positive() {
assert_approx_eq!(
f64,
StataDouble::Present(1.5).to_raw(Release::V113).unwrap(),
1.5
);
}
#[test]
fn v113_to_raw_present_negative() {
assert_approx_eq!(
f64,
StataDouble::Present(-1.5).to_raw(Release::V113).unwrap(),
-1.5
);
}
#[test]
fn v113_to_raw_missing_system() {
let got = StataDouble::Missing(MissingValue::System)
.to_raw(Release::V113)
.unwrap();
assert_eq!(got.to_bits(), 0x7FE0_0000_0000_0000);
}
#[test]
fn v113_to_raw_missing_a() {
let got = StataDouble::Missing(MissingValue::A)
.to_raw(Release::V113)
.unwrap();
assert_eq!(got.to_bits(), 0x7FE0_0100_0000_0000);
}
#[test]
fn v113_to_raw_missing_z() {
let got = StataDouble::Missing(MissingValue::Z)
.to_raw(Release::V113)
.unwrap();
assert_eq!(got.to_bits(), 0x7FE0_1A00_0000_0000);
}
#[test]
fn v104_to_raw_missing_system_emits_magic() {
let got = StataDouble::Missing(MissingValue::System)
.to_raw(Release::V104)
.unwrap();
assert_eq!(got.to_bits(), MISSING_DOUBLE_SYSTEM_V104);
}
#[test]
fn v105_to_raw_missing_system_emits_magic() {
let got = StataDouble::Missing(MissingValue::System)
.to_raw(Release::V105)
.unwrap();
assert_eq!(got.to_bits(), MISSING_DOUBLE_SYSTEM_V104);
}
#[test]
fn v106_to_raw_missing_system_emits_max_double() {
let got = StataDouble::Missing(MissingValue::System)
.to_raw(Release::V106)
.unwrap();
assert_eq!(got.to_bits(), MISSING_DOUBLE_SYSTEM_V106_V112);
}
#[test]
fn v112_to_raw_missing_system_emits_max_double() {
let got = StataDouble::Missing(MissingValue::System)
.to_raw(Release::V112)
.unwrap();
assert_eq!(got.to_bits(), MISSING_DOUBLE_SYSTEM_V106_V112);
}
#[test]
fn v104_to_raw_missing_tagged_errors() {
assert_eq!(
StataDouble::Missing(MissingValue::A).to_raw(Release::V104),
Err(StataError::TaggedMissingUnsupported),
);
}
#[test]
fn v112_to_raw_missing_tagged_errors() {
assert_eq!(
StataDouble::Missing(MissingValue::Z).to_raw(Release::V112),
Err(StataError::TaggedMissingUnsupported),
);
}
#[test]
fn present_returns_inner_for_present() {
assert_eq!(StataDouble::Present(2.5).present(), Some(2.5));
}
#[test]
fn present_returns_none_for_missing() {
assert_eq!(StataDouble::Missing(MissingValue::System).present(), None);
assert_eq!(StataDouble::Missing(MissingValue::A).present(), None);
}
#[test]
fn from_byte_present_widens() {
assert_eq!(
StataDouble::from(StataByte::Present(42)),
StataDouble::Present(42.0),
);
}
#[test]
fn from_byte_missing_translates_directly() {
assert_eq!(
StataDouble::from(StataByte::Missing(MissingValue::System)),
StataDouble::Missing(MissingValue::System),
);
}
#[test]
fn from_int_present_widens() {
assert_eq!(
StataDouble::from(StataInt::Present(-32_768)),
StataDouble::Present(-32_768.0),
);
}
#[test]
fn from_int_missing_translates_directly() {
assert_eq!(
StataDouble::from(StataInt::Missing(MissingValue::Z)),
StataDouble::Missing(MissingValue::Z),
);
}
#[test]
fn from_long_present_widens() {
assert_eq!(
StataDouble::from(StataLong::Present(2_147_483_647)),
StataDouble::Present(2_147_483_647.0),
);
}
#[test]
fn from_long_missing_translates_directly() {
assert_eq!(
StataDouble::from(StataLong::Missing(MissingValue::A)),
StataDouble::Missing(MissingValue::A),
);
}
#[test]
fn from_float_present_widens_losslessly() {
assert_eq!(
StataDouble::from(StataFloat::Present(1000.0)),
StataDouble::Present(1000.0),
);
}
#[test]
fn from_float_missing_translates_directly() {
assert_eq!(
StataDouble::from(StataFloat::Missing(MissingValue::System)),
StataDouble::Missing(MissingValue::System),
);
}
}