mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 06:14:46 +00:00
refactor decimal toStr
This commit is contained in:
parent
cb42f0c039
commit
bc88fc3880
1 changed files with 76 additions and 80 deletions
|
@ -12,7 +12,6 @@ pub const RocDec = extern struct {
|
||||||
pub const whole_number_places: u5 = 21;
|
pub const whole_number_places: u5 = 21;
|
||||||
const max_digits: u6 = 39;
|
const max_digits: u6 = 39;
|
||||||
const max_str_length: u6 = max_digits + 2; // + 2 here to account for the sign & decimal dot
|
const max_str_length: u6 = max_digits + 2; // + 2 here to account for the sign & decimal dot
|
||||||
const leading_zeros: [17]u8 = "00000000000000000".*;
|
|
||||||
|
|
||||||
pub const min: RocDec = .{ .num = math.minInt(i128) };
|
pub const min: RocDec = .{ .num = math.minInt(i128) };
|
||||||
pub const max: RocDec = .{ .num = math.maxInt(i128) };
|
pub const max: RocDec = .{ .num = math.maxInt(i128) };
|
||||||
|
@ -130,101 +129,76 @@ pub const RocDec = extern struct {
|
||||||
return RocStr.init("0.0", 3);
|
return RocStr.init("0.0", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this Dec is negative, and if so convert to positive
|
const num = self.num;
|
||||||
// We will handle adding the '-' later
|
const is_negative = num < 0;
|
||||||
const is_negative = self.num < 0;
|
|
||||||
const num = if (is_negative) std.math.negate(self.num) catch {
|
|
||||||
@panic("TODO runtime exception failing to negate");
|
|
||||||
} else self.num;
|
|
||||||
|
|
||||||
// Format the backing i128 into an array of digits (u8s)
|
// Format the backing i128 into an array of digit (ascii) characters (u8s)
|
||||||
var digit_bytes: [max_digits + 1]u8 = undefined;
|
var digit_bytes_storage: [max_digits + 1]u8 = undefined;
|
||||||
var num_digits = std.fmt.formatIntBuf(digit_bytes[0..], num, 10, false, .{});
|
var num_digits = std.fmt.formatIntBuf(digit_bytes_storage[0..], num, 10, false, .{});
|
||||||
|
var digit_bytes: [*]u8 = digit_bytes_storage[0..];
|
||||||
|
|
||||||
|
// space where we assemble all the characters that make up the final string
|
||||||
|
var str_bytes: [max_str_length]u8 = undefined;
|
||||||
|
var position: usize = 0;
|
||||||
|
|
||||||
|
// if negative, the first character is a negating minus
|
||||||
|
if (is_negative) {
|
||||||
|
str_bytes[position] = '-';
|
||||||
|
position += 1;
|
||||||
|
|
||||||
|
// but also, we have one fewer digit than we have characters
|
||||||
|
num_digits -= 1;
|
||||||
|
|
||||||
|
// and we drop the minus to make later arithmetic correct
|
||||||
|
digit_bytes += 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the slice for before the decimal point
|
// Get the slice for before the decimal point
|
||||||
var before_digits_slice: []const u8 = undefined;
|
|
||||||
var before_digits_offset: usize = 0;
|
var before_digits_offset: usize = 0;
|
||||||
var before_digits_adjust: u6 = 0;
|
|
||||||
if (num_digits > decimal_places) {
|
if (num_digits > decimal_places) {
|
||||||
|
// we have more digits than fit after the decimal point,
|
||||||
|
// so we must have digits before the decimal point
|
||||||
before_digits_offset = num_digits - decimal_places;
|
before_digits_offset = num_digits - decimal_places;
|
||||||
before_digits_slice = digit_bytes[0..before_digits_offset];
|
|
||||||
|
for (digit_bytes[0..before_digits_offset]) |c| {
|
||||||
|
str_bytes[position] = c;
|
||||||
|
position += 1;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
before_digits_adjust = @intCast(u6, math.absInt(@intCast(i7, num_digits) - decimal_places) catch {
|
// otherwise there are no actual digits before the decimal point
|
||||||
@panic("TODO runtime exception for overflow when getting abs");
|
// but we format it with a '0'
|
||||||
});
|
str_bytes[position] = '0';
|
||||||
before_digits_slice = "0";
|
position += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Figure out how many trailing zeros there are
|
// we've done everything before the decimal point, so now we can put the decimal point in
|
||||||
// I tried to use https://ziglang.org/documentation/0.8.0/#ctz and it mostly worked,
|
str_bytes[position] = '.';
|
||||||
// but was giving seemingly incorrect values for certain numbers. So instead we use
|
position += 1;
|
||||||
// a while loop and figure it out that way.
|
|
||||||
//
|
|
||||||
// const trailing_zeros = @ctz(u6, num);
|
|
||||||
//
|
|
||||||
var trailing_zeros: u6 = 0;
|
|
||||||
var index = decimal_places - 1 - before_digits_adjust;
|
|
||||||
var is_consecutive_zero = true;
|
|
||||||
while (index != 0) {
|
|
||||||
var digit = digit_bytes[before_digits_offset + index];
|
|
||||||
if (digit == '0' and is_consecutive_zero) {
|
|
||||||
trailing_zeros += 1;
|
|
||||||
} else {
|
|
||||||
is_consecutive_zero = false;
|
|
||||||
}
|
|
||||||
index -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const trailing_zeros: u6 = count_trailing_zeros_base10(num);
|
||||||
|
if (trailing_zeros == decimal_places) {
|
||||||
|
// add just a single zero if all decimal digits are zero
|
||||||
|
str_bytes[position] = '0';
|
||||||
|
position += 1;
|
||||||
|
} else {
|
||||||
// Figure out if we need to prepend any zeros to the after decimal point
|
// Figure out if we need to prepend any zeros to the after decimal point
|
||||||
// For example, for the number 0.000123 we need to prepend 3 zeros after the decimal point
|
// For example, for the number 0.000123 we need to prepend 3 zeros after the decimal point
|
||||||
// This will only be needed for numbers less 0.01, otherwise after_digits_slice will handle this
|
|
||||||
const after_zeros_num = if (num_digits < decimal_places) decimal_places - num_digits else 0;
|
const after_zeros_num = if (num_digits < decimal_places) decimal_places - num_digits else 0;
|
||||||
const after_zeros_slice: []const u8 = leading_zeros[0..after_zeros_num];
|
|
||||||
|
|
||||||
// Get the slice for after the decimal point
|
|
||||||
var after_digits_slice: []const u8 = undefined;
|
|
||||||
if ((num_digits - before_digits_offset) == trailing_zeros) {
|
|
||||||
after_digits_slice = "0";
|
|
||||||
} else {
|
|
||||||
after_digits_slice = digit_bytes[before_digits_offset .. num_digits - trailing_zeros];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the slice for the sign
|
|
||||||
const sign_slice: []const u8 = if (is_negative) "-" else leading_zeros[0..0];
|
|
||||||
|
|
||||||
// Hardcode adding a `1` for the '.' character
|
|
||||||
const str_len: usize = sign_slice.len + before_digits_slice.len + 1 + after_zeros_slice.len + after_digits_slice.len;
|
|
||||||
|
|
||||||
// Join the slices together
|
|
||||||
// Ideally, we'd use str_len here, but the array length needs to be comptime (unless we want to pass in an allocator here & use that)
|
|
||||||
var str_bytes: [max_str_length]u8 = undefined;
|
|
||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
|
while (i < after_zeros_num) : (i += 1) {
|
||||||
for (sign_slice) |c| {
|
str_bytes[position] = '0';
|
||||||
str_bytes[i] = c;
|
position += 1;
|
||||||
i += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (before_digits_slice) |c| {
|
// otherwise append the decimal digits except the trailing zeros
|
||||||
str_bytes[i] = c;
|
for (digit_bytes[before_digits_offset .. num_digits - trailing_zeros]) |c| {
|
||||||
i += 1;
|
str_bytes[position] = c;
|
||||||
|
position += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
str_bytes[i] = '.';
|
return RocStr.init(&str_bytes, position);
|
||||||
i += 1;
|
|
||||||
|
|
||||||
for (after_zeros_slice) |c| {
|
|
||||||
str_bytes[i] = c;
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (after_digits_slice) |c| {
|
|
||||||
str_bytes[i] = c;
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return RocStr.init(&str_bytes, str_len);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eq(self: RocDec, other: RocDec) bool {
|
pub fn eq(self: RocDec, other: RocDec) bool {
|
||||||
|
@ -369,6 +343,28 @@ pub const RocDec = extern struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A number has `k` trailling zeros if `10^k` divides into it cleanly
|
||||||
|
inline fn count_trailing_zeros_base10(input: i128) u6 {
|
||||||
|
if (input == 0) {
|
||||||
|
// this should not happen in practice
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var count: u6 = 0;
|
||||||
|
var k: i128 = 1;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (@mod(input, std.math.pow(i128, 10, k)) == 0) {
|
||||||
|
count += 1;
|
||||||
|
k += 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
const U256 = struct {
|
const U256 = struct {
|
||||||
hi: u128,
|
hi: u128,
|
||||||
lo: u128,
|
lo: u128,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue