Skip to content

Commit 1b17eb8

Browse files
committed
support unlimited obase
1 parent 129ea52 commit 1b17eb8

File tree

7 files changed

+67
-21
lines changed

7 files changed

+67
-21
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "dc4"
3-
version = "2.1.2"
3+
version = "2.2.0"
44
authors = ["William R. Fraser <[email protected]>"]
55
license = "MIT/Apache-2.0"
66
description = "a Unix 'dc' implementation in Rust"

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ linked instead of dynamically linked.
3434
Some behaviors have been intentionally changed from GNU dc:
3535

3636
- running shell commands with the '!' command is not supported
37-
- the output radix ('o') is limited to between 2 and 16, inclusive. GNU dc
38-
allows >16, for which it uses a different, incompatible, output format.
3937

4038
Any other differences (other than cases where GNU dc crashes and dc4 does not)
4139
should be considered a bug.

src/big_real.rs

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// BigReal :: An arbitrary-precision real number class.
33
//
4-
// Copyright (c) 2016-2024 by William R. Fraser
4+
// Copyright (c) 2016-2025 by William R. Fraser
55
//
66

77
use std::cmp::{max, Ordering};
@@ -65,9 +65,8 @@ impl BigReal {
6565

6666
pub fn to_str_radix(&self, radix: u32) -> String {
6767
if self.shift == 0 {
68-
self.value.to_str_radix(radix)
69-
}
70-
else if radix == 10 {
68+
Self::int_str_radix(&self.value, radix)
69+
} else if radix == 10 {
7170
// For decimal, it's fine to just put the dot in the right place.
7271
let mut output = if self.is_negative() {
7372
"-".to_string()
@@ -91,8 +90,7 @@ impl BigReal {
9190
output.push_str(&digits[decimal_pos..]);
9291
}
9392
output
94-
}
95-
else {
93+
} else {
9694
// For non-decimal, the whole part is fine, but the string representation of the
9795
// fractional part needs to be computed manually using long division.
9896

@@ -105,7 +103,7 @@ impl BigReal {
105103
let whole = self.change_shift(0).abs();
106104

107105
if !whole.value.is_zero() { // suppress leading zero
108-
string_result.push_str(&whole.value.to_str_radix(radix));
106+
string_result.push_str(&Self::int_str_radix(&whole.value, radix));
109107
}
110108
string_result.push('.');
111109

@@ -120,10 +118,15 @@ impl BigReal {
120118
let mut place = BigInt::from(radix);
121119

122120
loop {
123-
let div_rem = part.div_rem(&max_place);
121+
let (div, rem) = part.div_rem(&max_place);
124122

125-
string_result.push_str(&div_rem.0.to_str_radix(radix));
126-
part = div_rem.1 * radix;
123+
let s = Self::int_str_radix(&div, radix);
124+
if radix > 16 && string_result.ends_with('.') {
125+
string_result.push_str(&s[1..]);
126+
} else {
127+
string_result.push_str(&s);
128+
}
129+
part = rem * radix;
127130

128131
// check if we've reached the appropriate precision
129132
if place >= max_place {
@@ -136,6 +139,43 @@ impl BigReal {
136139
}
137140
}
138141

142+
fn int_str_radix(n: &BigInt, radix: u32) -> String {
143+
if radix <= 16 {
144+
return n.to_str_radix(radix);
145+
}
146+
147+
// For radix >16, it prints each place as a base-10 number, separated by spaces.
148+
// For radix 100 each place gets padded to 2 digits, radix 101 gets 3 digits, etc.
149+
let digit_width = (radix - 1).ilog10() as usize + 1;
150+
151+
let mut power = BigInt::from(radix);
152+
if n <= &power {
153+
// Shortcut: if we're already less than the radix, just base-10 and pad it and be done.
154+
return format!(" {:0>digit_width$}", n.to_str_radix(10));
155+
}
156+
157+
// find the power of radix to start with
158+
let mut powers = vec![BigInt::one()];
159+
while &power < n {
160+
powers.push(power.clone());
161+
power *= radix;
162+
}
163+
164+
let mut out = String::new();
165+
let mut n = n.to_owned();
166+
while let Some(power) = powers.pop() {
167+
let (div, rem) = n.div_rem(&power);
168+
let s = div.to_str_radix(10);
169+
out.push(' ');
170+
for _ in s.len() .. digit_width {
171+
out.push('0');
172+
}
173+
out.push_str(&s);
174+
n = rem;
175+
}
176+
out
177+
}
178+
139179
pub fn pow(&self, exponent: &BigReal, scale: u32) -> BigReal {
140180
let negative = exponent.is_negative();
141181

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// This is the program entry point.
55
// It parses command line arguments and invokes the dc4 library.
66
//
7-
// Copyright (c) 2015-2024 by William R. Fraser
7+
// Copyright (c) 2015-2025 by William R. Fraser
88
//
99

1010
#![deny(rust_2018_idioms)]

src/state.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -368,14 +368,9 @@ impl Dc4State {
368368
}
369369
}
370370
Action::SetOutputRadix => match self.pop_top()? {
371-
// BigInt::to_str_radix actually supports radix up to 36, but we restrict it to 16
372-
// here because those are the only values that will round-trip (because only
373-
// 'A'...'F' will be interpreted as numbers.
374-
// On the other hand, actual dc supports unlimited output radix, but after 16 it
375-
// starts to use a different format.
376371
DcValue::Num(n) => {
377372
match n.to_u32() {
378-
Some(radix) if (2..=16).contains(&radix) => {
373+
Some(radix) if radix > 1 => {
379374
self.oradix = radix;
380375
}
381376
Some(_) | None => {

tests/testlib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,3 +475,16 @@ fn test_zero_print() {
475475
fn test_obase_neg_frac() {
476476
assert_eq!(dc4_run(b"_1.5 16of"), "-1.8\n");
477477
}
478+
479+
#[test]
480+
fn test_large_obase() {
481+
assert_eq!(dc4_run(b"1 100of"), " 01\n");
482+
assert_eq!(dc4_run(b"1 101of"), " 001\n");
483+
assert_eq!(dc4_run(b"1024 64of"), " 16 00\n");
484+
assert_eq!(dc4_run(b"5120 64of"), " 01 16 00\n");
485+
assert_eq!(dc4_run(b"123456 101of"), " 012 010 034\n");
486+
assert_eq!(dc4_run(b"123456.789 101of"), " 012 010 034.079 069\n");
487+
assert_eq!(dc4_run(b"_123456.789 16of"), "-1E240.C9F\n");
488+
assert_eq!(dc4_run(b"_123456.789 101of"), "- 012 010 034.079 069\n");
489+
assert_eq!(dc4_run(b"0.789 101of"), ".079 069\n");
490+
}

0 commit comments

Comments
 (0)