///|
// Deliberately excludes Int64 minimum, so negation and absolute value are safe.
let decimal_bound : Int64 = 9000000000000000000L
///|
pub struct DecimalValue {
priv coefficient : Int64
priv scale : Int32
} derive(Eq, @debug.Debug)
///|
pub extend DecimalValue with Eq::{equal, not_equal}
///|
pub extend DecimalValue with @debug.Debug::{to_repr}
///|
fn decimal_abs(n : Int64) -> Int64 {
if n < 0L {
-n
} else {
n
}
}
///|
fn decimal_times_ten(n : Int64) -> Int64 raise SdmxError {
if decimal_abs(n) > decimal_bound / 10L {
raise Invalid(path="decimal", reason="coefficient overflow")
}
n * 10L
}
///|
fn normalized_decimal(coefficient : Int64, scale : Int32) -> DecimalValue {
let mut c = coefficient
let mut s = scale
while s > 0 && c % 10L == 0L {
c /= 10L
s -= 1
}
{ coefficient: c, scale: s, }
}
///|
pub fn DecimalValue::parse(text : String) -> DecimalValue raise SdmxError {
if text.is_empty() || text.length() > 128 {
raise Invalid(path="decimal", reason="expected 1..128 characters")
}
let chars = text.to_array()
let mut i = 0
let mut negative = false
if chars[0] == '-' || chars[0] == '+' {
negative = chars[0] == '-'
i += 1
}
let mut coefficient = 0L
let mut digits = 0
let mut scale = 0
let mut dot = false
while i < chars.length() && chars[i] != 'e' && chars[i] != 'E' {
let ch = chars[i]
if ch == '.' && !dot {
dot = true
} else if ch >= '0' && ch <= '9' {
let digit = (ch.to_int() - 48).to_int64()
if coefficient > (decimal_bound - digit) / 10L {
raise Invalid(path="decimal", reason="coefficient overflow")
}
coefficient = coefficient * 10L + digit
digits += 1
if dot {
scale += 1
}
} else {
raise Invalid(path="decimal", reason="invalid numeric character")
}
i += 1
}
if digits == 0 {
raise Invalid(path="decimal", reason="missing digits")
}
let mut exponent = 0
if i < chars.length() {
i += 1
let mut exp_negative = false
if i < chars.length() && (chars[i] == '+' || chars[i] == '-') {
exp_negative = chars[i] == '-'
i += 1
}
let start = i
while i < chars.length() {
let ch = chars[i]
if ch < '0' || ch > '9' || exponent > 100 {
raise Invalid(
path="decimal.exponent",
reason="invalid or excessive exponent",
)
}
exponent = exponent * 10 + ch.to_int() - 48
i += 1
}
if i == start || exponent > 128 {
raise Invalid(
path="decimal.exponent",
reason="missing or excessive exponent",
)
}
if exp_negative {
exponent = -exponent
}
}
scale -= exponent
while scale < 0 {
coefficient = decimal_times_ten(coefficient)
scale += 1
}
if negative {
coefficient = -coefficient
}
let value = normalized_decimal(coefficient, scale)
if value.scale > 18 {
raise Invalid(path="decimal", reason="more than 18 fractional digits")
}
value
}
///|
pub fn DecimalValue::format(self : DecimalValue) -> String {
let digits = decimal_abs(self.coefficient).to_string()
let sign = if self.coefficient < 0L { "-" } else { "" }
if self.scale == 0 {
return sign + digits
}
if digits.length() <= self.scale {
return sign + "0." + "0".repeat(self.scale - digits.length()) + digits
}
let split = digits.length() - self.scale
sign + digits[:split].to_owned() + "." + digits[split:].to_owned()
}
///|
pub fn DecimalValue::add(
self : DecimalValue,
other : DecimalValue,
) -> DecimalValue raise SdmxError {
let scale = if self.scale > other.scale { self.scale } else { other.scale }
let mut left = self.coefficient
let mut right = other.coefficient
for i = self.scale; i < scale; i = i + 1 {
left = decimal_times_ten(left)
}
for i = other.scale; i < scale; i = i + 1 {
right = decimal_times_ten(right)
}
if (right > 0L && left > decimal_bound - right) ||
(right < 0L && left < -decimal_bound - right) {
raise Invalid(path="decimal.sum", reason="coefficient overflow")
}
normalized_decimal(left + right, scale)
}
///|
pub fn DecimalValue::compare_value(
self : DecimalValue,
other : DecimalValue,
) -> Int32 {
if self.coefficient == 0L {
return if other.coefficient == 0L {
0
} else if other.coefficient > 0L {
-1
} else {
1
}
}
if other.coefficient == 0L {
return if self.coefficient > 0L { 1 } else { -1 }
}
if self.coefficient < 0L && other.coefficient >= 0L {
return -1
}
if self.coefficient >= 0L && other.coefficient < 0L {
return 1
}
let scale = if self.scale > other.scale { self.scale } else { other.scale }
let a = decimal_abs(self.coefficient).to_string() +
"0".repeat(scale - self.scale)
let b = decimal_abs(other.coefficient).to_string() +
"0".repeat(scale - other.scale)
let result = if a.length() != b.length() {
a.length().compare(b.length())
} else {
a.lexical_compare(b)
}
if self.coefficient < 0L {
-result
} else {
result
}
}
///|
pub fn DecimalValue::is_integer(self : DecimalValue) -> Bool {
self.scale == 0
}