use crate::output::{Format, Formatted};
use std::fmt::{self, Write};
use std::ops::{Add, Div, Mul, Neg, Rem, Sub};
#[derive(Clone)]
pub struct Number {
value: f64,
}
impl PartialEq for Number {
fn eq(&self, other: &Self) -> bool {
(self.value - other.value).abs() / self.value.abs() <= f64::EPSILON
}
}
impl Eq for Number {}
impl PartialOrd for Number {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
if self == other {
Some(std::cmp::Ordering::Equal)
} else {
self.value.partial_cmp(&other.value)
}
}
}
impl Neg for Number {
type Output = Self;
fn neg(self) -> Self {
-&self
}
}
impl Neg for &Number {
type Output = Number;
fn neg(self) -> Number {
(-self.value).into()
}
}
impl Mul for Number {
type Output = Self;
fn mul(mut self, rhs: Self) -> Self {
self.value *= rhs.value;
self
}
}
impl Mul for &Number {
type Output = Number;
fn mul(self, rhs: Self) -> Self::Output {
(self.value * rhs.value).into()
}
}
impl Number {
pub fn ceil(&self) -> Self {
Self {
value: self.value.ceil(),
}
}
pub fn floor(&self) -> Self {
Self {
value: self.value.floor(),
}
}
pub fn trunc(&self) -> Self {
Self {
value: self.value.trunc(),
}
}
pub fn round(&self) -> Self {
Self {
value: self.value.round(),
}
}
pub fn signum(&self) -> Self {
if self.value == 0. {
self.clone()
} else {
self.value.signum().into()
}
}
pub fn abs(&self) -> Self {
self.value.abs().into()
}
pub fn is_negative(&self) -> bool {
self.value.is_sign_negative()
}
pub fn is_finite(&self) -> bool {
self.value.is_finite()
}
pub fn into_integer(self) -> Result<i64, Self> {
let int = self.value.round() as i64;
if ((int as f64) - self.value).abs() <= f32::EPSILON.into() {
Ok(int)
} else {
Err(self)
}
}
pub fn powi(self, p: i32) -> Self {
self.value.powi(p).into()
}
pub fn format(&self, format: Format) -> Formatted<Self> {
Formatted {
value: self,
format,
}
}
}
impl From<i64> for Number {
fn from(value: i64) -> Self {
(value as f64).into()
}
}
impl From<i32> for Number {
fn from(value: i32) -> Self {
Self::from(f64::from(value))
}
}
impl From<usize> for Number {
fn from(value: usize) -> Self {
(value as f64).into()
}
}
impl From<f64> for Number {
fn from(value: f64) -> Self {
Self { value }
}
}
impl Add for Number {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
value: self.value + rhs.value,
}
}
}
impl Mul<i64> for Number {
type Output = Self;
fn mul(self, rhs: i64) -> Self {
Self {
value: self.value * (rhs as f64),
}
}
}
impl Div for Number {
type Output = Self;
fn div(self, rhs: Self) -> Self {
Self {
value: self.value / rhs.value,
}
}
}
impl Div<i64> for Number {
type Output = Self;
fn div(self, rhs: i64) -> Self {
Self {
value: self.value / (rhs as f64),
}
}
}
impl From<Number> for f64 {
fn from(val: Number) -> Self {
val.value
}
}
impl Div for &Number {
type Output = Number;
fn div(self, rhs: Self) -> Self::Output {
(self.value / rhs.value).into()
}
}
impl Rem for &Number {
type Output = Number;
fn rem(self, rhs: Self) -> Self::Output {
let a = self.value;
let b = rhs.value;
let result = a % b;
let result =
if a != 0. && (b.is_sign_negative() != a.is_sign_negative()) {
if b.is_finite() {
result + b
} else {
f64::NAN
}
} else {
result
};
Number { value: result }
}
}
impl Sub for &Number {
type Output = Number;
fn sub(self, rhs: Self) -> Self::Output {
(self.value - rhs.value).into()
}
}
impl fmt::Display for Formatted<'_, Number> {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
let s = self.value.value;
if s.is_nan() {
write!(out, "NaN")
} else if s.is_infinite() {
write!(
out,
"{}infinity",
if s.is_sign_negative() { "-" } else { "" }
)
} else {
let mut frac = s.fract();
let mut whole = s.trunc().abs();
let mut dec = String::with_capacity(if frac == 0. {
0
} else {
self.format.precision
});
if frac != 0. {
let max_decimals = 16 - whole.log10().ceil() as usize;
for _ in 1..max_decimals.min(self.format.precision) {
frac *= 10.;
write!(dec, "{}", (frac as i8).abs())?;
frac = frac.fract();
if frac == 0. {
break;
}
}
if frac != 0. {
let end = (frac * 10.).round().abs() as u8;
if end == 10 {
loop {
match dec.pop() {
Some('9') => continue,
None => {
whole += 1.;
break;
}
Some(c) => {
dec.push(char::from(c as u8 + 1));
break;
}
}
}
} else if end == 0 {
loop {
match dec.pop() {
Some('0') => continue,
None => break,
Some(c) => {
dec.push(c);
break;
}
}
}
} else {
write!(dec, "{end}")?;
}
}
}
if s.is_sign_negative() && (whole != 0. || !dec.is_empty()) {
out.write_char('-')?;
}
let skip_zero = self.format.is_compressed();
if !(whole == 0. && skip_zero && !dec.is_empty()) {
write!(out, "{whole}")?;
}
if !dec.is_empty() {
write!(out, ".{dec}")?;
}
Ok(())
}
}
}
impl fmt::Debug for Number {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
write!(out, "Number({:?})", self.value)
}
}
#[test]
fn debug_integer() {
assert_eq!(format!("{:?}", Number::from(17)), "Number(17.0)",);
}
#[test]
fn debug_long_integer() {
assert_eq!(format!("{:#?}", Number::from(17)), "Number(17.0)",);
}
#[test]
fn debug_float() {
assert_eq!(format!("{:?}", Number::from(17.5)), "Number(17.5)",);
}
#[test]
fn debug_int_value() {
assert_eq!(
format!("{:#?}", crate::sass::Value::scalar(17)),
"Numeric(\
\n Number(17.0); UnitSet [],\
\n)",
);
}