mod test;
use std::fmt;
use std::convert::TryInto;
use std::borrow::Cow;
use failure_derive::Fail;
use serde_derive::{Serialize, Deserialize};
pub type TickUnit = u64;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
pub struct Tick(TickUnit);
impl fmt::Display for Tick {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}^-1)", self.0)
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
pub enum Tickable {
Ticked(TickUnit),
Unticked(String),
}
impl From<TickUnit> for Tickable {
fn from(ticks: TickUnit) -> Tickable {
Tickable::Ticked(ticks)
}
}
impl From<String> for Tickable {
fn from(unticked: String) -> Tickable {
Tickable::Unticked(unticked)
}
}
impl From<&str> for Tickable {
fn from(unticked: &str) -> Tickable {
Tickable::Unticked(unticked.to_owned())
}
}
impl Tickable {
pub fn ticked(&self, tick: Tick) -> TickUnit {
match self {
Tickable::Ticked(value) => *value,
Tickable::Unticked(value) => tick.ticked(&value).unwrap()
}
}
pub fn unticked(&'_ self, tick: Tick) -> Cow<'_, str> {
match self {
Tickable::Ticked(value) => Cow::Owned(tick.unticked(*value).unwrap()),
Tickable::Unticked(value) => Cow::Borrowed(&value)
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Fail)]
#[fail(display = "failed to convert {:?} with tick {}", value, tick)]
pub struct ConversionError {
tick: Tick,
value: Tickable,
}
impl ConversionError {
fn ticked(value: TickUnit, tick: Tick) -> Self {
ConversionError {
tick,
value: Tickable::Ticked(value),
}
}
fn unticked(value: String, tick: Tick) -> Self {
ConversionError {
tick,
value: Tickable::Unticked(value),
}
}
}
impl Tick {
pub fn new(ticks_per_unit: TickUnit) -> Self {
if ticks_per_unit == 0 {
panic!("`ticks_per_unit` cannot be 0");
}
Tick(ticks_per_unit)
}
pub fn ticks_per_unit(self) -> TickUnit {
self.0
}
pub fn ticked(self, unticked: &str) -> Result<TickUnit, ConversionError> {
let mut denom: u128 = 0;
let mut int: u64 = 0;
let mut fract: u64 = 0;
let mut base: u64 = 1;
let mut left = false;
for c in unticked.chars().rev() {
let digit = match c {
'0' ... '9' => (c as u64) - ('0' as u64),
'.' => {
left = true;
denom = u128::from(base);
base = 1;
continue;
}
_ => return Err(ConversionError::unticked(unticked.to_owned(), self)),
};
if left {
int = int.checked_add(digit.checked_mul(base).unwrap()).unwrap();
} else {
fract = fract.checked_add(digit.checked_mul(base).unwrap()).unwrap();
}
base = base.checked_mul(10).unwrap();
}
if !left {
int = fract;
fract = 0;
denom = 1;
}
let num = u128::from(int)
.checked_mul(denom).unwrap()
.checked_add(u128::from(fract)).unwrap()
.checked_mul(u128::from(self.0)).unwrap();
Ok((num / denom).try_into().unwrap())
}
pub fn unticked(self, ticked: TickUnit) -> Result<String, ConversionError> {
let mut pad: usize = 0;
let mut pow: u64 = 1;
while self.0 > pow { pad += 1;
pow = pow.checked_mul(10).unwrap();
}
if pow % self.0 != 0 {
return Err(ConversionError::ticked(ticked.to_owned(), self));
}
let int = ticked / self.0;
let pow = u128::from(pow);
let fract = (pow * u128::from(ticked) / u128::from(self.0)) % pow; let fract: u64 = fract.try_into().unwrap();
fn write(mut num: u64, out: &mut [u8], mut used: usize) -> usize {
loop {
let digit = (num % 10) as u8;
out[used] = b'0' + digit;
num /= 10;
used += 1;
if num == 0 {
break;
}
}
used
};
let mut out = [b'0'; 21];
let _ = write(fract, &mut out[..], 0);
out[pad] = b'.';
let used = write(int, &mut out[..], pad + 1);
let mut s = Vec::with_capacity(used);
for c in out[..used].iter().rev() {
s.push(*c as u8);
}
Ok(String::from_utf8(s).expect("cannot fail"))
}
crate fn tick_size(unticked: &str) -> Option<Tick> {
if unticked.starts_with('1') || unticked.starts_with("1.") {
return Some(Tick::new(1));
}
let index = unticked.chars().position(|c| c == '.')?;
let fract = unticked[index + 1 ..].parse::<u64>().ok()?;
let exp = unticked[index + 1 ..].len();
let pow = 10u64.checked_pow(exp as u32)?;
if pow % fract != 0 {
return None;
}
Some(Tick::new(pow / fract))
}
}