// Copyright 2026 hua1104. Apache-2.0.
//
// Plain-decimal formatting shared by the report writers and the SVG renderer.
// Actuarial tables are read by eye down their columns, so values stay in fixed
// decimal form and are never abbreviated into scientific notation.
///|
/// Render `value` rounded to `places` decimal places. Negative values keep
/// their sign even when rounding to zero, which matters for small reserves.
pub fn fixed(value : Double, places : Int32) -> String {
if places <= 0 {
let rounded = @math.round(value)
return "\{rounded}"
}
let scale = @math.pow(10.0, places.to_double())
let rounded = @math.round(value * scale)
// The sign is taken from the input, not from the rounded value: rounding a
// tiny negative reserve gives negative zero, and negative zero is not
// negative, so testing `rounded` would silently lose the sign.
let negative = value < 0.0
let magnitude = if negative { 0.0 - rounded } else { rounded }
let whole = @math.floor(magnitude / scale)
let fraction = magnitude - whole * scale
let sign = if negative { "-" } else { "" }
sign + "\{whole}" + "." + fill_zero("\{fraction}", places)
}
///|
/// Left-pad a string with zeros until it reaches `width` characters.
fn fill_zero(text : String, width : Int32) -> String {
let mut out = text
while out.length() < width {
out = "0" + out
}
out
}
///|
/// Render a value as a percentage with `places` decimal places.
pub fn percent(value : Double, places : Int32) -> String {
fixed(value * 100.0, places) + "%"
}
///|
/// Right-align `value` in a field `width` characters wide, growing past it
/// rather than truncating when the content is longer.
pub fn pad_left(value : String, width : Int32) -> String {
let mut out = value
while out.length() < width {
out = " " + out
}
out
}
///|
/// Left-align `value` in a field `width` characters wide.
pub fn pad_right(value : String, width : Int32) -> String {
let mut out = value
while out.length() < width {
out = out + " "
}
out
}
///|
/// A horizontal rule of `width` characters of the given `pattern`. Used for
/// table rules without depending on Unicode box drawing.
pub fn rule(width : Int32, pattern : Char) -> String {
let mut out = ""
for _i in 0..