feat: improve CLI arguments
This commit is contained in:
+56
-13
@@ -14,6 +14,8 @@ use crate::fractal::{
|
|||||||
compute_set_reference,
|
compute_set_reference,
|
||||||
};
|
};
|
||||||
use crate::lights::Light;
|
use crate::lights::Light;
|
||||||
|
use crate::view::parse_half_height_spec;
|
||||||
|
use crate::view::parse_re_im_spec;
|
||||||
use crate::view::{
|
use crate::view::{
|
||||||
Big, DEFAULT_HALF_HEIGHT, ViewState, big_from_decimal_str, big_from_f64, big_to_decimal_str,
|
Big, DEFAULT_HALF_HEIGHT, ViewState, big_from_decimal_str, big_from_f64, big_to_decimal_str,
|
||||||
parse_view_spec, precision_for,
|
parse_view_spec, precision_for,
|
||||||
@@ -356,7 +358,7 @@ fn sig_digits_for(bits: usize) -> usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Format a magnification for the editable field (compact scientific).
|
/// Format a magnification for the editable field (compact scientific).
|
||||||
fn format_magnification(m: f64) -> String {
|
fn format_zoom(m: f64) -> String {
|
||||||
format!("{m:.4e}")
|
format!("{m:.4e}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,7 +417,7 @@ impl FractalApp {
|
|||||||
let sig = sig_digits_for(view.precision_bits());
|
let sig = sig_digits_for(view.precision_bits());
|
||||||
let center_re_edit = big_to_decimal_str(&view.center_re, sig);
|
let center_re_edit = big_to_decimal_str(&view.center_re, sig);
|
||||||
let center_im_edit = big_to_decimal_str(&view.center_im, sig);
|
let center_im_edit = big_to_decimal_str(&view.center_im, sig);
|
||||||
let zoom_edit = format_magnification(view.magnification());
|
let zoom_edit = format_zoom(view.zoom());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
view,
|
view,
|
||||||
@@ -532,16 +534,24 @@ impl FractalApp {
|
|||||||
if let Some(spec) = cli.view {
|
if let Some(spec) = cli.view {
|
||||||
self.apply_view_spec(&spec);
|
self.apply_view_spec(&spec);
|
||||||
}
|
}
|
||||||
|
if let Some(iterations) = cli.iterations {
|
||||||
|
self.auto_iterations = false;
|
||||||
|
self.max_iterations = iterations;
|
||||||
|
}
|
||||||
|
if let Some(half_height) = cli.half_height {
|
||||||
|
self.apply_half_height_spec(&half_height);
|
||||||
|
}
|
||||||
|
if let Some(position) = cli.position {
|
||||||
|
self.apply_re_im_spec(&position);
|
||||||
|
}
|
||||||
if cli.de {
|
if cli.de {
|
||||||
self.de_coloring = true;
|
self.de_coloring = true;
|
||||||
}
|
}
|
||||||
if cli.buddhabrot {
|
if cli.buddhabrot {
|
||||||
self.mode = FractalMode::Buddhabrot;
|
self.mode = FractalMode::Buddhabrot;
|
||||||
}
|
}
|
||||||
if let Some(p) = cli.buddha_palette {
|
|
||||||
self.buddha_palette = p.min(BUDDHA_PALETTE_NAMES.len() as u32 - 1);
|
|
||||||
}
|
|
||||||
if let Some(p) = cli.palette {
|
if let Some(p) = cli.palette {
|
||||||
|
self.buddha_palette = p.min(BUDDHA_PALETTE_NAMES.len() as u32 - 1);
|
||||||
self.palette = p.min(PALETTE_NAMES.len() as u32 - 1);
|
self.palette = p.min(PALETTE_NAMES.len() as u32 - 1);
|
||||||
}
|
}
|
||||||
self.export_path = cli.export_path;
|
self.export_path = cli.export_path;
|
||||||
@@ -562,6 +572,27 @@ impl FractalApp {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply a half_height spec. Used by the native debug env var.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn apply_half_height_spec(&mut self, spec: &str) -> bool {
|
||||||
|
let Some(half_height) = parse_half_height_spec(spec) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
self.view.half_height = half_height;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
/// Apply a view spec "re,im" (re/im are decimal,
|
||||||
|
/// parsed at full precision). Used by the native debug env var.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn apply_re_im_spec(&mut self, spec: &str) -> bool {
|
||||||
|
let Some((re, im)) = parse_re_im_spec(spec, self.view.precision_bits()) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
self.view.center_re = re;
|
||||||
|
self.view.center_im = im;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
/// The current view (center + half-height). Used by headless animation
|
/// The current view (center + half-height). Used by headless animation
|
||||||
/// to snapshot the start of a camera path.
|
/// to snapshot the start of a camera path.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
@@ -586,6 +617,19 @@ impl FractalApp {
|
|||||||
self.auto_iterations = v;
|
self.auto_iterations = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set `max_iterations`.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub(crate) fn set_max_iterations(&mut self, i: u32) {
|
||||||
|
self.auto_iterations = false;
|
||||||
|
self.max_iterations = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get `max_iterations`.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub(crate) fn max_iterations(&mut self) -> u32 {
|
||||||
|
self.max_iterations
|
||||||
|
}
|
||||||
|
|
||||||
/// Jump to a preset Mandelbrot location: decimal center (parsed at the
|
/// Jump to a preset Mandelbrot location: decimal center (parsed at the
|
||||||
/// precision the zoom needs), half-height, and a fitting iteration count.
|
/// precision the zoom needs), half-height, and a fitting iteration count.
|
||||||
fn go_to_place(&mut self, re: &str, im: &str, half_height: f64, iterations: u32) {
|
fn go_to_place(&mut self, re: &str, im: &str, half_height: f64, iterations: u32) {
|
||||||
@@ -1837,7 +1881,7 @@ impl FractalApp {
|
|||||||
self.center_im_edit = big_to_decimal_str(&self.view.center_im, sig);
|
self.center_im_edit = big_to_decimal_str(&self.view.center_im, sig);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.label("magnification (×):");
|
ui.label("zoom:");
|
||||||
let zoom_resp = ui.add(
|
let zoom_resp = ui.add(
|
||||||
egui::TextEdit::singleline(&mut self.zoom_edit)
|
egui::TextEdit::singleline(&mut self.zoom_edit)
|
||||||
.desired_width(f32::INFINITY)
|
.desired_width(f32::INFINITY)
|
||||||
@@ -1848,18 +1892,17 @@ impl FractalApp {
|
|||||||
}
|
}
|
||||||
if zoom_resp.lost_focus() {
|
if zoom_resp.lost_focus() {
|
||||||
if self.zoom_edited
|
if self.zoom_edited
|
||||||
&& let Ok(m) = self.zoom_edit.trim().parse::<f64>()
|
&& let Ok(hh) = self.zoom_edit.trim().parse::<f64>()
|
||||||
|
&& hh > 0.0
|
||||||
|
&& hh.is_finite()
|
||||||
{
|
{
|
||||||
let hh = DEFAULT_HALF_HEIGHT / m;
|
self.view.half_height = hh;
|
||||||
if m > 0.0 && hh > 0.0 && hh.is_finite() {
|
self.view.sync_precision();
|
||||||
self.view.half_height = hh;
|
|
||||||
self.view.sync_precision();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.zoom_edited = false;
|
self.zoom_edited = false;
|
||||||
}
|
}
|
||||||
if !zoom_resp.has_focus() {
|
if !zoom_resp.has_focus() {
|
||||||
self.zoom_edit = format_magnification(self.view.magnification());
|
self.zoom_edit = format_zoom(self.view.zoom());
|
||||||
}
|
}
|
||||||
ui.label(format!("reference: {} pts", self.reference.len()));
|
ui.label(format!("reference: {} pts", self.reference.len()));
|
||||||
ui.label(format!("precision: {} bits", self.view.precision_bits()));
|
ui.label(format!("precision: {} bits", self.view.precision_bits()));
|
||||||
|
|||||||
+26
-14
@@ -13,6 +13,14 @@ pub struct Cli {
|
|||||||
#[arg(long, value_enum)]
|
#[arg(long, value_enum)]
|
||||||
pub kind: Option<KindArg>,
|
pub kind: Option<KindArg>,
|
||||||
|
|
||||||
|
/// Start in Julia mode with this seed constant.
|
||||||
|
#[arg(long, value_name = "RE,IM")]
|
||||||
|
pub julia: Option<String>,
|
||||||
|
|
||||||
|
/// Switch to the Buddhabrot renderer.
|
||||||
|
#[arg(long)]
|
||||||
|
pub buddhabrot: bool,
|
||||||
|
|
||||||
/// Exponent for the Multibrot kind (z -> z^power + c), clamped to [2, 8].
|
/// Exponent for the Multibrot kind (z -> z^power + c), clamped to [2, 8].
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub power: Option<u32>,
|
pub power: Option<u32>,
|
||||||
@@ -21,14 +29,6 @@ pub struct Cli {
|
|||||||
#[arg(long, value_name = "RE,IM")]
|
#[arg(long, value_name = "RE,IM")]
|
||||||
pub complex_power: Option<String>,
|
pub complex_power: Option<String>,
|
||||||
|
|
||||||
/// Start in Julia mode with this seed constant.
|
|
||||||
#[arg(long, value_name = "RE,IM")]
|
|
||||||
pub julia: Option<String>,
|
|
||||||
|
|
||||||
/// Coloring palette index.
|
|
||||||
#[arg(long, value_name = "INDEX")]
|
|
||||||
pub palette: Option<u32>,
|
|
||||||
|
|
||||||
/// Phoenix constant p for the Phoenix kind (z -> z^2 + c + p*z_prev).
|
/// Phoenix constant p for the Phoenix kind (z -> z^2 + c + p*z_prev).
|
||||||
#[arg(long, value_name = "RE,IM")]
|
#[arg(long, value_name = "RE,IM")]
|
||||||
pub phoenix_p: Option<String>,
|
pub phoenix_p: Option<String>,
|
||||||
@@ -45,17 +45,25 @@ pub struct Cli {
|
|||||||
#[arg(long, value_name = "RE,IM,HALF_HEIGHT[,ITERATIONS]")]
|
#[arg(long, value_name = "RE,IM,HALF_HEIGHT[,ITERATIONS]")]
|
||||||
pub view: Option<String>,
|
pub view: Option<String>,
|
||||||
|
|
||||||
|
/// Jump to a specific position on startup.
|
||||||
|
#[arg(long, short('p'), value_name = "RE,IM")]
|
||||||
|
pub position: Option<String>,
|
||||||
|
|
||||||
|
/// Set a maximum iterations count on startup.
|
||||||
|
#[arg(long, short('i'))]
|
||||||
|
pub iterations: Option<u32>,
|
||||||
|
|
||||||
|
/// Set the zoom level on startup.
|
||||||
|
#[arg(long("zoom"), short('z'))]
|
||||||
|
pub half_height: Option<String>,
|
||||||
|
|
||||||
/// Enable distance-estimation shading.
|
/// Enable distance-estimation shading.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub de: bool,
|
pub de: bool,
|
||||||
|
|
||||||
/// Switch to the Buddhabrot renderer.
|
/// Coloring palette index.
|
||||||
#[arg(long)]
|
|
||||||
pub buddhabrot: bool,
|
|
||||||
|
|
||||||
/// Buddhabrot tonemap palette index.
|
|
||||||
#[arg(long, value_name = "INDEX")]
|
#[arg(long, value_name = "INDEX")]
|
||||||
pub buddha_palette: Option<u32>,
|
pub palette: Option<u32>,
|
||||||
|
|
||||||
/// Output path for --headless (default: fractal-<timestamp>.png). When
|
/// Output path for --headless (default: fractal-<timestamp>.png). When
|
||||||
/// animating (--to-view/--to-share), this is a directory of
|
/// animating (--to-view/--to-share), this is a directory of
|
||||||
@@ -77,6 +85,10 @@ pub struct Cli {
|
|||||||
#[arg(long, value_name = "FRAGMENT")]
|
#[arg(long, value_name = "FRAGMENT")]
|
||||||
pub to_share: Option<String>,
|
pub to_share: Option<String>,
|
||||||
|
|
||||||
|
/// Set a maximum iterations count at animation end.
|
||||||
|
#[arg(long)]
|
||||||
|
pub to_iterations: Option<u32>,
|
||||||
|
|
||||||
/// Number of frames to render for an animation. Alternative to --fps +
|
/// Number of frames to render for an animation. Alternative to --fps +
|
||||||
/// --duration.
|
/// --duration.
|
||||||
#[arg(long, value_name = "N")]
|
#[arg(long, value_name = "N")]
|
||||||
|
|||||||
+29
-10
@@ -11,7 +11,8 @@ use crate::app::{FractalApp, unix_timestamp};
|
|||||||
use crate::cli::Cli;
|
use crate::cli::Cli;
|
||||||
use crate::fractal::{ExportRender, FractalRenderer, ShareState, export_to_png_blocking};
|
use crate::fractal::{ExportRender, FractalRenderer, ShareState, export_to_png_blocking};
|
||||||
use crate::view::{
|
use crate::view::{
|
||||||
ViewState, big_from_decimal_str, interpolate_view, parse_view_spec, precision_for,
|
ViewState, big_from_decimal_str, interpolate_f64, interpolate_view, parse_view_spec,
|
||||||
|
precision_for,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Cap on the output image dimension (px), to stay within GPU texture limits.
|
/// Cap on the output image dimension (px), to stay within GPU texture limits.
|
||||||
@@ -29,6 +30,7 @@ pub fn run(cli: Cli) -> Result<(), String> {
|
|||||||
// consumes `cli` to build the start state.
|
// consumes `cli` to build the start state.
|
||||||
let to_view = cli.to_view.clone();
|
let to_view = cli.to_view.clone();
|
||||||
let to_share = cli.to_share.clone();
|
let to_share = cli.to_share.clone();
|
||||||
|
let to_iterations = cli.to_iterations;
|
||||||
let frames_arg = cli.frames;
|
let frames_arg = cli.frames;
|
||||||
let fps = cli.fps;
|
let fps = cli.fps;
|
||||||
let duration = cli.duration;
|
let duration = cli.duration;
|
||||||
@@ -43,6 +45,7 @@ pub fn run(cli: Cli) -> Result<(), String> {
|
|||||||
app,
|
app,
|
||||||
to_view,
|
to_view,
|
||||||
to_share,
|
to_share,
|
||||||
|
to_iterations,
|
||||||
frames_arg,
|
frames_arg,
|
||||||
fps,
|
fps,
|
||||||
duration,
|
duration,
|
||||||
@@ -97,6 +100,7 @@ fn run_animation(
|
|||||||
mut app: FractalApp,
|
mut app: FractalApp,
|
||||||
to_view: Option<String>,
|
to_view: Option<String>,
|
||||||
to_share: Option<String>,
|
to_share: Option<String>,
|
||||||
|
mut to_iterations: Option<u32>,
|
||||||
frames_arg: Option<u32>,
|
frames_arg: Option<u32>,
|
||||||
fps: f64,
|
fps: f64,
|
||||||
duration: Option<f64>,
|
duration: Option<f64>,
|
||||||
@@ -116,11 +120,20 @@ fn run_animation(
|
|||||||
return Err("animation needs at least 2 frames".into());
|
return Err("animation needs at least 2 frames".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let to = parse_animation_target(to_view.as_deref(), to_share.as_deref())?;
|
let (to, to_iterations_share) =
|
||||||
|
parse_animation_target(to_view.as_deref(), to_share.as_deref())?;
|
||||||
|
if to_iterations.is_none()
|
||||||
|
&& let Some(to_iterations_share) = to_iterations_share
|
||||||
|
{
|
||||||
|
to_iterations = Some(to_iterations_share);
|
||||||
|
}
|
||||||
let from = app.view_state().clone();
|
let from = app.view_state().clone();
|
||||||
// Iteration count auto-scales with zoom depth per frame, the same way it
|
let from_iterations = app.max_iterations();
|
||||||
// does while zooming interactively — no need to interpolate it by hand.
|
if to_iterations.is_none() {
|
||||||
app.set_auto_iterations(true);
|
// Iteration count auto-scales with zoom depth per frame, the same way it
|
||||||
|
// does while zooming interactively — no need to interpolate it by hand.
|
||||||
|
app.set_auto_iterations(true);
|
||||||
|
}
|
||||||
|
|
||||||
let out_dir = export_path.unwrap_or_else(|| format!("frames-{}", unix_timestamp()));
|
let out_dir = export_path.unwrap_or_else(|| format!("frames-{}", unix_timestamp()));
|
||||||
std::fs::create_dir_all(&out_dir).map_err(|e| format!("failed to create {out_dir}: {e}"))?;
|
std::fs::create_dir_all(&out_dir).map_err(|e| format!("failed to create {out_dir}: {e}"))?;
|
||||||
@@ -133,6 +146,11 @@ fn run_animation(
|
|||||||
for i in 0..frames {
|
for i in 0..frames {
|
||||||
let raw_t = i as f64 / (frames - 1) as f64;
|
let raw_t = i as f64 / (frames - 1) as f64;
|
||||||
let t = if linear { raw_t } else { smoothstep(raw_t) };
|
let t = if linear { raw_t } else { smoothstep(raw_t) };
|
||||||
|
if let Some(to) = to_iterations {
|
||||||
|
app.set_max_iterations(
|
||||||
|
interpolate_f64(from_iterations as f64, to as f64, t).round() as u32,
|
||||||
|
);
|
||||||
|
}
|
||||||
app.set_view(interpolate_view(&from, &to, t));
|
app.set_view(interpolate_view(&from, &to, t));
|
||||||
|
|
||||||
eprintln!("[{:>4}/{frames}] computing reference orbit…", i + 1);
|
eprintln!("[{:>4}/{frames}] computing reference orbit…", i + 1);
|
||||||
@@ -180,11 +198,9 @@ fn run_animation(
|
|||||||
fn parse_animation_target(
|
fn parse_animation_target(
|
||||||
to_view: Option<&str>,
|
to_view: Option<&str>,
|
||||||
to_share: Option<&str>,
|
to_share: Option<&str>,
|
||||||
) -> Result<ViewState, String> {
|
) -> Result<(ViewState, Option<u32>), String> {
|
||||||
if let Some(spec) = to_view {
|
if let Some(spec) = to_view {
|
||||||
return parse_view_spec(spec)
|
return parse_view_spec(spec).ok_or_else(|| format!("invalid --to-view spec: {spec}"));
|
||||||
.map(|(view, _)| view)
|
|
||||||
.ok_or_else(|| format!("invalid --to-view spec: {spec}"));
|
|
||||||
}
|
}
|
||||||
let frag = to_share.expect("run_animation only called with one of to_view/to_share set");
|
let frag = to_share.expect("run_animation only called with one of to_view/to_share set");
|
||||||
let state =
|
let state =
|
||||||
@@ -194,7 +210,10 @@ fn parse_animation_target(
|
|||||||
big_from_decimal_str(&state.center_re, bits).ok_or("invalid --to-share center (re)")?;
|
big_from_decimal_str(&state.center_re, bits).ok_or("invalid --to-share center (re)")?;
|
||||||
let im =
|
let im =
|
||||||
big_from_decimal_str(&state.center_im, bits).ok_or("invalid --to-share center (im)")?;
|
big_from_decimal_str(&state.center_im, bits).ok_or("invalid --to-share center (im)")?;
|
||||||
Ok(ViewState::with_center(re, im, state.half_height))
|
Ok((
|
||||||
|
ViewState::with_center(re, im, state.half_height),
|
||||||
|
Some(state.iterations),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ease-in/ease-out pacing: slow at both ends, fast through the middle.
|
/// Ease-in/ease-out pacing: slow at both ends, fast through the middle.
|
||||||
|
|||||||
+33
@@ -58,6 +58,11 @@ impl ViewState {
|
|||||||
DEFAULT_HALF_HEIGHT / self.half_height
|
DEFAULT_HALF_HEIGHT / self.half_height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Current zoom level.
|
||||||
|
pub fn zoom(&self) -> f64 {
|
||||||
|
self.half_height
|
||||||
|
}
|
||||||
|
|
||||||
/// Bits of precision the center currently needs for this zoom level.
|
/// Bits of precision the center currently needs for this zoom level.
|
||||||
pub fn precision_bits(&self) -> usize {
|
pub fn precision_bits(&self) -> usize {
|
||||||
precision_for(self.half_height)
|
precision_for(self.half_height)
|
||||||
@@ -140,6 +145,30 @@ pub fn parse_view_spec(spec: &str) -> Option<(ViewState, Option<u32>)> {
|
|||||||
Some((ViewState::with_center(re, im, half_height), iterations))
|
Some((ViewState::with_center(re, im, half_height), iterations))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a half_height spec. Shared by
|
||||||
|
/// `FractalApp::apply_half_height_spec` (the `--zoom` CLI flag) and headless
|
||||||
|
/// animation's `--to-zoom`.
|
||||||
|
pub fn parse_half_height_spec(spec: &str) -> Option<f64> {
|
||||||
|
let half_height = spec.trim().parse::<f64>().ok()?;
|
||||||
|
if !(half_height > 0.0 && half_height.is_finite()) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(half_height)
|
||||||
|
}
|
||||||
|
/// Parse a "re,im" spec (re/im decimal, parsed at
|
||||||
|
/// full precision) into a view. Shared by
|
||||||
|
/// `FractalApp::apply_re_im_spec` (the `--position` CLI flag) and headless
|
||||||
|
/// animation's `--to-position`.
|
||||||
|
pub fn parse_re_im_spec(spec: &str, bits: usize) -> Option<(Big, Big)> {
|
||||||
|
let parts: Vec<&str> = spec.split(',').collect();
|
||||||
|
if parts.len() != 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let re = big_from_decimal_str(parts[0], bits)?;
|
||||||
|
let im = big_from_decimal_str(parts[1], bits)?;
|
||||||
|
Some((re, im))
|
||||||
|
}
|
||||||
|
|
||||||
/// Interpolate between two views for an animation frame, `t` in `[0, 1]`.
|
/// Interpolate between two views for an animation frame, `t` in `[0, 1]`.
|
||||||
/// The half-height interpolates geometrically (log-linear), since zoom depth
|
/// The half-height interpolates geometrically (log-linear), since zoom depth
|
||||||
/// spans many decades and a linear sweep would crawl at the start and blow
|
/// spans many decades and a linear sweep would crawl at the start and blow
|
||||||
@@ -172,6 +201,10 @@ pub fn interpolate_view(from: &ViewState, to: &ViewState, t: f64) -> ViewState {
|
|||||||
ViewState::with_center(center_re, center_im, half_height)
|
ViewState::with_center(center_re, center_im, half_height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn interpolate_f64(from: f64, to: f64, t: f64) -> f64 {
|
||||||
|
from + (to - from) * t
|
||||||
|
}
|
||||||
|
|
||||||
/// Render a `Big` as a decimal string with `sig_digits` significant digits.
|
/// Render a `Big` as a decimal string with `sig_digits` significant digits.
|
||||||
pub fn big_to_decimal_str(x: &Big, sig_digits: usize) -> String {
|
pub fn big_to_decimal_str(x: &Big, sig_digits: usize) -> String {
|
||||||
let dec = x
|
let dec = x
|
||||||
|
|||||||
Reference in New Issue
Block a user