mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 14:24:45 +00:00
462 lines
13 KiB
Zig
462 lines
13 KiB
Zig
const std = @import("std");
|
|
const str = @import("str.zig");
|
|
|
|
const math = std.math;
|
|
const RocStr = str.RocStr;
|
|
|
|
pub const RocDec = struct {
|
|
num: i128,
|
|
|
|
pub const decimal_places: comptime u32 = 18;
|
|
|
|
pub const min: comptime RocDec = .{ .num = math.minInt(i128) };
|
|
pub const max: comptime RocDec = .{ .num = math.maxInt(i128) };
|
|
|
|
pub const one_point_zero_i128: comptime i128 = comptime math.pow(i128, 10, RocDec.decimal_places);
|
|
pub const one_point_zero: comptime RocDec = .{ .num = one_point_zero_i128 };
|
|
|
|
pub fn fromU64(num: u64) RocDec {
|
|
return .{ .num = num * one_point_zero_i128 };
|
|
}
|
|
|
|
pub fn fromString(roc_str: RocStr) ?RocDec {
|
|
if (roc_str.isEmpty()) {
|
|
return null;
|
|
}
|
|
|
|
const length = roc_str.len();
|
|
|
|
const roc_str_slice = roc_str.asSlice();
|
|
|
|
var is_negative: bool = roc_str_slice[0] == '-';
|
|
var initial_index: usize = if (is_negative) 1 else 0;
|
|
|
|
var point_index: ?usize = null;
|
|
var index: usize = initial_index;
|
|
while (index < length) {
|
|
var byte: u8 = roc_str_slice[index];
|
|
if (byte == '.' and point_index == null) {
|
|
point_index = index;
|
|
index += 1;
|
|
continue;
|
|
}
|
|
|
|
if (!isDigit(byte)) {
|
|
return null;
|
|
}
|
|
index += 1;
|
|
}
|
|
|
|
var before_str_length = length;
|
|
var after_val_i128: ?i128 = null;
|
|
if (point_index) |pi| {
|
|
before_str_length = pi;
|
|
|
|
var after_str_len = (length - 1) - pi;
|
|
if (after_str_len > decimal_places) {
|
|
std.debug.panic("TODO runtime exception for too many decimal places!", .{});
|
|
}
|
|
var diff_decimal_places = decimal_places - after_str_len;
|
|
|
|
var after_str = roc_str_slice[pi + 1 .. length];
|
|
var after_u64 = std.fmt.parseUnsigned(u64, after_str, 10) catch null;
|
|
after_val_i128 = if (after_u64) |f| @intCast(i128, f) * math.pow(i128, 10, diff_decimal_places) else null;
|
|
}
|
|
|
|
var before_str = roc_str_slice[initial_index..before_str_length];
|
|
var before_val_not_adjusted = std.fmt.parseUnsigned(i128, before_str, 10) catch null;
|
|
|
|
var before_val_i128: ?i128 = null;
|
|
if (before_val_not_adjusted) |before| {
|
|
var result: i128 = undefined;
|
|
var overflowed = @mulWithOverflow(i128, before, one_point_zero_i128, &result);
|
|
if (overflowed) {
|
|
std.debug.panic("TODO runtime exception for overflow!", .{});
|
|
}
|
|
before_val_i128 = result;
|
|
}
|
|
|
|
var dec: ?RocDec = null;
|
|
if (before_val_i128) |before| {
|
|
if (after_val_i128) |after| {
|
|
var result: i128 = undefined;
|
|
var overflowed = @addWithOverflow(i128, before, after, &result);
|
|
if (overflowed) {
|
|
std.debug.panic("TODO runtime exception for overflow!", .{});
|
|
}
|
|
dec = .{ .num = result };
|
|
} else {
|
|
dec = .{ .num = before };
|
|
}
|
|
} else if (after_val_i128) |after| {
|
|
dec = .{ .num = after };
|
|
}
|
|
|
|
if (dec) |d| {
|
|
if (is_negative) {
|
|
dec = d.negate();
|
|
}
|
|
}
|
|
|
|
return dec;
|
|
}
|
|
|
|
fn isDigit(c: u8) bool {
|
|
return switch (c) {
|
|
'0'...'9' => true,
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
pub fn negate(self: RocDec) ?RocDec {
|
|
var negated = math.negate(self.num) catch null;
|
|
return if (negated) |n| .{ .num = n } else null;
|
|
}
|
|
|
|
pub fn add(self: RocDec, other: RocDec) RocDec {
|
|
var answer: i128 = undefined;
|
|
const overflowed = @addWithOverflow(i128, self.num, other.num, &answer);
|
|
|
|
if (!overflowed) {
|
|
return RocDec{ .num = answer };
|
|
} else {
|
|
std.debug.panic("TODO runtime exception for overflow!", .{});
|
|
}
|
|
}
|
|
|
|
pub fn mul(self: RocDec, other: RocDec) RocDec {
|
|
const self_i128 = self.num;
|
|
const other_i128 = other.num;
|
|
// const answer = 0; //self_i256 * other_i256;
|
|
|
|
const is_answer_negative = (self_i128 < 0) != (other_i128 < 0);
|
|
|
|
const self_u128 = @intCast(u128, math.absInt(self_i128) catch {
|
|
if (other_i128 == 0) {
|
|
return .{ .num = 0 };
|
|
} else if (other_i128 == RocDec.one_point_zero.num) {
|
|
return self;
|
|
} else {
|
|
std.debug.panic("TODO runtime exception for overflow!", .{});
|
|
}
|
|
});
|
|
|
|
const other_u128 = @intCast(u128, math.absInt(other_i128) catch {
|
|
if (self_i128 == 0) {
|
|
return .{ .num = 0 };
|
|
} else if (self_i128 == RocDec.one_point_zero.num) {
|
|
return other;
|
|
} else {
|
|
std.debug.panic("TODO runtime exception for overflow!", .{});
|
|
}
|
|
});
|
|
|
|
const unsigned_answer: i128 = mul_and_decimalize(self_u128, other_u128);
|
|
|
|
if (is_answer_negative) {
|
|
return .{ .num = -unsigned_answer };
|
|
} else {
|
|
return .{ .num = unsigned_answer };
|
|
}
|
|
}
|
|
};
|
|
|
|
const U256 = struct {
|
|
hi: u128,
|
|
lo: u128,
|
|
};
|
|
|
|
fn mul_and_decimalize(a: u128, b: u128) i128 {
|
|
const answer_u256 = mul_u128(a, b);
|
|
|
|
var lhs_hi = answer_u256.hi;
|
|
var lhs_lo = answer_u256.lo;
|
|
|
|
// Divide - or just add 1, multiply by floor(2^315/10^18), then right shift 315 times.
|
|
// floor(2^315/10^18) is 66749594872528440074844428317798503581334516323645399060845050244444366430645
|
|
|
|
// Add 1.
|
|
// This can't overflow because the intial numbers are only 127bit due to removing the sign bit.
|
|
var overflowed = @addWithOverflow(u128, lhs_lo, 1, &lhs_lo);
|
|
lhs_hi = blk: {
|
|
if (overflowed) {
|
|
break :blk lhs_hi + 1;
|
|
} else {
|
|
break :blk lhs_hi + 0;
|
|
}
|
|
};
|
|
|
|
// This needs to do multiplication in a way that expands,
|
|
// since we throw away 315 bits we care only about the higher end, not lower.
|
|
// So like need to do high low mult with 2 U256's and then bitshift.
|
|
// I bet this has a lot of room for multiplication optimization.
|
|
const rhs_hi: u128 = 0x9392ee8e921d5d073aff322e62439fcf;
|
|
const rhs_lo: u128 = 0x32d7f344649470f90cac0c573bf9e1b5;
|
|
|
|
const ea = mul_u128(lhs_lo, rhs_lo);
|
|
const gf = mul_u128(lhs_hi, rhs_lo);
|
|
const jh = mul_u128(lhs_lo, rhs_hi);
|
|
const lk = mul_u128(lhs_hi, rhs_hi);
|
|
|
|
const e = ea.hi;
|
|
const _a = ea.lo;
|
|
|
|
const g = gf.hi;
|
|
const f = gf.lo;
|
|
|
|
const j = jh.hi;
|
|
const h = jh.lo;
|
|
|
|
const l = lk.hi;
|
|
const k = lk.lo;
|
|
|
|
// b = e + f + h
|
|
var e_plus_f: u128 = undefined;
|
|
overflowed = @addWithOverflow(u128, e, f, &e_plus_f);
|
|
var b_carry1: u128 = undefined;
|
|
if (overflowed) {
|
|
b_carry1 = 1;
|
|
} else {
|
|
b_carry1 = 0;
|
|
}
|
|
|
|
var idk: u128 = undefined;
|
|
overflowed = @addWithOverflow(u128, e_plus_f, h, &idk);
|
|
var b_carry2: u128 = undefined;
|
|
if (overflowed) {
|
|
b_carry2 = 1;
|
|
} else {
|
|
b_carry2 = 0;
|
|
}
|
|
|
|
// c = carry + g + j + k // it doesn't say +k but I think it should be?
|
|
var g_plus_j: u128 = undefined;
|
|
overflowed = @addWithOverflow(u128, g, j, &g_plus_j);
|
|
var c_carry1: u128 = undefined;
|
|
if (overflowed) {
|
|
c_carry1 = 1;
|
|
} else {
|
|
c_carry1 = 0;
|
|
}
|
|
|
|
var g_plus_j_plus_k: u128 = undefined;
|
|
overflowed = @addWithOverflow(u128, g_plus_j, k, &g_plus_j_plus_k);
|
|
var c_carry2: u128 = undefined;
|
|
if (overflowed) {
|
|
c_carry2 = 1;
|
|
} else {
|
|
c_carry2 = 0;
|
|
}
|
|
|
|
var c_without_bcarry2: u128 = undefined;
|
|
overflowed = @addWithOverflow(u128, g_plus_j_plus_k, b_carry1, &c_without_bcarry2);
|
|
var c_carry3: u128 = undefined;
|
|
if (overflowed) {
|
|
c_carry3 = 1;
|
|
} else {
|
|
c_carry3 = 0;
|
|
}
|
|
|
|
var c: u128 = undefined;
|
|
overflowed = @addWithOverflow(u128, c_without_bcarry2, b_carry2, &c);
|
|
var c_carry4: u128 = undefined;
|
|
if (overflowed) {
|
|
c_carry4 = 1;
|
|
} else {
|
|
c_carry4 = 0;
|
|
}
|
|
|
|
// d = carry + l
|
|
var d: u128 = undefined;
|
|
overflowed = @addWithOverflow(u128, l, c_carry1, &d);
|
|
overflowed = overflowed or @addWithOverflow(u128, d, c_carry2, &d);
|
|
overflowed = overflowed or @addWithOverflow(u128, d, c_carry3, &d);
|
|
overflowed = overflowed or @addWithOverflow(u128, d, c_carry4, &d);
|
|
|
|
if (overflowed) {
|
|
std.debug.panic("TODO runtime exception for overflow!", .{});
|
|
}
|
|
|
|
// Final 512bit value is d, c, b, a
|
|
// need to left shift 321 times
|
|
// 315 - 256 is 59. So left shift d, c 59 times.
|
|
return @intCast(i128, c >> 59 | (d << (128 - 59)));
|
|
}
|
|
|
|
fn mul_u128(a: u128, b: u128) U256 {
|
|
var hi: u128 = undefined;
|
|
var lo: u128 = undefined;
|
|
|
|
const bits_in_dword_2: u32 = 64;
|
|
const lower_mask: u128 = math.maxInt(u128) >> bits_in_dword_2;
|
|
|
|
lo = (a & lower_mask) * (b & lower_mask);
|
|
|
|
var t = lo >> bits_in_dword_2;
|
|
|
|
lo &= lower_mask;
|
|
|
|
t += (a >> bits_in_dword_2) * (b & lower_mask);
|
|
|
|
lo += (t & lower_mask) << bits_in_dword_2;
|
|
|
|
hi = t >> bits_in_dword_2;
|
|
|
|
t = lo >> bits_in_dword_2;
|
|
|
|
lo &= lower_mask;
|
|
|
|
t += (b >> bits_in_dword_2) * (a & lower_mask);
|
|
|
|
lo += (t & lower_mask) << bits_in_dword_2;
|
|
|
|
hi += t >> bits_in_dword_2;
|
|
|
|
hi += (a >> bits_in_dword_2) * (b >> bits_in_dword_2);
|
|
|
|
return .{ .hi = hi, .lo = lo };
|
|
}
|
|
|
|
const testing = std.testing;
|
|
const expectEqual = testing.expectEqual;
|
|
|
|
test "fromU64" {
|
|
var dec = RocDec.fromU64(25);
|
|
|
|
try expectEqual(RocDec{ .num = 25000000000000000000 }, dec);
|
|
}
|
|
|
|
test "fromString: empty" {
|
|
var roc_str = RocStr.init("", 0);
|
|
var dec = RocDec.fromString(roc_str);
|
|
|
|
try expectEqual(dec, null);
|
|
}
|
|
|
|
test "fromString: 0" {
|
|
var roc_str = RocStr.init("0", 1);
|
|
var dec = RocDec.fromString(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = 0 }, dec.?);
|
|
}
|
|
|
|
test "fromString: 1" {
|
|
var roc_str = RocStr.init("1", 1);
|
|
var dec = RocDec.fromString(roc_str);
|
|
|
|
try expectEqual(RocDec.one_point_zero, dec.?);
|
|
}
|
|
|
|
test "fromString: 123.45" {
|
|
var roc_str = RocStr.init("123.45", 6);
|
|
var dec = RocDec.fromString(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = 123450000000000000000 }, dec.?);
|
|
}
|
|
|
|
test "fromString: .45" {
|
|
var roc_str = RocStr.init(".45", 3);
|
|
var dec = RocDec.fromString(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = 450000000000000000 }, dec.?);
|
|
}
|
|
|
|
test "fromString: 0.45" {
|
|
var roc_str = RocStr.init("0.45", 4);
|
|
var dec = RocDec.fromString(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = 450000000000000000 }, dec.?);
|
|
}
|
|
|
|
test "fromString: 123" {
|
|
var roc_str = RocStr.init("123", 3);
|
|
var dec = RocDec.fromString(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = 123000000000000000000 }, dec.?);
|
|
}
|
|
|
|
test "fromString: -.45" {
|
|
var roc_str = RocStr.init("-.45", 4);
|
|
var dec = RocDec.fromString(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = -450000000000000000 }, dec.?);
|
|
}
|
|
|
|
test "fromString: -0.45" {
|
|
var roc_str = RocStr.init("-0.45", 5);
|
|
var dec = RocDec.fromString(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = -450000000000000000 }, dec.?);
|
|
}
|
|
|
|
test "fromString: -123" {
|
|
var roc_str = RocStr.init("-123", 4);
|
|
var dec = RocDec.fromString(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = -123000000000000000000 }, dec.?);
|
|
}
|
|
|
|
test "fromString: -123.45" {
|
|
var roc_str = RocStr.init("-123.45", 7);
|
|
var dec = RocDec.fromString(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = -123450000000000000000 }, dec.?);
|
|
}
|
|
|
|
test "fromString: abc" {
|
|
var roc_str = RocStr.init("abc", 3);
|
|
var dec = RocDec.fromString(roc_str);
|
|
|
|
try expectEqual(dec, null);
|
|
}
|
|
|
|
test "fromString: 123.abc" {
|
|
var roc_str = RocStr.init("123.abc", 7);
|
|
var dec = RocDec.fromString(roc_str);
|
|
|
|
try expectEqual(dec, null);
|
|
}
|
|
|
|
test "fromString: abc.123" {
|
|
var roc_str = RocStr.init("abc.123", 7);
|
|
var dec = RocDec.fromString(roc_str);
|
|
|
|
try expectEqual(dec, null);
|
|
}
|
|
|
|
test "fromString: .123.1" {
|
|
var roc_str = RocStr.init(".123.1", 6);
|
|
var dec = RocDec.fromString(roc_str);
|
|
|
|
try expectEqual(dec, null);
|
|
}
|
|
|
|
test "add: 0" {
|
|
var dec: RocDec = .{ .num = 0 };
|
|
|
|
try expectEqual(RocDec{ .num = 0 }, dec.add(.{ .num = 0 }));
|
|
}
|
|
|
|
test "add: 1" {
|
|
var dec: RocDec = .{ .num = 0 };
|
|
|
|
try expectEqual(RocDec{ .num = 1 }, dec.add(.{ .num = 1 }));
|
|
}
|
|
|
|
test "mul: by 0" {
|
|
var dec: RocDec = .{ .num = 0 };
|
|
|
|
try expectEqual(RocDec{ .num = 0 }, dec.mul(.{ .num = 0 }));
|
|
}
|
|
|
|
test "mul: by 1" {
|
|
var dec: RocDec = RocDec.fromU64(15);
|
|
|
|
try expectEqual(RocDec.fromU64(15), dec.mul(RocDec.fromU64(1)));
|
|
}
|
|
|
|
test "mul: by 2" {
|
|
var dec: RocDec = RocDec.fromU64(15);
|
|
|
|
try expectEqual(RocDec.fromU64(30), dec.mul(RocDec.fromU64(2)));
|
|
}
|