diff --git a/Cargo.toml b/Cargo.toml index c63e790..d6a6814 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rawsample" -version = "0.2.0" +version = "0.3.0" edition = "2018" authors = ["HEnquist "] description = "A library for working with raw audio samples." diff --git a/README.md b/README.md index 82970a8..e56aac0 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,30 @@ Most audio APIs work with buffers of bytes. To do anything with the sample values, these raw bytes must be converted to and from numeric types. This library aims to provide the low level tools for converting most common sample formats from raw bytes to float values. -Both f32 and f64 are supported, as well as both big-endian and little-endian byte order. +Both `f32` and `f64` are supported, as well as both big-endian and little-endian byte order. + +Methods are also provided for converting samples between floats (`f32` and `f64`) and integers (`i8`, `u8`, `i16` and `i32`). + +When samples are converted, the amplitude is scaled to fit the new type. +For floats, the values +/- 1.0 are considered full amplitude. +Higher and lower values are possible, but they will be clipped if the sample is converted to an integer type. +For integer types, full amplitude is simply the minimum and maximum possible values of the type. + + +## Example: ```rust use rawsample::{SampleWriter, SampleReader, SampleFormat}; + // create a vec of samples let values = vec![-0.5, -0.25, -0.125, 0.0, 0.125, 0.25, 0.5]; + // create a vec to store raw bytes let mut rawbytes: Vec = Vec::new(); + // write the samples as raw bytes f64::write_samples(&values, &mut rawbytes, &SampleFormat::S32LE).unwrap(); + // create another vec to store the samples after reading back let mut values2 = Vec::new(); let mut slice: &[u8] = &rawbytes; diff --git a/src/lib.rs b/src/lib.rs index 478c71f..1b2c297 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,28 +1,4 @@ -//! # RawSample -//! -//! A library for working with raw audio samples. -//! -//! Most audio APIs work with buffers of bytes. -//! To do anything with the sample values, these raw bytes must be converted to and from numeric types. -//! -//! This library aims to provide the low level tools for converting most common sample formats from raw bytes to float values. -//! Both f32 and f64 are supported, as well as both big-endian and little-endian byte order. -//! -//! ```rust -//! use rawsample::{SampleWriter, SampleReader, SampleFormat}; -//! // create a vec of samples -//! let values = vec![-0.5, -0.25, -0.125, 0.0, 0.125, 0.25, 0.5]; -//! // create a vec to store raw bytes -//! let mut rawbytes: Vec = Vec::new(); -//! // write the samples as raw bytes -//! f64::write_samples(&values, &mut rawbytes, &SampleFormat::S32LE).unwrap(); -//! // create another vec to store the samples after reading back -//! let mut values2 = Vec::new(); -//! let mut slice: &[u8] = &rawbytes; -//! -//! // read the raw bytes back as samples into the new vec -//! f64::read_all_samples(&mut slice, &mut values2, &SampleFormat::S32LE).unwrap(); -//! ``` +#![doc = include_str!("../README.md")] extern crate num_traits; use num_traits::{Bounded, Float, ToPrimitive}; @@ -30,12 +6,12 @@ use std::error::Error; use std::io::ErrorKind; use std::io::{Read, Write}; -/// The Sample trait is used for low-level conversions of samples stored as raw bytes, to f32 or f64 sample values. +/// The `BytesSample` trait is used for low-level conversions of samples stored as raw bytes, to `f32` or `f64` sample values. /// /// The float values are expected to use the range -1.0 <= value < +1.0. /// The integer types are mapped to this range. -/// Using f32, up to 24 byte integers can be converted without loss to and from float. -/// 32-bit integers required the use of f64 for lossless conversion. +/// Using `f32`, up to 24 byte integers can be converted without loss to and from float. +/// 32-bit integers required the use of `f64` for lossless conversion. /// /// The exact range depends on the format. The lower limit is always -1.0. But the upper limit is (2^(n-1)-1)/2^(n-1). /// For example for 16-bit integer, the maximum value is (2^15-1)/2^15, approximately +0.99997. @@ -46,10 +22,11 @@ use std::io::{Read, Write}; /// When writing samples, the float sample values are clamped to the range supported by the chosen format. /// Float output values are also clamped to the -1.0 to +1.0 range, since this is what most audio APIs expect. -pub trait Sample { +pub trait BytesSample { const MAX_I32: T; const MAX_I24: T; const MAX_I16: T; + const MAX_I8: T; /// Convert a sample value to S32LE (4 bytes) fn to_s32_le(&self) -> ([u8; 4], bool); @@ -75,6 +52,10 @@ pub trait Sample { fn to_f32_le(&self) -> ([u8; 4], bool); /// Convert a sample value to F32BE (4 bytes) fn to_f32_be(&self) -> ([u8; 4], bool); + /// Convert a sample value to S8 (1 byte) + fn to_s8_ne(&self) -> ([u8; 1], bool); + /// Convert a sample value to U8 (1 byte) + fn to_u8_ne(&self) -> ([u8; 1], bool); /// Convert S32LE (4 bytes) to a sample value fn from_s32_le(bytes: [u8; 4]) -> Self; @@ -100,10 +81,18 @@ pub trait Sample { fn from_f64_le(bytes: [u8; 8]) -> Self; /// Convert F64BE (8 bytes) to a sample value fn from_f64_be(bytes: [u8; 8]) -> Self; + /// Convert S8 (1 byte) to a sample value + fn from_s8_ne(bytes: [u8; 1]) -> Self; + /// Convert U8 (1 byte) to a sample value + fn from_u8_ne(bytes: [u8; 1]) -> Self; } /// The supported sample formats. pub enum SampleFormat { + /// 8 bit signed integer (single byte without endianness). + S8, + /// 8 bit unsigned integer (single byte without endianness). + U8, /// 16 bit signed integer, little endian. S16LE, /// 16 bit signed integer, big endian. @@ -130,6 +119,28 @@ pub enum SampleFormat { F64BE, } +impl SampleFormat { + /// Get the number of bytes that the format uses to store each sample. + pub fn bytes_per_sample(&self) -> usize { + match self { + SampleFormat::S8 => 1, + SampleFormat::U8 => 1, + SampleFormat::S16LE => 2, + SampleFormat::S16BE => 2, + SampleFormat::S24LE3 => 3, + SampleFormat::S24BE3 => 3, + SampleFormat::S24LE4 => 4, + SampleFormat::S24BE4 => 4, + SampleFormat::S32LE => 4, + SampleFormat::S32BE => 4, + SampleFormat::F32LE => 4, + SampleFormat::F32BE => 4, + SampleFormat::F64LE => 8, + SampleFormat::F64BE => 8, + } + } +} + macro_rules! write_samples { ($values:expr, $target:expr, $conv:ident) => {{ let mut nbr_clipped = 0; @@ -145,59 +156,64 @@ macro_rules! write_samples { } /// The SampleWriter trait enables converting and writing many sample values from a slice. -pub trait SampleWriter> { +pub trait SampleWriter> { /// Write sample values from a slice to anything that implements the "Write" trait. - /// This can be for example a file, or a Vec of u8. - /// Input samples are f32 or f64, and are converted to the given sample format. + /// This can be for example a file, or a `Vec` of `u8`. + /// Input samples are `f32` or `f64`, and are converted to the given sample format. /// The sample values are clamped to the range supported by the output format. /// For the float types, the input range is -1.0 to +1.0. /// For the integer types, the input range doesn't include 1.0. - /// For example for I16 the maximum value is (2^15-1)/2^15, approximately +0.99997. + /// For example for `i16` the maximum value is (2^15-1)/2^15, approximately +0.99997. /// The number of clipped samples is returned. fn write_samples( values: &[T], target: &mut dyn Write, sformat: &SampleFormat, ) -> Result> { - let nbr_clipped; - match sformat { + let nbr_clipped = match sformat { + SampleFormat::S8 => { + write_samples!(values, target, to_s8_ne) + } + SampleFormat::U8 => { + write_samples!(values, target, to_u8_ne) + } SampleFormat::S16LE => { - nbr_clipped = write_samples!(values, target, to_s16_le); + write_samples!(values, target, to_s16_le) } SampleFormat::S16BE => { - nbr_clipped = write_samples!(values, target, to_s16_be); + write_samples!(values, target, to_s16_be) } SampleFormat::S24LE3 => { - nbr_clipped = write_samples!(values, target, to_s24_3_le); + write_samples!(values, target, to_s24_3_le) } SampleFormat::S24BE3 => { - nbr_clipped = write_samples!(values, target, to_s24_3_be); + write_samples!(values, target, to_s24_3_be) } SampleFormat::S24LE4 => { - nbr_clipped = write_samples!(values, target, to_s24_4_le); + write_samples!(values, target, to_s24_4_le) } SampleFormat::S24BE4 => { - nbr_clipped = write_samples!(values, target, to_s24_4_be); + write_samples!(values, target, to_s24_4_be) } SampleFormat::S32LE => { - nbr_clipped = write_samples!(values, target, to_s32_le); + write_samples!(values, target, to_s32_le) } SampleFormat::S32BE => { - nbr_clipped = write_samples!(values, target, to_s32_be); + write_samples!(values, target, to_s32_be) } SampleFormat::F32LE => { - nbr_clipped = write_samples!(values, target, to_f32_le); + write_samples!(values, target, to_f32_le) } SampleFormat::F32BE => { - nbr_clipped = write_samples!(values, target, to_f32_be); + write_samples!(values, target, to_f32_be) } SampleFormat::F64LE => { - nbr_clipped = write_samples!(values, target, to_f64_le); + write_samples!(values, target, to_f64_le) } SampleFormat::F64BE => { - nbr_clipped = write_samples!(values, target, to_f64_be); + write_samples!(values, target, to_f64_be) } - } + }; Ok(nbr_clipped) } } @@ -244,10 +260,10 @@ macro_rules! read_all_samples_to_vec { /// The SampleReader trait enables reading and converting raw bytes and to multiple samples. -pub trait SampleReader> { - /// Read bytes from anything that implements the "Read" trait. - /// This can be for example a file, or a slice of u8. - /// The bytes are then converted to f32 or f64 values, and stored in a slice. +pub trait SampleReader> { + /// Read bytes from anything that implements the [std::io::Read] trait. + /// This can be for example a file, or a slice of `u8`. + /// The bytes are then converted to `f32` or `f64` values, and stored in a slice. /// It will read until the samples slice is filled. /// If end-of-file of the source is reached before the slice is filled, the remaining values of the slice are left untouched. /// The number of samples read is returned. @@ -256,51 +272,56 @@ pub trait SampleReader> { samples: &mut [T], sampleformat: &SampleFormat, ) -> Result> { - let nbr_read; - match sampleformat { + let nbr_read = match sampleformat { + SampleFormat::S8 => { + read_samples_to_slice!(rawbytes, samples, from_s8_ne, 1) + } + SampleFormat::U8 => { + read_samples_to_slice!(rawbytes, samples, from_u8_ne, 1) + } SampleFormat::S16LE => { - nbr_read = read_samples_to_slice!(rawbytes, samples, from_s16_le, 2); + read_samples_to_slice!(rawbytes, samples, from_s16_le, 2) } SampleFormat::S16BE => { - nbr_read = read_samples_to_slice!(rawbytes, samples, from_s16_be, 2); + read_samples_to_slice!(rawbytes, samples, from_s16_be, 2) } SampleFormat::S24LE3 => { - nbr_read = read_samples_to_slice!(rawbytes, samples, from_s24_3_le, 3); + read_samples_to_slice!(rawbytes, samples, from_s24_3_le, 3) } SampleFormat::S24BE3 => { - nbr_read = read_samples_to_slice!(rawbytes, samples, from_s24_3_be, 3); + read_samples_to_slice!(rawbytes, samples, from_s24_3_be, 3) } SampleFormat::S24LE4 => { - nbr_read = read_samples_to_slice!(rawbytes, samples, from_s24_4_le, 4); + read_samples_to_slice!(rawbytes, samples, from_s24_4_le, 4) } SampleFormat::S24BE4 => { - nbr_read = read_samples_to_slice!(rawbytes, samples, from_s24_4_be, 4); + read_samples_to_slice!(rawbytes, samples, from_s24_4_be, 4) } SampleFormat::S32LE => { - nbr_read = read_samples_to_slice!(rawbytes, samples, from_s32_le, 4); + read_samples_to_slice!(rawbytes, samples, from_s32_le, 4) } SampleFormat::S32BE => { - nbr_read = read_samples_to_slice!(rawbytes, samples, from_s32_be, 4); + read_samples_to_slice!(rawbytes, samples, from_s32_be, 4) } SampleFormat::F32LE => { - nbr_read = read_samples_to_slice!(rawbytes, samples, from_f32_le, 4); + read_samples_to_slice!(rawbytes, samples, from_f32_le, 4) } SampleFormat::F32BE => { - nbr_read = read_samples_to_slice!(rawbytes, samples, from_f32_be, 4); + read_samples_to_slice!(rawbytes, samples, from_f32_be, 4) } SampleFormat::F64LE => { - nbr_read = read_samples_to_slice!(rawbytes, samples, from_f64_le, 8); + read_samples_to_slice!(rawbytes, samples, from_f64_le, 8) } SampleFormat::F64BE => { - nbr_read = read_samples_to_slice!(rawbytes, samples, from_f64_be, 8); + read_samples_to_slice!(rawbytes, samples, from_f64_be, 8) } - } + }; Ok(nbr_read) } /// Read all bytes from anything that implements the "Read" trait. - /// This can be for example a file, or a slice of u8. - /// The bytes are then converted to f32 or f64 values, and appended to a vec. + /// This can be for example a file, or a slice of `u8`. + /// The bytes are then converted to `f32` or `f64` values, and appended to a vec. /// It will continue reading until reaching end-of-file of the source. /// The number of samples read is returned. fn read_all_samples( @@ -310,6 +331,12 @@ pub trait SampleReader> { ) -> Result> { let start_len = samples.len(); match sampleformat { + SampleFormat::S8 => { + read_all_samples_to_vec!(rawbytes, samples, from_s8_ne, 1); + } + SampleFormat::U8 => { + read_all_samples_to_vec!(rawbytes, samples, from_u8_ne, 1); + } SampleFormat::S16LE => { read_all_samples_to_vec!(rawbytes, samples, from_s16_le, 2); } @@ -374,10 +401,23 @@ fn clamp_float(value: T) -> (T, bool) { (value, false) } -impl Sample for f64 { +impl BytesSample for f64 { const MAX_I32: f64 = 2147483648.0; const MAX_I24: f64 = 8388608.0; const MAX_I16: f64 = 32768.0; + const MAX_I8: f64 = 128.0; + + fn to_s8_ne(&self) -> ([u8; 1], bool) { + let val = self * f64::MAX_I8; + let (val, clipped) = clamp_int::(val); + ((val as i8).to_le_bytes(), clipped) + } + + fn to_u8_ne(&self) -> ([u8; 1], bool) { + let val = self * f64::MAX_I8 + f64::MAX_I8; + let (val, clipped) = clamp_int::(val); + ((val as u8).to_le_bytes(), clipped) + } fn to_s16_le(&self) -> ([u8; 2], bool) { let val = self * f64::MAX_I16; @@ -455,6 +495,16 @@ impl Sample for f64 { (val.to_be_bytes(), clipped) } + fn from_s8_ne(bytes: [u8; 1]) -> Self { + let intvalue = i8::from_le_bytes(bytes); + f64::from(intvalue) / f64::MAX_I8 + } + + fn from_u8_ne(bytes: [u8; 1]) -> Self { + let intvalue = u8::from_le_bytes(bytes); + (f64::from(intvalue) - f64::MAX_I8) / f64::MAX_I8 + } + fn from_s32_le(bytes: [u8; 4]) -> Self { let intvalue = i32::from_le_bytes(bytes); f64::from(intvalue) / f64::MAX_I32 @@ -516,10 +566,23 @@ impl Sample for f64 { } } -impl Sample for f32 { +impl BytesSample for f32 { const MAX_I32: f32 = 2147483648.0; const MAX_I24: f32 = 8388608.0; const MAX_I16: f32 = 32768.0; + const MAX_I8: f32 = 128.0; + + fn to_s8_ne(&self) -> ([u8; 1], bool) { + let val = self * f32::MAX_I8; + let (val, clipped) = clamp_int::(val); + ((val as i8).to_le_bytes(), clipped) + } + + fn to_u8_ne(&self) -> ([u8; 1], bool) { + let val = self * f32::MAX_I8 + f32::MAX_I8; + let (val, clipped) = clamp_int::(val); + ((val as u8).to_le_bytes(), clipped) + } fn to_s16_le(&self) -> ([u8; 2], bool) { let val = self * f32::MAX_I16; @@ -595,6 +658,16 @@ impl Sample for f32 { (val.to_be_bytes(), clipped) } + fn from_s8_ne(bytes: [u8; 1]) -> Self { + let intvalue = i8::from_le_bytes(bytes); + f32::from(intvalue) / f32::MAX_I8 + } + + fn from_u8_ne(bytes: [u8; 1]) -> Self { + let intvalue = u8::from_le_bytes(bytes); + (f32::from(intvalue) - f32::MAX_I8) / f32::MAX_I8 + } + fn from_s32_le(bytes: [u8; 4]) -> Self { let intvalue = i32::from_le_bytes(bytes); intvalue as f32 / f32::MAX_I32 @@ -656,9 +729,160 @@ impl Sample for f32 { } } +/// The NumericSample trait is used for conversions of samples +/// between numeric types such as `i16`, `i32` or `f32`, +/// and float values (`f32` or `f64`). +/// +/// The trait methods are similar to the [Sample] methods, +/// but convert to and from numeric types instead of raw bytes. +pub trait NumericSample { + /// Convert a sample value to `i8` + fn to_i8(&self) -> (i8, bool); + /// Convert a sample value to `u8` + fn to_u8(&self) -> (u8, bool); + /// Convert a sample value to `i16` + fn to_i16(&self) -> (i16, bool); + /// Convert a sample value to `i32` + fn to_i32(&self) -> (i32, bool); + /// Convert a sample value to `f32` + fn to_f32(&self) -> (f32, bool); + /// Convert a sample value to `f64` + fn to_f64(&self) -> (f64, bool); + + /// Convert `i8` to a sample value + fn from_i8(value: i8) -> Self; + /// Convert `u8` to a sample value + fn from_u8(value: u8) -> Self; + /// Convert `i16` to a sample value + fn from_i16(value: i16) -> Self; + /// Convert `i32` to a sample value + fn from_i32(value: i32) -> Self; + /// Convert `f32` to a sample value + fn from_f32(value: f32) -> Self; + /// Convert `f64` to a sample value + fn from_f64(value: f64) -> Self; +} + +impl NumericSample for f32 { + fn to_i8(&self) -> (i8, bool) { + let val = self * f32::MAX_I8; + let (val, clipped) = clamp_int::(val); + (val as i8, clipped) + } + + fn to_u8(&self) -> (u8, bool) { + let val = self * f32::MAX_I8 + f32::MAX_I8; + let (val, clipped) = clamp_int::(val); + (val as u8, clipped) + } + + fn to_i16(&self) -> (i16, bool) { + let val = self * f32::MAX_I16; + let (val, clipped) = clamp_int::(val); + (val as i16, clipped) + } + + fn to_i32(&self) -> (i32, bool) { + let val = self * f32::MAX_I32; + let (val, clipped) = clamp_int::(val); + (val as i32, clipped) + } + + fn to_f32(&self) -> (f32, bool) { + (*self, false) + } + + fn to_f64(&self) -> (f64, bool) { + (*self as f64, false) + } + + fn from_i8(value: i8) -> Self { + f32::from(value) / f32::MAX_I8 + } + + fn from_u8(value: u8) -> Self { + (f32::from(value) - f32::MAX_I8) / f32::MAX_I8 + } + + fn from_i16(value: i16) -> Self { + f32::from(value) / f32::MAX_I16 + } + + fn from_i32(value: i32) -> Self { + value as f32 / f32::MAX_I32 + } + + fn from_f32(value: f32) -> Self { + value + } + + fn from_f64(value: f64) -> Self { + value as f32 + } +} + +impl NumericSample for f64 { + fn to_i8(&self) -> (i8, bool) { + let val = self * f64::MAX_I8; + let (val, clipped) = clamp_int::(val); + (val as i8, clipped) + } + + fn to_u8(&self) -> (u8, bool) { + let val = self * f64::MAX_I8 + f64::MAX_I8; + let (val, clipped) = clamp_int::(val); + (val as u8, clipped) + } + + fn to_i16(&self) -> (i16, bool) { + let val = self * f64::MAX_I16; + let (val, clipped) = clamp_int::(val); + (val as i16, clipped) + } + + fn to_i32(&self) -> (i32, bool) { + let val = self * f64::MAX_I32; + let (val, clipped) = clamp_int::(val); + (val as i32, clipped) + } + + fn to_f32(&self) -> (f32, bool) { + (*self as f32, false) + } + + fn to_f64(&self) -> (f64, bool) { + (*self, false) + } + + fn from_i8(value: i8) -> Self { + f64::from(value) / f64::MAX_I8 + } + + fn from_u8(value: u8) -> Self { + (f64::from(value) - f64::MAX_I8) / f64::MAX_I8 + } + + fn from_i16(value: i16) -> Self { + f64::from(value) / f64::MAX_I16 + } + + fn from_i32(value: i32) -> Self { + value as f64 / f64::MAX_I32 + } + + fn from_f32(value: f32) -> Self { + value as f64 + } + + fn from_f64(value: f64) -> Self { + value + } +} + #[cfg(test)] mod tests { - use crate::Sample; + use crate::NumericSample; + use crate::BytesSample; use crate::SampleFormat; use crate::SampleReader; use crate::SampleWriter; @@ -701,7 +925,6 @@ mod tests { assert_eq!(val.to_s32_be(), ([128, 0, 0, 0], true)); } - #[test] fn check_f64_from_s32be() { let data = [32, 64, 0, 0]; @@ -1029,7 +1252,39 @@ mod tests { assert_eq!(val.to_s16_be(), ([32, 222], false)); let val: f32 = -0.256789; assert_eq!(val.to_s16_be(), ([223, 34], false)); - } + } + + #[test] + fn check_f32_to_s8() { + let val: f32 = 0.25; + assert_eq!(val.to_s8_ne(), ([32], false)); + let val: f32 = -0.25; + assert_eq!(val.to_s8_ne(), ([224], false)); + } + + #[test] + fn check_f32_to_u8() { + let val: f32 = 0.25; + assert_eq!(val.to_u8_ne(), ([160], false)); + let val: f32 = -0.25; + assert_eq!(val.to_u8_ne(), ([96], false)); + } + + #[test] + fn check_f32_from_s8() { + let data = [32]; + assert_eq!(f32::from_s8_ne(data), 0.25); + let data = [224]; + assert_eq!(f32::from_s8_ne(data), -0.25); + } + + #[test] + fn check_f32_from_u8() { + let data = [160]; + assert_eq!(f32::from_u8_ne(data), 0.25); + let data = [96]; + assert_eq!(f32::from_u8_ne(data), -0.25); + } #[test] fn check_f32_to_f32le() { @@ -1171,4 +1426,154 @@ mod tests { let expected = vec![-0.5, -0.25, -0.125, 0.0, 0.125, 0.25, 0.5, 0.75, 0.75]; assert_eq!(expected, values2); } + + // ------------------- + // single values to integers + // ------------------- + #[test] + fn check_f32_to_i16() { + let val: f32 = 0.25; + assert_eq!(val.to_i16(), (2 << 12, false)); + let val: f32 = -0.25; + assert_eq!(val.to_i16(), (-2 << 12, false)); + let val: f32 = 1.1; + assert_eq!(val.to_i16(), (i16::MAX, true)); + let val: f32 = -1.1; + assert_eq!(val.to_i16(), (i16::MIN, true)); + } + + #[test] + fn check_f32_to_i32() { + let val: f32 = 0.25; + assert_eq!(val.to_i32(), (2 << 28, false)); + let val: f32 = -0.25; + assert_eq!(val.to_i32(), (-2 << 28, false)); + let val: f32 = 1.1; + assert_eq!(val.to_i32(), (i32::MAX, true)); + let val: f32 = -1.1; + assert_eq!(val.to_i32(), (i32::MIN, true)); + } + + #[test] + fn check_f64_to_i16() { + let val: f64 = 0.25; + assert_eq!(val.to_i16(), (2 << 12, false)); + let val: f64 = -0.25; + assert_eq!(val.to_i16(), (-2 << 12, false)); + let val: f64 = 1.1; + assert_eq!(val.to_i16(), (i16::MAX, true)); + let val: f64 = -1.1; + assert_eq!(val.to_i16(), (i16::MIN, true)); + } + + #[test] + fn check_f64_to_i32() { + let val: f64 = 0.25; + assert_eq!(val.to_i32(), (2 << 28, false)); + let val: f64 = -0.25; + assert_eq!(val.to_i32(), (-2 << 28, false)); + let val: f64 = 1.1; + assert_eq!(val.to_i32(), (i32::MAX, true)); + let val: f64 = -1.1; + assert_eq!(val.to_i32(), (i32::MIN, true)); + } + + #[test] + fn check_f64_to_i8() { + let val: f64 = 0.25; + assert_eq!(val.to_i8(), (2 << 4, false)); + let val: f64 = -0.25; + assert_eq!(val.to_i8(), (-2 << 4, false)); + let val: f64 = 1.1; + assert_eq!(val.to_i8(), (i8::MAX, true)); + let val: f64 = -1.1; + assert_eq!(val.to_i8(), (i8::MIN, true)); + } + + #[test] + fn check_f64_to_u8() { + let val: f64 = 0.25; + assert_eq!(val.to_u8(), (128 + (2 << 4), false)); + let val: f64 = -0.25; + assert_eq!(val.to_u8(), (128 - (2 << 4), false)); + let val: f64 = 1.1; + assert_eq!(val.to_u8(), (u8::MAX, true)); + let val: f64 = -1.1; + assert_eq!(val.to_u8(), (u8::MIN, true)); + } + + // ------------------- + // single values from integers + // ------------------- + #[test] + fn check_f32_from_i16() { + let val: i16 = 2 << 12; + assert_eq!(f32::from_i16(val), 0.25); + let val: i16 = -2 << 12; + assert_eq!(f32::from_i16(val), -0.25); + let val: i16 = i16::MAX; + assert_eq!(f32::from_i16(val), 0.9999695); + let val: i16 = i16::MIN; + assert_eq!(f32::from_i16(val), -1.0); + } + + #[test] + fn check_f32_from_i32() { + let val: i32 = 2 << 28; + assert_eq!(f32::from_i32(val), 0.25); + let val: i32 = -2 << 28; + assert_eq!(f32::from_i32(val), -0.25); + let val: i32 = i32::MAX; + assert_eq!(f32::from_i32(val), 1.0); + let val: i32 = i32::MIN; + assert_eq!(f32::from_i32(val), -1.0); + } + + #[test] + fn check_f64_from_i16() { + let val: i16 = 2 << 12; + assert_eq!(f64::from_i16(val), 0.25); + let val: i16 = -2 << 12; + assert_eq!(f64::from_i16(val), -0.25); + let val: i16 = i16::MAX; + assert_eq!(f64::from_i16(val), 0.999969482421875); + let val: i16 = i16::MIN; + assert_eq!(f64::from_i16(val), -1.0); + } + + #[test] + fn check_f64_from_i32() { + let val: i32 = 2 << 28; + assert_eq!(f64::from_i32(val), 0.25); + let val: i32 = -2 << 28; + assert_eq!(f64::from_i32(val), -0.25); + let val: i32 = i32::MAX; + assert_eq!(f64::from_i32(val), 0.9999999995343387); + let val: i32 = i32::MIN; + assert_eq!(f64::from_i32(val), -1.0); + } + + #[test] + fn check_f64_from_i8() { + let val: i8 = 2 << 4; + assert_eq!(f64::from_i8(val), 0.25); + let val: i8 = -2 << 4; + assert_eq!(f64::from_i8(val), -0.25); + let val: i8 = i8::MAX; + assert_eq!(f64::from_i8(val), 0.9921875); + let val: i8 = i8::MIN; + assert_eq!(f64::from_i8(val), -1.0); + } + + #[test] + fn check_f64_from_u8() { + let val: u8 = 128 + (2 << 4); + assert_eq!(f64::from_u8(val), 0.25); + let val: u8 = 128 - (2 << 4); + assert_eq!(f64::from_u8(val), -0.25); + let val: u8 = u8::MAX; + assert_eq!(f64::from_u8(val), 0.9921875); + let val: u8 = u8::MIN; + assert_eq!(f64::from_u8(val), -1.0); + } }