add parseFraction to Rational struct (round trip toFractionString)
This commit is contained in:
parent
bcdf36bd48
commit
d36cf386ac
1 changed files with 79 additions and 0 deletions
|
|
@ -622,6 +622,45 @@ pub fn toFractionString(self: Rational, allocator: Allocator) Error![]u8 {
|
|||
Error.OutOfMemory;
|
||||
}
|
||||
|
||||
/// The inverse of `toFractionString`, at any size: `"-781250/12573"`, `"7"`.
|
||||
///
|
||||
/// `parse` caps literals at 512 digits, which is right for what a person types and
|
||||
/// wrong for a value the engine produced itself: `factorial(1000)` has 2,568 digits and
|
||||
/// has to come back exactly when a saved session is loaded. Only the canonical form is
|
||||
/// accepted - an optional minus, digits, and optionally a slash and more digits - and
|
||||
/// the result is reduced, so an unreduced fraction is still read as the right value.
|
||||
pub fn parseFraction(allocator: Allocator, text: []const u8) Error!Rational {
|
||||
const slash = std.mem.indexOfScalar(u8, text, '/');
|
||||
const num_text = if (slash) |s| text[0..s] else text;
|
||||
const den_text = if (slash) |s| text[s + 1 ..] else "1";
|
||||
const magnitude = if (num_text.len > 0 and num_text[0] == '-') num_text[1..] else num_text;
|
||||
if (!allDigits(magnitude) or !allDigits(den_text)) return Error.InvalidNumber;
|
||||
|
||||
var num = try Managed.init(allocator);
|
||||
errdefer num.deinit();
|
||||
try setDecimal(&num, num_text);
|
||||
var den = try Managed.init(allocator);
|
||||
errdefer den.deinit();
|
||||
try setDecimal(&den, den_text);
|
||||
return finish(allocator, &num, &den);
|
||||
}
|
||||
|
||||
fn allDigits(text: []const u8) bool {
|
||||
if (text.len == 0) return false;
|
||||
for (text) |c| {
|
||||
if (!std.ascii.isDigit(c)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn setDecimal(value: *Managed, text: []const u8) Error!void {
|
||||
value.setString(10, text) catch |err| return switch (err) {
|
||||
error.OutOfMemory => Error.OutOfMemory,
|
||||
// Unreachable after `allDigits`, and reported rather than asserted.
|
||||
else => Error.InvalidNumber,
|
||||
};
|
||||
}
|
||||
|
||||
pub const DecimalResult = struct {
|
||||
/// Decimal text. Caller owns the memory.
|
||||
text: []u8,
|
||||
|
|
@ -2124,3 +2163,43 @@ fn bodyScientificRendering(a: Allocator) anyerror!void {
|
|||
test "OOM safety: scientific rendering" {
|
||||
try oomSweep(bodyScientificRendering);
|
||||
}
|
||||
|
||||
// -- parseFraction --
|
||||
|
||||
test "parseFraction reads back what toFractionString writes, at any size" {
|
||||
for ([_][]const u8{ "0", "7", "-7", "1/3", "-1/3", "781250/12573" }) |text| {
|
||||
var r = try parseFraction(alloc, text);
|
||||
defer r.deinit();
|
||||
try expectFrac(text, r);
|
||||
}
|
||||
|
||||
// Far past `parse`'s 512-digit limit: a value the engine can produce must load.
|
||||
const digits = "9" ** 3000;
|
||||
// Over 2, because 10^3000 - 1 is odd: over 7 it would reduce, since 7 divides it.
|
||||
var big = try parseFraction(alloc, digits ++ "/2");
|
||||
defer big.deinit();
|
||||
const back = try big.toFractionString(alloc);
|
||||
defer alloc.free(back);
|
||||
try testing.expectEqualStrings(digits ++ "/2", back);
|
||||
try testing.expectError(Error.TooManyDigits, parse(alloc, digits));
|
||||
}
|
||||
|
||||
test "parseFraction reduces, and refuses anything but the canonical form" {
|
||||
var half = try parseFraction(alloc, "2/4");
|
||||
defer half.deinit();
|
||||
try expectFrac("1/2", half);
|
||||
|
||||
try testing.expectError(Error.DivisionByZero, parseFraction(alloc, "1/0"));
|
||||
for ([_][]const u8{ "", "-", "1/", "/2", "1/-2", "+1", "1.5", "1e3", "1 / 2", "0x10", "--1" }) |text| {
|
||||
try testing.expectError(Error.InvalidNumber, parseFraction(alloc, text));
|
||||
}
|
||||
}
|
||||
|
||||
test "parseFraction releases everything on any allocation failure" {
|
||||
try testing.checkAllAllocationFailures(testing.allocator, struct {
|
||||
fn run(a: Allocator) !void {
|
||||
var r = try parseFraction(a, "-123456789012345678901234567890/98765432109876543210");
|
||||
r.deinit();
|
||||
}
|
||||
}.run, .{});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue