///|
/// A bounded decimal that preserves the coefficient and number of fractional digits.
pub(all) struct ExactDecimal {
coefficient_value : Int64
scale_value : Int
negative_zero_value : Bool
} derive(Eq, Debug)
///|
fn decimal_error(message : String) -> NmeaError {
NmeaError::from_diagnostic(
Diagnostic::new(FieldInvalidDecimal, Error, message, SourceRef::sentence(0)),
)
}
///|
/// Parses a base-10 decimal without accepting exponent notation.
pub fn ExactDecimal::parse(
text : String,
max_scale? : Int = 9,
) -> Result[ExactDecimal, NmeaError] {
if max_scale < 0 || text.length() == 0 {
return Err(decimal_error("decimal is empty or has an invalid scale limit"))
}
let mut index = 0
let mut negative = false
if text[0].to_int() == 45 {
negative = true
index = 1
} else if text[0].to_int() == 43 {
index = 1
}
if index >= text.length() {
return Err(decimal_error("decimal has no digits"))
}
let mut coefficient : Int64 = 0L
let mut scale = 0
let mut digit_count = 0
let mut seen_dot = false
let mut saw_digit = false
while index < text.length() {
let code = text[index].to_int()
if code == 46 {
if seen_dot {
return Err(decimal_error("decimal contains multiple decimal points"))
}
seen_dot = true
index = index + 1
continue
}
if code < 48 || code > 57 {
return Err(decimal_error("decimal contains a non-digit character"))
}
saw_digit = true
digit_count = digit_count + 1
if digit_count > 18 {
return Err(decimal_error("decimal exceeds the supported digit bound"))
}
if seen_dot {
scale = scale + 1
if scale > max_scale {
return Err(decimal_error("decimal exceeds the configured scale"))
}
}
coefficient = coefficient * 10L + (code - 48).to_int64()
index = index + 1
}
if !saw_digit {
return Err(decimal_error("decimal has no digits"))
}
let signed = if negative { -coefficient } else { coefficient }
Ok({
coefficient_value: signed,
scale_value: scale,
negative_zero_value: negative && coefficient == 0L,
})
}
///|
pub fn ExactDecimal::coefficient(self : ExactDecimal) -> Int64 {
self.coefficient_value
}
///|
pub fn ExactDecimal::scale(self : ExactDecimal) -> Int {
self.scale_value
}
///|
pub fn ExactDecimal::is_negative_zero(self : ExactDecimal) -> Bool {
self.negative_zero_value
}
///|
pub fn ExactDecimal::compare(self : ExactDecimal, other : ExactDecimal) -> Int {
let mut left = self.coefficient_value
let mut right = other.coefficient_value
if self.scale_value < other.scale_value {
let mut count = 0
while count < other.scale_value - self.scale_value {
left = left * 10L
count = count + 1
}
} else if other.scale_value < self.scale_value {
let mut count = 0
while count < self.scale_value - other.scale_value {
right = right * 10L
count = count + 1
}
}
if left < right {
-1
} else if left > right {
1
} else {
0
}
}
///|
pub fn ExactDecimal::to_string(self : ExactDecimal) -> String {
let negative = self.coefficient_value < 0L || self.negative_zero_value
let magnitude = if self.coefficient_value < 0L {
-self.coefficient_value
} else {
self.coefficient_value
}
if self.scale_value == 0 {
return (if negative { "-" } else { "" }) + magnitude.to_string()
}
let digits = magnitude.to_string()
let padded = if digits.length() <= self.scale_value {
"0" + String::repeat("0", self.scale_value - digits.length()) + digits
} else {
digits
}
let split = padded.length() - self.scale_value
let whole = padded[:split].to_owned()
let fraction = padded[split:].to_owned()
(if negative { "-" } else { "" }) + whole + "." + fraction
}
///|
pub fn ExactDecimal::to_double(self : ExactDecimal) -> Double {
let mut divisor = 1.0
for _ in 0..