mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-20 07:17:50 +03:00
Merge pull request #1435 from rtfeldman/add-dec-types
Add Dec types & basic operations
This commit is contained in:
commit
63932b9b73
@ -27,7 +27,6 @@ pub fn build(b: *Builder) void {
|
||||
llvm_obj.strip = true;
|
||||
llvm_obj.emit_llvm_ir = true;
|
||||
llvm_obj.emit_bin = false;
|
||||
llvm_obj.bundle_compiler_rt = true;
|
||||
const ir = b.step("ir", "Build LLVM ir");
|
||||
ir.dependOn(&llvm_obj.step);
|
||||
|
||||
@ -40,6 +39,7 @@ pub fn build(b: *Builder) void {
|
||||
obj.linkSystemLibrary("c");
|
||||
obj.setOutputDir(".");
|
||||
obj.strip = true;
|
||||
obj.bundle_compiler_rt = true;
|
||||
const obj_step = b.step("object", "Build object file for linking");
|
||||
obj_step.dependOn(&obj.step);
|
||||
|
||||
|
@ -1,16 +1,19 @@
|
||||
const std = @import("std");
|
||||
const str = @import("str.zig");
|
||||
const utils = @import("utils.zig");
|
||||
|
||||
const math = std.math;
|
||||
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
|
||||
const RocStr = str.RocStr;
|
||||
const WithOverflow = utils.WithOverflow;
|
||||
|
||||
pub const RocDec = struct {
|
||||
pub const RocDec = extern struct {
|
||||
num: i128,
|
||||
|
||||
pub const decimal_places: u5 = 18;
|
||||
pub const whole_number_places: u5 = 21;
|
||||
const max_digits: u6 = 39;
|
||||
const leading_zeros: [17]u8 = "00000000000000000".*;
|
||||
const max_str_length: u6 = max_digits + 2; // + 2 here to account for the sign & decimal dot
|
||||
|
||||
pub const min: RocDec = .{ .num = math.minInt(i128) };
|
||||
pub const max: RocDec = .{ .num = math.maxInt(i128) };
|
||||
@ -22,6 +25,23 @@ pub const RocDec = struct {
|
||||
return .{ .num = num * one_point_zero_i128 };
|
||||
}
|
||||
|
||||
// TODO: There's got to be a better way to do this other than converting to Str
|
||||
pub fn fromF64(num: f64) ?RocDec {
|
||||
var digit_bytes: [19]u8 = undefined; // 19 = max f64 digits + '.' + '-'
|
||||
|
||||
var fbs = std.io.fixedBufferStream(digit_bytes[0..]);
|
||||
std.fmt.formatFloatDecimal(num, .{}, fbs.writer()) catch
|
||||
return null;
|
||||
|
||||
var dec = RocDec.fromStr(RocStr.init(&digit_bytes, fbs.pos));
|
||||
|
||||
if (dec) |d| {
|
||||
return d;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fromStr(roc_str: RocStr) ?RocDec {
|
||||
if (roc_str.isEmpty()) {
|
||||
return null;
|
||||
@ -57,7 +77,7 @@ pub const RocDec = struct {
|
||||
|
||||
var after_str_len = (length - 1) - pi;
|
||||
if (after_str_len > decimal_places) {
|
||||
std.debug.panic("TODO runtime exception for too many decimal places!", .{});
|
||||
@panic("TODO runtime exception for too many decimal places!");
|
||||
}
|
||||
var diff_decimal_places = decimal_places - after_str_len;
|
||||
|
||||
@ -74,37 +94,38 @@ pub const RocDec = struct {
|
||||
var result: i128 = undefined;
|
||||
var overflowed = @mulWithOverflow(i128, before, one_point_zero_i128, &result);
|
||||
if (overflowed) {
|
||||
std.debug.panic("TODO runtime exception for overflow!", .{});
|
||||
@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!", .{});
|
||||
const dec: RocDec = blk: {
|
||||
if (before_val_i128) |before| {
|
||||
if (after_val_i128) |after| {
|
||||
var result: i128 = undefined;
|
||||
var overflowed = @addWithOverflow(i128, before, after, &result);
|
||||
if (overflowed) {
|
||||
@panic("TODO runtime exception for overflow!");
|
||||
}
|
||||
break :blk .{ .num = result };
|
||||
} else {
|
||||
break :blk .{ .num = before };
|
||||
}
|
||||
dec = .{ .num = result };
|
||||
} else if (after_val_i128) |after| {
|
||||
break :blk .{ .num = after };
|
||||
} else {
|
||||
dec = .{ .num = before };
|
||||
return null;
|
||||
}
|
||||
} else if (after_val_i128) |after| {
|
||||
dec = .{ .num = after };
|
||||
}
|
||||
};
|
||||
|
||||
if (dec) |d| {
|
||||
if (is_negative) {
|
||||
dec = d.negate();
|
||||
}
|
||||
if (is_negative) {
|
||||
return dec.negate();
|
||||
} else {
|
||||
return dec;
|
||||
}
|
||||
|
||||
return dec;
|
||||
}
|
||||
|
||||
fn isDigit(c: u8) bool {
|
||||
inline fn isDigit(c: u8) bool {
|
||||
return (c -% 48) <= 9;
|
||||
}
|
||||
|
||||
@ -114,80 +135,84 @@ pub const RocDec = struct {
|
||||
return RocStr.init("0.0", 3);
|
||||
}
|
||||
|
||||
// Check if this Dec is negative, and if so convert to positive
|
||||
// We will handle adding the '-' later
|
||||
const is_negative = self.num < 0;
|
||||
const num = if (is_negative) std.math.negate(self.num) catch {
|
||||
std.debug.panic("TODO runtime exception failing to negate", .{});
|
||||
} else self.num;
|
||||
const num = self.num;
|
||||
const is_negative = num < 0;
|
||||
|
||||
// Format the backing i128 into an array of digits (u8s)
|
||||
var digit_bytes: [max_digits + 1]u8 = undefined;
|
||||
var num_digits = std.fmt.formatIntBuf(digit_bytes[0..], num, 10, false, .{});
|
||||
// Format the backing i128 into an array of digit (ascii) characters (u8s)
|
||||
var digit_bytes_storage: [max_digits + 1]u8 = undefined;
|
||||
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
|
||||
var before_digits_slice: []const u8 = undefined;
|
||||
var before_digits_offset: usize = 0;
|
||||
var before_digits_adjust: u6 = 0;
|
||||
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_slice = digit_bytes[0..before_digits_offset];
|
||||
} else {
|
||||
before_digits_adjust = @intCast(u6, math.absInt(@intCast(i7, num_digits) - decimal_places) catch {
|
||||
std.debug.panic("TODO runtime exception for overflow when getting abs", .{});
|
||||
});
|
||||
before_digits_slice = "0";
|
||||
}
|
||||
|
||||
// Figure out how many trailing zeros there are
|
||||
// I tried to use https://ziglang.org/documentation/0.8.0/#ctz and it mostly worked,
|
||||
// but was giving seemingly incorrect values for certain numbers. So instead we use
|
||||
// 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;
|
||||
for (digit_bytes[0..before_digits_offset]) |c| {
|
||||
str_bytes[position] = c;
|
||||
position += 1;
|
||||
}
|
||||
index -= 1;
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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_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];
|
||||
// otherwise there are no actual digits before the decimal point
|
||||
// but we format it with a '0'
|
||||
str_bytes[position] = '0';
|
||||
position += 1;
|
||||
}
|
||||
|
||||
// Get the slice for the sign
|
||||
const sign_slice: []const u8 = if (is_negative) "-" else leading_zeros[0..0];
|
||||
// we've done everything before the decimal point, so now we can put the decimal point in
|
||||
str_bytes[position] = '.';
|
||||
position += 1;
|
||||
|
||||
// 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;
|
||||
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
|
||||
// For example, for the number 0.000123 we need to prepend 3 zeros after the decimal point
|
||||
const after_zeros_num = if (num_digits < decimal_places) decimal_places - num_digits else 0;
|
||||
|
||||
// Join the slices together
|
||||
// We do `max_digits + 2` here because we need to account for a possible sign ('-') and the dot ('.').
|
||||
// Ideally, we'd use str_len here
|
||||
var str_bytes: [max_digits + 2]u8 = undefined;
|
||||
_ = std.fmt.bufPrint(str_bytes[0..str_len], "{s}{s}.{s}{s}", .{ sign_slice, before_digits_slice, after_zeros_slice, after_digits_slice }) catch {
|
||||
std.debug.panic("TODO runtime exception failing to print slices", .{});
|
||||
};
|
||||
var i: usize = 0;
|
||||
while (i < after_zeros_num) : (i += 1) {
|
||||
str_bytes[position] = '0';
|
||||
position += 1;
|
||||
}
|
||||
|
||||
return RocStr.init(&str_bytes, str_len);
|
||||
// otherwise append the decimal digits except the trailing zeros
|
||||
for (digit_bytes[before_digits_offset .. num_digits - trailing_zeros]) |c| {
|
||||
str_bytes[position] = c;
|
||||
position += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return RocStr.init(&str_bytes, position);
|
||||
}
|
||||
|
||||
pub fn eq(self: RocDec, other: RocDec) bool {
|
||||
return self.num == other.num;
|
||||
}
|
||||
|
||||
pub fn neq(self: RocDec, other: RocDec) bool {
|
||||
return self.num != other.num;
|
||||
}
|
||||
|
||||
pub fn negate(self: RocDec) ?RocDec {
|
||||
@ -195,29 +220,41 @@ pub const RocDec = struct {
|
||||
return if (negated) |n| .{ .num = n } else null;
|
||||
}
|
||||
|
||||
pub fn add(self: RocDec, other: RocDec) RocDec {
|
||||
pub fn addWithOverflow(self: RocDec, other: RocDec) WithOverflow(RocDec) {
|
||||
var answer: i128 = undefined;
|
||||
const overflowed = @addWithOverflow(i128, self.num, other.num, &answer);
|
||||
|
||||
if (!overflowed) {
|
||||
return RocDec{ .num = answer };
|
||||
return .{ .value = RocDec{ .num = answer }, .has_overflowed = overflowed };
|
||||
}
|
||||
|
||||
pub fn add(self: RocDec, other: RocDec) RocDec {
|
||||
const answer = RocDec.addWithOverflow(self, other);
|
||||
|
||||
if (answer.has_overflowed) {
|
||||
@panic("TODO runtime exception for overflow!");
|
||||
} else {
|
||||
std.debug.panic("TODO runtime exception for overflow!", .{});
|
||||
return answer.value;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sub(self: RocDec, other: RocDec) RocDec {
|
||||
pub fn subWithOverflow(self: RocDec, other: RocDec) WithOverflow(RocDec) {
|
||||
var answer: i128 = undefined;
|
||||
const overflowed = @subWithOverflow(i128, self.num, other.num, &answer);
|
||||
|
||||
if (!overflowed) {
|
||||
return RocDec{ .num = answer };
|
||||
return .{ .value = RocDec{ .num = answer }, .has_overflowed = overflowed };
|
||||
}
|
||||
|
||||
pub fn sub(self: RocDec, other: RocDec) RocDec {
|
||||
const answer = RocDec.subWithOverflow(self, other);
|
||||
|
||||
if (answer.has_overflowed) {
|
||||
@panic("TODO runtime exception for overflow!");
|
||||
} else {
|
||||
std.debug.panic("TODO runtime exception for overflow!", .{});
|
||||
return answer.value;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mul(self: RocDec, other: RocDec) RocDec {
|
||||
pub fn mulWithOverflow(self: RocDec, other: RocDec) WithOverflow(RocDec) {
|
||||
const self_i128 = self.num;
|
||||
const other_i128 = other.num;
|
||||
// const answer = 0; //self_i256 * other_i256;
|
||||
@ -226,30 +263,40 @@ pub const RocDec = struct {
|
||||
|
||||
const self_u128 = @intCast(u128, math.absInt(self_i128) catch {
|
||||
if (other_i128 == 0) {
|
||||
return .{ .num = 0 };
|
||||
return .{ .value = RocDec{ .num = 0 }, .has_overflowed = false };
|
||||
} else if (other_i128 == RocDec.one_point_zero.num) {
|
||||
return self;
|
||||
return .{ .value = self, .has_overflowed = false };
|
||||
} else {
|
||||
std.debug.panic("TODO runtime exception for overflow!", .{});
|
||||
return .{ .value = undefined, .has_overflowed = true };
|
||||
}
|
||||
});
|
||||
|
||||
const other_u128 = @intCast(u128, math.absInt(other_i128) catch {
|
||||
if (self_i128 == 0) {
|
||||
return .{ .num = 0 };
|
||||
return .{ .value = RocDec{ .num = 0 }, .has_overflowed = false };
|
||||
} else if (self_i128 == RocDec.one_point_zero.num) {
|
||||
return other;
|
||||
return .{ .value = other, .has_overflowed = false };
|
||||
} else {
|
||||
std.debug.panic("TODO runtime exception for overflow!", .{});
|
||||
return .{ .value = undefined, .has_overflowed = true };
|
||||
}
|
||||
});
|
||||
|
||||
const unsigned_answer: i128 = mul_and_decimalize(self_u128, other_u128);
|
||||
|
||||
if (is_answer_negative) {
|
||||
return .{ .num = -unsigned_answer };
|
||||
return .{ .value = RocDec{ .num = -unsigned_answer }, .has_overflowed = false };
|
||||
} else {
|
||||
return .{ .num = unsigned_answer };
|
||||
return .{ .value = RocDec{ .num = unsigned_answer }, .has_overflowed = false };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mul(self: RocDec, other: RocDec) RocDec {
|
||||
const answer = RocDec.mulWithOverflow(self, other);
|
||||
|
||||
if (answer.has_overflowed) {
|
||||
@panic("TODO runtime exception for overflow!");
|
||||
} else {
|
||||
return answer.value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -264,7 +311,9 @@ pub const RocDec = struct {
|
||||
|
||||
// (n / 0) is an error
|
||||
if (denominator_i128 == 0) {
|
||||
std.debug.panic("TODO runtime exception for divide by 0!", .{});
|
||||
// The compiler frontend does the `denominator == 0` check for us,
|
||||
// therefore this case is unreachable from roc user code
|
||||
unreachable;
|
||||
}
|
||||
|
||||
// If they're both negative, or if neither is negative, the final answer
|
||||
@ -292,7 +341,7 @@ pub const RocDec = struct {
|
||||
if (denominator_i128 == one_point_zero_i128) {
|
||||
return self;
|
||||
} else {
|
||||
std.debug.panic("TODO runtime exception for overflow when dividing!", .{});
|
||||
@panic("TODO runtime exception for overflow when dividing!");
|
||||
}
|
||||
};
|
||||
const numerator_u128 = @intCast(u128, numerator_abs_i128);
|
||||
@ -305,7 +354,7 @@ pub const RocDec = struct {
|
||||
if (numerator_i128 == one_point_zero_i128) {
|
||||
return other;
|
||||
} else {
|
||||
std.debug.panic("TODO runtime exception for overflow when dividing!", .{});
|
||||
@panic("TODO runtime exception for overflow when dividing!");
|
||||
}
|
||||
};
|
||||
const denominator_u128 = @intCast(u128, denominator_abs_i128);
|
||||
@ -317,13 +366,35 @@ pub const RocDec = struct {
|
||||
if (answer.hi == 0 and answer.lo <= math.maxInt(i128)) {
|
||||
unsigned_answer = @intCast(i128, answer.lo);
|
||||
} else {
|
||||
std.debug.panic("TODO runtime exception for overflow when dividing!", .{});
|
||||
@panic("TODO runtime exception for overflow when dividing!");
|
||||
}
|
||||
|
||||
return RocDec{ .num = if (is_answer_negative) -unsigned_answer else unsigned_answer };
|
||||
}
|
||||
};
|
||||
|
||||
// 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 {
|
||||
hi: u128,
|
||||
lo: u128,
|
||||
@ -437,7 +508,7 @@ fn mul_and_decimalize(a: u128, b: u128) i128 {
|
||||
overflowed = overflowed or @addWithOverflow(u128, d, c_carry4, &d);
|
||||
|
||||
if (overflowed) {
|
||||
std.debug.panic("TODO runtime exception for overflow!", .{});
|
||||
@panic("TODO runtime exception for overflow!");
|
||||
}
|
||||
|
||||
// Final 512bit value is d, c, b, a
|
||||
@ -652,6 +723,11 @@ test "fromU64" {
|
||||
try expectEqual(RocDec{ .num = 25000000000000000000 }, dec);
|
||||
}
|
||||
|
||||
test "fromF64" {
|
||||
var dec = RocDec.fromF64(25.5);
|
||||
try expectEqual(RocDec{ .num = 25500000000000000000 }, dec.?);
|
||||
}
|
||||
|
||||
test "fromStr: empty" {
|
||||
var roc_str = RocStr.init("", 0);
|
||||
var dec = RocDec.fromStr(roc_str);
|
||||
@ -867,6 +943,26 @@ test "toStr: 12345678912345678912.111111111111111111 (max number of digits)" {
|
||||
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
|
||||
}
|
||||
|
||||
test "toStr: std.math.maxInt" {
|
||||
var dec: RocDec = .{ .num = std.math.maxInt(i128) };
|
||||
var res_roc_str = dec.toStr();
|
||||
errdefer res_roc_str.?.deinit();
|
||||
defer res_roc_str.?.deinit();
|
||||
|
||||
const res_slice: []const u8 = "170141183460469231731.687303715884105727"[0..];
|
||||
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
|
||||
}
|
||||
|
||||
test "toStr: std.math.minInt" {
|
||||
var dec: RocDec = .{ .num = std.math.minInt(i128) };
|
||||
var res_roc_str = dec.toStr();
|
||||
errdefer res_roc_str.?.deinit();
|
||||
defer res_roc_str.?.deinit();
|
||||
|
||||
const res_slice: []const u8 = "-170141183460469231731.687303715884105728"[0..];
|
||||
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
|
||||
}
|
||||
|
||||
test "toStr: 0" {
|
||||
var dec: RocDec = .{ .num = 0 };
|
||||
var res_roc_str = dec.toStr();
|
||||
@ -953,3 +1049,37 @@ test "div: 10 / 3" {
|
||||
|
||||
try expectEqual(res, numer.div(denom));
|
||||
}
|
||||
|
||||
// exports
|
||||
|
||||
pub fn fromF64C(arg: f64) callconv(.C) i128 {
|
||||
return if (@call(.{ .modifier = always_inline }, RocDec.fromF64, .{arg})) |dec| dec.num else @panic("TODO runtime exception failing convert f64 to RocDec");
|
||||
}
|
||||
|
||||
pub fn eqC(arg1: RocDec, arg2: RocDec) callconv(.C) bool {
|
||||
return @call(.{ .modifier = always_inline }, RocDec.eq, .{ arg1, arg2 });
|
||||
}
|
||||
|
||||
pub fn neqC(arg1: RocDec, arg2: RocDec) callconv(.C) bool {
|
||||
return @call(.{ .modifier = always_inline }, RocDec.neq, .{ arg1, arg2 });
|
||||
}
|
||||
|
||||
pub fn negateC(arg: RocDec) callconv(.C) i128 {
|
||||
return if (@call(.{ .modifier = always_inline }, RocDec.negate, .{arg})) |dec| dec.num else @panic("TODO overflow for negating RocDec");
|
||||
}
|
||||
|
||||
pub fn addC(arg1: RocDec, arg2: RocDec) callconv(.C) WithOverflow(RocDec) {
|
||||
return @call(.{ .modifier = always_inline }, RocDec.addWithOverflow, .{ arg1, arg2 });
|
||||
}
|
||||
|
||||
pub fn subC(arg1: RocDec, arg2: RocDec) callconv(.C) WithOverflow(RocDec) {
|
||||
return @call(.{ .modifier = always_inline }, RocDec.subWithOverflow, .{ arg1, arg2 });
|
||||
}
|
||||
|
||||
pub fn mulC(arg1: RocDec, arg2: RocDec) callconv(.C) WithOverflow(RocDec) {
|
||||
return @call(.{ .modifier = always_inline }, RocDec.mulWithOverflow, .{ arg1, arg2 });
|
||||
}
|
||||
|
||||
pub fn divC(arg1: RocDec, arg2: RocDec) callconv(.C) i128 {
|
||||
return @call(.{ .modifier = always_inline }, RocDec.div, .{ arg1, arg2 }).num;
|
||||
}
|
||||
|
@ -5,6 +5,17 @@ const testing = std.testing;
|
||||
// Dec Module
|
||||
const dec = @import("dec.zig");
|
||||
|
||||
comptime {
|
||||
exportDecFn(dec.fromF64C, "from_f64");
|
||||
exportDecFn(dec.eqC, "eq");
|
||||
exportDecFn(dec.neqC, "neq");
|
||||
exportDecFn(dec.negateC, "negate");
|
||||
exportDecFn(dec.addC, "add_with_overflow");
|
||||
exportDecFn(dec.subC, "sub_with_overflow");
|
||||
exportDecFn(dec.mulC, "mul_with_overflow");
|
||||
exportDecFn(dec.divC, "div");
|
||||
}
|
||||
|
||||
// List Module
|
||||
const list = @import("list.zig");
|
||||
|
||||
@ -102,13 +113,71 @@ fn exportStrFn(comptime func: anytype, comptime func_name: []const u8) void {
|
||||
fn exportDictFn(comptime func: anytype, comptime func_name: []const u8) void {
|
||||
exportBuiltinFn(func, "dict." ++ func_name);
|
||||
}
|
||||
|
||||
fn exportListFn(comptime func: anytype, comptime func_name: []const u8) void {
|
||||
exportBuiltinFn(func, "list." ++ func_name);
|
||||
}
|
||||
fn exportDecFn(comptime func: anytype, comptime func_name: []const u8) void {
|
||||
exportBuiltinFn(func, "dec." ++ func_name);
|
||||
}
|
||||
|
||||
// Custom panic function, as builtin Zig version errors during LLVM verification
|
||||
pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn {
|
||||
std.debug.print("{s}: {?}", .{ message, stacktrace });
|
||||
unreachable;
|
||||
}
|
||||
|
||||
// Run all tests in imported modules
|
||||
// https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94
|
||||
test "" {
|
||||
testing.refAllDecls(@This());
|
||||
}
|
||||
|
||||
// For unclear reasons, sometimes this function is not linked in on some machines.
|
||||
// Therefore we provide it as LLVM bitcode and mark it as externally linked during our LLVM codegen
|
||||
//
|
||||
// Taken from
|
||||
// https://github.com/ziglang/zig/blob/85755c51d529e7d9b406c6bdf69ce0a0f33f3353/lib/std/special/compiler_rt/muloti4.zig
|
||||
//
|
||||
// Thank you Zig Contributors!
|
||||
export fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 {
|
||||
// @setRuntimeSafety(builtin.is_test);
|
||||
|
||||
const min = @bitCast(i128, @as(u128, 1 << (128 - 1)));
|
||||
const max = ~min;
|
||||
overflow.* = 0;
|
||||
|
||||
const r = a *% b;
|
||||
if (a == min) {
|
||||
if (b != 0 and b != 1) {
|
||||
overflow.* = 1;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
if (b == min) {
|
||||
if (a != 0 and a != 1) {
|
||||
overflow.* = 1;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
const sa = a >> (128 - 1);
|
||||
const abs_a = (a ^ sa) -% sa;
|
||||
const sb = b >> (128 - 1);
|
||||
const abs_b = (b ^ sb) -% sb;
|
||||
|
||||
if (abs_a < 2 or abs_b < 2) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if (sa == sb) {
|
||||
if (abs_a > @divTrunc(max, abs_b)) {
|
||||
overflow.* = 1;
|
||||
}
|
||||
} else {
|
||||
if (abs_a > @divTrunc(min, -abs_b)) {
|
||||
overflow.* = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
const std = @import("std");
|
||||
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
|
||||
|
||||
pub fn WithOverflow(comptime T: type) type {
|
||||
return extern struct { value: T, has_overflowed: bool };
|
||||
}
|
||||
|
||||
// If allocation fails, this must cxa_throw - it must not return a null pointer!
|
||||
extern fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void;
|
||||
|
||||
|
@ -66,3 +66,12 @@ pub const LIST_SORT_WITH: &str = "roc_builtins.list.sort_with";
|
||||
pub const LIST_CONCAT: &str = "roc_builtins.list.concat";
|
||||
pub const LIST_SET: &str = "roc_builtins.list.set";
|
||||
pub const LIST_SET_IN_PLACE: &str = "roc_builtins.list.set_in_place";
|
||||
|
||||
pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64";
|
||||
pub const DEC_EQ: &str = "roc_builtins.dec.eq";
|
||||
pub const DEC_NEQ: &str = "roc_builtins.dec.neq";
|
||||
pub const DEC_NEGATE: &str = "roc_builtins.dec.negate";
|
||||
pub const DEC_ADD_WITH_OVERFLOW: &str = "roc_builtins.dec.add_with_overflow";
|
||||
pub const DEC_SUB_WITH_OVERFLOW: &str = "roc_builtins.dec.sub_with_overflow";
|
||||
pub const DEC_MUL_WITH_OVERFLOW: &str = "roc_builtins.dec.mul_with_overflow";
|
||||
pub const DEC_DIV: &str = "roc_builtins.dec.div";
|
||||
|
@ -455,8 +455,7 @@ fn num_add_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
num_binop(symbol, var_store, LowLevel::NumAddWrap)
|
||||
}
|
||||
|
||||
/// Num.addChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
|
||||
fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
fn num_overflow_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) -> Def {
|
||||
let bool_var = var_store.fresh();
|
||||
let num_var_1 = var_store.fresh();
|
||||
let num_var_2 = var_store.fresh();
|
||||
@ -464,7 +463,7 @@ fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let ret_var = var_store.fresh();
|
||||
let record_var = var_store.fresh();
|
||||
|
||||
// let arg_3 = RunLowLevel NumAddChecked arg_1 arg_2
|
||||
// let arg_3 = RunLowLevel NumXXXChecked arg_1 arg_2
|
||||
//
|
||||
// if arg_3.b then
|
||||
// # overflow
|
||||
@ -517,11 +516,11 @@ fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
),
|
||||
};
|
||||
|
||||
// arg_3 = RunLowLevel NumAddChecked arg_1 arg_2
|
||||
// arg_3 = RunLowLevel NumXXXChecked arg_1 arg_2
|
||||
let def = crate::def::Def {
|
||||
loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)),
|
||||
loc_expr: no_region(RunLowLevel {
|
||||
op: LowLevel::NumAddChecked,
|
||||
op: lowlevel,
|
||||
args: vec![
|
||||
(num_var_1, Var(Symbol::ARG_1)),
|
||||
(num_var_2, Var(Symbol::ARG_2)),
|
||||
@ -544,6 +543,11 @@ fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.addChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
|
||||
fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
num_overflow_checked(symbol, var_store, LowLevel::NumAddChecked)
|
||||
}
|
||||
|
||||
/// Num.sub : Num a, Num a -> Num a
|
||||
fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
num_binop(symbol, var_store, LowLevel::NumSub)
|
||||
@ -556,91 +560,7 @@ fn num_sub_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
|
||||
/// Num.subChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
|
||||
fn num_sub_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let bool_var = var_store.fresh();
|
||||
let num_var_1 = var_store.fresh();
|
||||
let num_var_2 = var_store.fresh();
|
||||
let num_var_3 = var_store.fresh();
|
||||
let ret_var = var_store.fresh();
|
||||
let record_var = var_store.fresh();
|
||||
|
||||
// let arg_3 = RunLowLevel NumSubChecked arg_1 arg_2
|
||||
//
|
||||
// if arg_3.b then
|
||||
// # overflow
|
||||
// Err Overflow
|
||||
// else
|
||||
// # all is well
|
||||
// Ok arg_3.a
|
||||
|
||||
let cont = If {
|
||||
branch_var: ret_var,
|
||||
cond_var: bool_var,
|
||||
branches: vec![(
|
||||
// if-condition
|
||||
no_region(
|
||||
// arg_3.b
|
||||
Access {
|
||||
record_var,
|
||||
ext_var: var_store.fresh(),
|
||||
field: "b".into(),
|
||||
field_var: var_store.fresh(),
|
||||
loc_expr: Box::new(no_region(Var(Symbol::ARG_3))),
|
||||
},
|
||||
),
|
||||
// overflow!
|
||||
no_region(tag(
|
||||
"Err",
|
||||
vec![tag("Overflow", Vec::new(), var_store)],
|
||||
var_store,
|
||||
)),
|
||||
)],
|
||||
final_else: Box::new(
|
||||
// all is well
|
||||
no_region(
|
||||
// Ok arg_3.a
|
||||
tag(
|
||||
"Ok",
|
||||
vec![
|
||||
// arg_3.a
|
||||
Access {
|
||||
record_var,
|
||||
ext_var: var_store.fresh(),
|
||||
field: "a".into(),
|
||||
field_var: num_var_3,
|
||||
loc_expr: Box::new(no_region(Var(Symbol::ARG_3))),
|
||||
},
|
||||
],
|
||||
var_store,
|
||||
),
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
// arg_3 = RunLowLevel NumSubChecked arg_1 arg_2
|
||||
let def = crate::def::Def {
|
||||
loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)),
|
||||
loc_expr: no_region(RunLowLevel {
|
||||
op: LowLevel::NumSubChecked,
|
||||
args: vec![
|
||||
(num_var_1, Var(Symbol::ARG_1)),
|
||||
(num_var_2, Var(Symbol::ARG_2)),
|
||||
],
|
||||
ret_var: record_var,
|
||||
}),
|
||||
expr_var: record_var,
|
||||
pattern_vars: SendMap::default(),
|
||||
annotation: None,
|
||||
};
|
||||
|
||||
let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var);
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(num_var_1, Symbol::ARG_1), (num_var_2, Symbol::ARG_2)],
|
||||
var_store,
|
||||
body,
|
||||
ret_var,
|
||||
)
|
||||
num_overflow_checked(symbol, var_store, LowLevel::NumSubChecked)
|
||||
}
|
||||
|
||||
/// Num.mul : Num a, Num a -> Num a
|
||||
@ -655,91 +575,7 @@ fn num_mul_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
|
||||
/// Num.mulChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
|
||||
fn num_mul_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let bool_var = var_store.fresh();
|
||||
let num_var_1 = var_store.fresh();
|
||||
let num_var_2 = var_store.fresh();
|
||||
let num_var_3 = var_store.fresh();
|
||||
let ret_var = var_store.fresh();
|
||||
let record_var = var_store.fresh();
|
||||
|
||||
// let arg_3 = RunLowLevel NumMulChecked arg_1 arg_2
|
||||
//
|
||||
// if arg_3.b then
|
||||
// # overflow
|
||||
// Err Overflow
|
||||
// else
|
||||
// # all is well
|
||||
// Ok arg_3.a
|
||||
|
||||
let cont = If {
|
||||
branch_var: ret_var,
|
||||
cond_var: bool_var,
|
||||
branches: vec![(
|
||||
// if-condition
|
||||
no_region(
|
||||
// arg_3.b
|
||||
Access {
|
||||
record_var,
|
||||
ext_var: var_store.fresh(),
|
||||
field: "b".into(),
|
||||
field_var: var_store.fresh(),
|
||||
loc_expr: Box::new(no_region(Var(Symbol::ARG_3))),
|
||||
},
|
||||
),
|
||||
// overflow!
|
||||
no_region(tag(
|
||||
"Err",
|
||||
vec![tag("Overflow", Vec::new(), var_store)],
|
||||
var_store,
|
||||
)),
|
||||
)],
|
||||
final_else: Box::new(
|
||||
// all is well
|
||||
no_region(
|
||||
// Ok arg_3.a
|
||||
tag(
|
||||
"Ok",
|
||||
vec![
|
||||
// arg_3.a
|
||||
Access {
|
||||
record_var,
|
||||
ext_var: var_store.fresh(),
|
||||
field: "a".into(),
|
||||
field_var: num_var_3,
|
||||
loc_expr: Box::new(no_region(Var(Symbol::ARG_3))),
|
||||
},
|
||||
],
|
||||
var_store,
|
||||
),
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
// arg_3 = RunLowLevel NumMulChecked arg_1 arg_2
|
||||
let def = crate::def::Def {
|
||||
loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)),
|
||||
loc_expr: no_region(RunLowLevel {
|
||||
op: LowLevel::NumMulChecked,
|
||||
args: vec![
|
||||
(num_var_1, Var(Symbol::ARG_1)),
|
||||
(num_var_2, Var(Symbol::ARG_2)),
|
||||
],
|
||||
ret_var: record_var,
|
||||
}),
|
||||
expr_var: record_var,
|
||||
pattern_vars: SendMap::default(),
|
||||
annotation: None,
|
||||
};
|
||||
|
||||
let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var);
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(num_var_1, Symbol::ARG_1), (num_var_2, Symbol::ARG_2)],
|
||||
var_store,
|
||||
body,
|
||||
ret_var,
|
||||
)
|
||||
num_overflow_checked(symbol, var_store, LowLevel::NumMulChecked)
|
||||
}
|
||||
|
||||
/// Num.isGt : Num a, Num a -> Bool
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::path::Path;
|
||||
|
||||
use crate::llvm::bitcode::call_bitcode_fn;
|
||||
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
|
||||
use crate::llvm::build_dict::{
|
||||
dict_contains, dict_difference, dict_empty, dict_get, dict_insert, dict_intersection,
|
||||
dict_keys, dict_len, dict_remove, dict_union, dict_values, dict_walk, set_from_list,
|
||||
@ -362,13 +362,16 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
|
||||
// List of all supported LLVM intrinsics:
|
||||
//
|
||||
// https://releases.llvm.org/10.0.0/docs/LangRef.html#standard-c-library-intrinsics
|
||||
let i1_type = ctx.bool_type();
|
||||
let f64_type = ctx.f64_type();
|
||||
let i128_type = ctx.i128_type();
|
||||
let i64_type = ctx.i64_type();
|
||||
let i32_type = ctx.i32_type();
|
||||
let i16_type = ctx.i16_type();
|
||||
let i1_type = ctx.bool_type();
|
||||
let i8_type = ctx.i8_type();
|
||||
let i16_type = ctx.i16_type();
|
||||
let i32_type = ctx.i32_type();
|
||||
let i64_type = ctx.i64_type();
|
||||
|
||||
if let Some(func) = module.get_function("__muloti4") {
|
||||
func.set_linkage(Linkage::External);
|
||||
}
|
||||
|
||||
add_intrinsic(
|
||||
module,
|
||||
@ -418,8 +421,6 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
|
||||
f64_type.fn_type(&[f64_type.into()], false),
|
||||
);
|
||||
|
||||
// add with overflow
|
||||
|
||||
add_intrinsic(module, LLVM_SADD_WITH_OVERFLOW_I8, {
|
||||
let fields = [i8_type.into(), i1_type.into()];
|
||||
ctx.struct_type(&fields, false)
|
||||
@ -444,14 +445,6 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
|
||||
.fn_type(&[i64_type.into(), i64_type.into()], false)
|
||||
});
|
||||
|
||||
add_intrinsic(module, LLVM_SADD_WITH_OVERFLOW_I128, {
|
||||
let fields = [i128_type.into(), i1_type.into()];
|
||||
ctx.struct_type(&fields, false)
|
||||
.fn_type(&[i128_type.into(), i128_type.into()], false)
|
||||
});
|
||||
|
||||
// sub with overflow
|
||||
|
||||
add_intrinsic(module, LLVM_SSUB_WITH_OVERFLOW_I8, {
|
||||
let fields = [i8_type.into(), i1_type.into()];
|
||||
ctx.struct_type(&fields, false)
|
||||
@ -475,12 +468,6 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
|
||||
ctx.struct_type(&fields, false)
|
||||
.fn_type(&[i64_type.into(), i64_type.into()], false)
|
||||
});
|
||||
|
||||
add_intrinsic(module, LLVM_SSUB_WITH_OVERFLOW_I128, {
|
||||
let fields = [i128_type.into(), i1_type.into()];
|
||||
ctx.struct_type(&fields, false)
|
||||
.fn_type(&[i128_type.into(), i128_type.into()], false)
|
||||
});
|
||||
}
|
||||
|
||||
static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64";
|
||||
@ -640,10 +627,15 @@ pub fn float_with_precision<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
value: f64,
|
||||
precision: &Builtin,
|
||||
) -> FloatValue<'ctx> {
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
match precision {
|
||||
Builtin::Float64 => env.context.f64_type().const_float(value),
|
||||
Builtin::Float32 => env.context.f32_type().const_float(value),
|
||||
Builtin::Decimal => call_bitcode_fn(
|
||||
env,
|
||||
&[env.context.f64_type().const_float(value).into()],
|
||||
&bitcode::DEC_FROM_F64,
|
||||
),
|
||||
Builtin::Float64 => env.context.f64_type().const_float(value).into(),
|
||||
Builtin::Float32 => env.context.f32_type().const_float(value).into(),
|
||||
_ => panic!("Invalid layout for float literal = {:?}", precision),
|
||||
}
|
||||
}
|
||||
@ -662,7 +654,7 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
|
||||
},
|
||||
|
||||
Float(float) => match layout {
|
||||
Layout::Builtin(builtin) => float_with_precision(env, *float, builtin).into(),
|
||||
Layout::Builtin(builtin) => float_with_precision(env, *float, builtin),
|
||||
_ => panic!("Invalid layout for float literal = {:?}", layout),
|
||||
},
|
||||
|
||||
@ -5257,6 +5249,39 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
|
||||
}
|
||||
}
|
||||
|
||||
fn throw_on_overflow<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
result: StructValue<'ctx>, // of the form { value: T, has_overflowed: bool }
|
||||
message: &str,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let bd = env.builder;
|
||||
let context = env.context;
|
||||
|
||||
let has_overflowed = bd.build_extract_value(result, 1, "has_overflowed").unwrap();
|
||||
|
||||
let condition = bd.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
has_overflowed.into_int_value(),
|
||||
context.bool_type().const_zero(),
|
||||
"has_not_overflowed",
|
||||
);
|
||||
|
||||
let then_block = context.append_basic_block(parent, "then_block");
|
||||
let throw_block = context.append_basic_block(parent, "throw_block");
|
||||
|
||||
bd.build_conditional_branch(condition, then_block, throw_block);
|
||||
|
||||
bd.position_at_end(throw_block);
|
||||
|
||||
throw_exception(env, message);
|
||||
|
||||
bd.position_at_end(then_block);
|
||||
|
||||
bd.build_extract_value(result, 0, "operation_result")
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn build_int_binop<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
@ -5273,8 +5298,6 @@ fn build_int_binop<'a, 'ctx, 'env>(
|
||||
|
||||
match op {
|
||||
NumAdd => {
|
||||
let context = env.context;
|
||||
|
||||
let intrinsic = match lhs_layout {
|
||||
Layout::Builtin(Builtin::Int8) => LLVM_SADD_WITH_OVERFLOW_I8,
|
||||
Layout::Builtin(Builtin::Int16) => LLVM_SADD_WITH_OVERFLOW_I16,
|
||||
@ -5293,34 +5316,11 @@ fn build_int_binop<'a, 'ctx, 'env>(
|
||||
.call_intrinsic(intrinsic, &[lhs.into(), rhs.into()])
|
||||
.into_struct_value();
|
||||
|
||||
let add_result = bd.build_extract_value(result, 0, "add_result").unwrap();
|
||||
let has_overflowed = bd.build_extract_value(result, 1, "has_overflowed").unwrap();
|
||||
|
||||
let condition = bd.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
has_overflowed.into_int_value(),
|
||||
context.bool_type().const_zero(),
|
||||
"has_not_overflowed",
|
||||
);
|
||||
|
||||
let then_block = context.append_basic_block(parent, "then_block");
|
||||
let throw_block = context.append_basic_block(parent, "throw_block");
|
||||
|
||||
bd.build_conditional_branch(condition, then_block, throw_block);
|
||||
|
||||
bd.position_at_end(throw_block);
|
||||
|
||||
throw_exception(env, "integer addition overflowed!");
|
||||
|
||||
bd.position_at_end(then_block);
|
||||
|
||||
add_result
|
||||
throw_on_overflow(env, parent, result, "integer addition overflowed!")
|
||||
}
|
||||
NumAddWrap => bd.build_int_add(lhs, rhs, "add_int_wrap").into(),
|
||||
NumAddChecked => env.call_intrinsic(LLVM_SADD_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]),
|
||||
NumSub => {
|
||||
let context = env.context;
|
||||
|
||||
let intrinsic = match lhs_layout {
|
||||
Layout::Builtin(Builtin::Int8) => LLVM_SSUB_WITH_OVERFLOW_I8,
|
||||
Layout::Builtin(Builtin::Int16) => LLVM_SSUB_WITH_OVERFLOW_I16,
|
||||
@ -5339,59 +5339,16 @@ fn build_int_binop<'a, 'ctx, 'env>(
|
||||
.call_intrinsic(intrinsic, &[lhs.into(), rhs.into()])
|
||||
.into_struct_value();
|
||||
|
||||
let sub_result = bd.build_extract_value(result, 0, "sub_result").unwrap();
|
||||
let has_overflowed = bd.build_extract_value(result, 1, "has_overflowed").unwrap();
|
||||
|
||||
let condition = bd.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
has_overflowed.into_int_value(),
|
||||
context.bool_type().const_zero(),
|
||||
"has_not_overflowed",
|
||||
);
|
||||
|
||||
let then_block = context.append_basic_block(parent, "then_block");
|
||||
let throw_block = context.append_basic_block(parent, "throw_block");
|
||||
|
||||
bd.build_conditional_branch(condition, then_block, throw_block);
|
||||
|
||||
bd.position_at_end(throw_block);
|
||||
|
||||
throw_exception(env, "integer subtraction overflowed!");
|
||||
|
||||
bd.position_at_end(then_block);
|
||||
|
||||
sub_result
|
||||
throw_on_overflow(env, parent, result, "integer subtraction overflowed!")
|
||||
}
|
||||
NumSubWrap => bd.build_int_sub(lhs, rhs, "sub_int").into(),
|
||||
NumSubChecked => env.call_intrinsic(LLVM_SSUB_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]),
|
||||
NumMul => {
|
||||
let context = env.context;
|
||||
let result = env
|
||||
.call_intrinsic(LLVM_SMUL_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()])
|
||||
.into_struct_value();
|
||||
|
||||
let mul_result = bd.build_extract_value(result, 0, "mul_result").unwrap();
|
||||
let has_overflowed = bd.build_extract_value(result, 1, "has_overflowed").unwrap();
|
||||
|
||||
let condition = bd.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
has_overflowed.into_int_value(),
|
||||
context.bool_type().const_zero(),
|
||||
"has_not_overflowed",
|
||||
);
|
||||
|
||||
let then_block = context.append_basic_block(parent, "then_block");
|
||||
let throw_block = context.append_basic_block(parent, "throw_block");
|
||||
|
||||
bd.build_conditional_branch(condition, then_block, throw_block);
|
||||
|
||||
bd.position_at_end(throw_block);
|
||||
|
||||
throw_exception(env, "integer multiplication overflowed!");
|
||||
|
||||
bd.position_at_end(then_block);
|
||||
|
||||
mul_result
|
||||
throw_on_overflow(env, parent, result, "integer multiplication overflowed!")
|
||||
}
|
||||
NumMulWrap => bd.build_int_mul(lhs, rhs, "mul_int").into(),
|
||||
NumMulChecked => env.call_intrinsic(LLVM_SMUL_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]),
|
||||
@ -5530,6 +5487,9 @@ pub fn build_num_binop<'a, 'ctx, 'env>(
|
||||
rhs_layout,
|
||||
op,
|
||||
),
|
||||
Decimal => {
|
||||
build_dec_binop(env, parent, lhs_arg, lhs_layout, rhs_arg, rhs_layout, op)
|
||||
}
|
||||
_ => {
|
||||
unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, lhs_layout);
|
||||
}
|
||||
@ -5716,6 +5676,75 @@ fn build_float_binop<'a, 'ctx, 'env>(
|
||||
}
|
||||
}
|
||||
|
||||
fn build_dec_binop<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
lhs: BasicValueEnum<'ctx>,
|
||||
_lhs_layout: &Layout<'a>,
|
||||
rhs: BasicValueEnum<'ctx>,
|
||||
_rhs_layout: &Layout<'a>,
|
||||
op: LowLevel,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
use roc_module::low_level::LowLevel::*;
|
||||
|
||||
match op {
|
||||
NumAddChecked => call_bitcode_fn(env, &[lhs, rhs], &bitcode::DEC_ADD_WITH_OVERFLOW),
|
||||
NumSubChecked => call_bitcode_fn(env, &[lhs, rhs], &bitcode::DEC_SUB_WITH_OVERFLOW),
|
||||
NumMulChecked => call_bitcode_fn(env, &[lhs, rhs], &bitcode::DEC_MUL_WITH_OVERFLOW),
|
||||
NumAdd => build_dec_binop_throw_on_overflow(
|
||||
env,
|
||||
parent,
|
||||
bitcode::DEC_ADD_WITH_OVERFLOW,
|
||||
lhs,
|
||||
rhs,
|
||||
"decimal addition overflowed",
|
||||
),
|
||||
NumSub => build_dec_binop_throw_on_overflow(
|
||||
env,
|
||||
parent,
|
||||
bitcode::DEC_SUB_WITH_OVERFLOW,
|
||||
lhs,
|
||||
rhs,
|
||||
"decimal subtraction overflowed",
|
||||
),
|
||||
NumMul => build_dec_binop_throw_on_overflow(
|
||||
env,
|
||||
parent,
|
||||
bitcode::DEC_MUL_WITH_OVERFLOW,
|
||||
lhs,
|
||||
rhs,
|
||||
"decimal multiplication overflowed",
|
||||
),
|
||||
NumDivUnchecked => call_bitcode_fn(env, &[lhs, rhs], &bitcode::DEC_DIV),
|
||||
_ => {
|
||||
unreachable!("Unrecognized int binary operation: {:?}", op);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_dec_binop_throw_on_overflow<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
operation: &str,
|
||||
lhs: BasicValueEnum<'ctx>,
|
||||
rhs: BasicValueEnum<'ctx>,
|
||||
message: &str,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let overflow_type = crate::llvm::convert::zig_with_overflow_roc_dec(env);
|
||||
|
||||
let result_ptr = env.builder.build_alloca(overflow_type, "result_ptr");
|
||||
call_void_bitcode_fn(env, &[result_ptr.into(), lhs, rhs], operation);
|
||||
|
||||
let result = env
|
||||
.builder
|
||||
.build_load(result_ptr, "load_overflow")
|
||||
.into_struct_value();
|
||||
|
||||
let value = throw_on_overflow(env, parent, result, message).into_struct_value();
|
||||
|
||||
env.builder.build_extract_value(value, 0, "num").unwrap()
|
||||
}
|
||||
|
||||
fn int_type_signed_min(int_type: IntType) -> IntValue {
|
||||
let width = int_type.get_bit_width();
|
||||
|
||||
|
@ -128,6 +128,7 @@ fn hash_builtin<'a, 'ctx, 'env>(
|
||||
| Builtin::Float32
|
||||
| Builtin::Float128
|
||||
| Builtin::Float16
|
||||
| Builtin::Decimal
|
||||
| Builtin::Usize => {
|
||||
let hash_bytes = store_and_use_as_u8_ptr(env, val, &layout);
|
||||
hash_bitcode_fn(env, seed, hash_bytes, layout.stack_size(ptr_bytes))
|
||||
|
@ -1,5 +1,7 @@
|
||||
use crate::llvm::build::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV};
|
||||
use crate::llvm::build::{tag_pointer_clear_tag_id, Env};
|
||||
use crate::llvm::bitcode::call_bitcode_fn;
|
||||
use crate::llvm::build::{
|
||||
cast_block_of_memory_to_tag, get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV,
|
||||
};
|
||||
use crate::llvm::build_list::{list_len, load_list_ptr};
|
||||
use crate::llvm::build_str::str_equal;
|
||||
use crate::llvm::convert::basic_type_from_layout;
|
||||
@ -9,6 +11,7 @@ use inkwell::values::{
|
||||
BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
|
||||
};
|
||||
use inkwell::{AddressSpace, FloatPredicate, IntPredicate};
|
||||
use roc_builtins::bitcode;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
|
||||
|
||||
@ -96,6 +99,7 @@ fn build_eq_builtin<'a, 'ctx, 'env>(
|
||||
|
||||
Builtin::Usize => int_cmp(IntPredicate::EQ, "eq_usize"),
|
||||
|
||||
Builtin::Decimal => call_bitcode_fn(env, &[lhs_val, rhs_val], &bitcode::DEC_EQ),
|
||||
Builtin::Float128 => float_cmp(FloatPredicate::OEQ, "eq_f128"),
|
||||
Builtin::Float64 => float_cmp(FloatPredicate::OEQ, "eq_f64"),
|
||||
Builtin::Float32 => float_cmp(FloatPredicate::OEQ, "eq_f32"),
|
||||
@ -241,6 +245,7 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
|
||||
|
||||
Builtin::Usize => int_cmp(IntPredicate::NE, "neq_usize"),
|
||||
|
||||
Builtin::Decimal => call_bitcode_fn(env, &[lhs_val, rhs_val], &bitcode::DEC_NEQ),
|
||||
Builtin::Float128 => float_cmp(FloatPredicate::ONE, "neq_f128"),
|
||||
Builtin::Float64 => float_cmp(FloatPredicate::ONE, "neq_f64"),
|
||||
Builtin::Float32 => float_cmp(FloatPredicate::ONE, "neq_f32"),
|
||||
|
@ -97,6 +97,7 @@ pub fn basic_type_from_builtin<'a, 'ctx, 'env>(
|
||||
Int8 => context.i8_type().as_basic_type_enum(),
|
||||
Int1 => context.bool_type().as_basic_type_enum(),
|
||||
Usize => ptr_int(context, ptr_bytes).as_basic_type_enum(),
|
||||
Decimal => context.i128_type().as_basic_type_enum(),
|
||||
Float128 => context.f128_type().as_basic_type_enum(),
|
||||
Float64 => context.f64_type().as_basic_type_enum(),
|
||||
Float32 => context.f32_type().as_basic_type_enum(),
|
||||
@ -221,3 +222,11 @@ pub fn zig_has_tag_id_type<'a, 'ctx, 'env>(
|
||||
) -> StructType<'ctx> {
|
||||
env.module.get_struct_type("list.HasTagId").unwrap()
|
||||
}
|
||||
|
||||
pub fn zig_with_overflow_roc_dec<'a, 'ctx, 'env>(
|
||||
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
|
||||
) -> StructType<'ctx> {
|
||||
env.module
|
||||
.get_struct_type("utils.WithOverflow(dec.RocDec)")
|
||||
.unwrap()
|
||||
}
|
||||
|
@ -873,6 +873,9 @@ define_builtins! {
|
||||
97 NUM_INT_CAST: "intCast"
|
||||
98 NUM_MAX_I128: "maxI128"
|
||||
99 NUM_IS_MULTIPLE_OF: "isMultipleOf"
|
||||
100 NUM_AT_DECIMAL: "@Decimal"
|
||||
101 NUM_DECIMAL: "Decimal" imported
|
||||
102 NUM_DEC: "Dec" imported // the Num.Dectype alias
|
||||
|
||||
}
|
||||
2 BOOL: "Bool" => {
|
||||
|
@ -1261,7 +1261,7 @@ fn builtin_spec(
|
||||
|
||||
match builtin {
|
||||
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize => builder.add_tuple_type(&[]),
|
||||
Float128 | Float64 | Float32 | Float16 => builder.add_tuple_type(&[]),
|
||||
Decimal | Float128 | Float64 | Float32 | Float16 => builder.add_tuple_type(&[]),
|
||||
Str | EmptyStr => str_type(builder),
|
||||
Dict(key_layout, value_layout) => {
|
||||
let value_type = layout_spec_help(builder, value_layout, when_recursive)?;
|
||||
|
@ -2733,10 +2733,10 @@ pub fn with_hole<'a>(
|
||||
Layout::Builtin(float_precision_to_builtin(precision)),
|
||||
hole,
|
||||
),
|
||||
IntOrFloat::DecimalFloatType(precision) => Stmt::Let(
|
||||
IntOrFloat::DecimalFloatType => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(Literal::Float(num as f64)),
|
||||
Layout::Builtin(float_precision_to_builtin(precision)),
|
||||
Layout::Builtin(Builtin::Decimal),
|
||||
hole,
|
||||
),
|
||||
_ => unreachable!("unexpected float precision for integer"),
|
||||
@ -2769,10 +2769,10 @@ pub fn with_hole<'a>(
|
||||
Layout::Builtin(float_precision_to_builtin(precision)),
|
||||
hole,
|
||||
),
|
||||
IntOrFloat::DecimalFloatType(precision) => Stmt::Let(
|
||||
IntOrFloat::DecimalFloatType => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(Literal::Float(num as f64)),
|
||||
Layout::Builtin(float_precision_to_builtin(precision)),
|
||||
Layout::Builtin(Builtin::Decimal),
|
||||
hole,
|
||||
),
|
||||
},
|
||||
@ -6870,7 +6870,7 @@ fn from_can_pattern_help<'a>(
|
||||
IntOrFloat::SignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)),
|
||||
IntOrFloat::UnsignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)),
|
||||
IntOrFloat::BinaryFloatType(_) => Ok(Pattern::FloatLiteral(*num as u64)),
|
||||
IntOrFloat::DecimalFloatType(_) => Ok(Pattern::FloatLiteral(*num as u64)),
|
||||
IntOrFloat::DecimalFloatType => Ok(Pattern::FloatLiteral(*num as u64)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -7460,7 +7460,7 @@ pub enum IntOrFloat {
|
||||
SignedIntType(IntPrecision),
|
||||
UnsignedIntType(IntPrecision),
|
||||
BinaryFloatType(FloatPrecision),
|
||||
DecimalFloatType(FloatPrecision),
|
||||
DecimalFloatType,
|
||||
}
|
||||
|
||||
fn float_precision_to_builtin(precision: FloatPrecision) -> Builtin<'static> {
|
||||
@ -7563,6 +7563,10 @@ pub fn num_argument_to_int_or_float(
|
||||
| Content::Alias(Symbol::NUM_AT_BINARY64, _, _) => {
|
||||
IntOrFloat::BinaryFloatType(FloatPrecision::F64)
|
||||
}
|
||||
Content::Alias(Symbol::NUM_DECIMAL, _, _)
|
||||
| Content::Alias(Symbol::NUM_AT_DECIMAL, _, _) => {
|
||||
IntOrFloat::DecimalFloatType
|
||||
}
|
||||
Content::Alias(Symbol::NUM_F32, _, _)
|
||||
| Content::Alias(Symbol::NUM_BINARY32, _, _)
|
||||
| Content::Alias(Symbol::NUM_AT_BINARY32, _, _) => {
|
||||
|
@ -460,6 +460,7 @@ pub enum Builtin<'a> {
|
||||
Int8,
|
||||
Int1,
|
||||
Usize,
|
||||
Decimal,
|
||||
Float128,
|
||||
Float64,
|
||||
Float32,
|
||||
@ -952,6 +953,7 @@ impl<'a> Builtin<'a> {
|
||||
const I8_SIZE: u32 = std::mem::size_of::<i8>() as u32;
|
||||
const I1_SIZE: u32 = std::mem::size_of::<bool>() as u32;
|
||||
const USIZE_SIZE: u32 = std::mem::size_of::<usize>() as u32;
|
||||
const DECIMAL_SIZE: u32 = std::mem::size_of::<i128>() as u32;
|
||||
const F128_SIZE: u32 = 16;
|
||||
const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32;
|
||||
const F32_SIZE: u32 = std::mem::size_of::<f32>() as u32;
|
||||
@ -981,6 +983,7 @@ impl<'a> Builtin<'a> {
|
||||
Int8 => Builtin::I8_SIZE,
|
||||
Int1 => Builtin::I1_SIZE,
|
||||
Usize => Builtin::USIZE_SIZE,
|
||||
Decimal => Builtin::DECIMAL_SIZE,
|
||||
Float128 => Builtin::F128_SIZE,
|
||||
Float64 => Builtin::F64_SIZE,
|
||||
Float32 => Builtin::F32_SIZE,
|
||||
@ -1007,6 +1010,7 @@ impl<'a> Builtin<'a> {
|
||||
Int8 => align_of::<i8>() as u32,
|
||||
Int1 => align_of::<bool>() as u32,
|
||||
Usize => align_of::<usize>() as u32,
|
||||
Decimal => align_of::<i128>() as u32,
|
||||
Float128 => align_of::<i128>() as u32,
|
||||
Float64 => align_of::<f64>() as u32,
|
||||
Float32 => align_of::<f32>() as u32,
|
||||
@ -1022,8 +1026,8 @@ impl<'a> Builtin<'a> {
|
||||
use Builtin::*;
|
||||
|
||||
match self {
|
||||
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Float128 | Float64 | Float32
|
||||
| Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => true,
|
||||
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64
|
||||
| Float32 | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => true,
|
||||
Str | Dict(_, _) | Set(_) | List(_) => false,
|
||||
}
|
||||
}
|
||||
@ -1033,8 +1037,8 @@ impl<'a> Builtin<'a> {
|
||||
use Builtin::*;
|
||||
|
||||
match self {
|
||||
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Float128 | Float64 | Float32
|
||||
| Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => false,
|
||||
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64
|
||||
| Float32 | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => false,
|
||||
List(_) => true,
|
||||
|
||||
Str | Dict(_, _) | Set(_) => true,
|
||||
@ -1057,6 +1061,7 @@ impl<'a> Builtin<'a> {
|
||||
Int8 => alloc.text("Int8"),
|
||||
Int1 => alloc.text("Int1"),
|
||||
Usize => alloc.text("Usize"),
|
||||
Decimal => alloc.text("Decimal"),
|
||||
Float128 => alloc.text("Float128"),
|
||||
Float64 => alloc.text("Float64"),
|
||||
Float32 => alloc.text("Float32"),
|
||||
@ -1144,6 +1149,10 @@ fn layout_from_flat_type<'a>(
|
||||
}
|
||||
|
||||
// Floats
|
||||
Symbol::NUM_DEC => {
|
||||
debug_assert_eq!(args.len(), 0);
|
||||
Ok(Layout::Builtin(Builtin::Decimal))
|
||||
}
|
||||
Symbol::NUM_F64 => {
|
||||
debug_assert_eq!(args.len(), 0);
|
||||
Ok(Layout::Builtin(Builtin::Float64))
|
||||
@ -1900,6 +1909,7 @@ fn layout_from_num_content<'a>(content: Content) -> Result<Layout<'a>, LayoutPro
|
||||
|
||||
// Floats
|
||||
Symbol::NUM_FLOATINGPOINT => Ok(Layout::Builtin(Builtin::Float64)),
|
||||
Symbol::NUM_DEC => Ok(Layout::Builtin(Builtin::Decimal)),
|
||||
Symbol::NUM_F64 => Ok(Layout::Builtin(Builtin::Float64)),
|
||||
Symbol::NUM_F32 => Ok(Layout::Builtin(Builtin::Float32)),
|
||||
|
||||
@ -1975,6 +1985,11 @@ fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result<Layout<'a>, LayoutPr
|
||||
|
||||
Ok(Layout::Builtin(Builtin::Float64))
|
||||
}
|
||||
Content::Alias(Symbol::NUM_DECIMAL, args, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
|
||||
Ok(Layout::Builtin(Builtin::Decimal))
|
||||
}
|
||||
Content::FlexVar(_) | Content::RigidVar(_) => {
|
||||
// default to f64
|
||||
Ok(Layout::Builtin(Builtin::Float64))
|
||||
|
@ -721,10 +721,10 @@ mod test_reporting {
|
||||
|
||||
these names seem close though:
|
||||
|
||||
Decimal
|
||||
Dec
|
||||
Result
|
||||
Num
|
||||
Set
|
||||
U8
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
@ -172,6 +172,21 @@ mod solve_expr {
|
||||
infer_eq("0.5", "Float *");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dec_literal() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
val : Dec
|
||||
val = 1.2
|
||||
|
||||
val
|
||||
"#
|
||||
),
|
||||
"Dec",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_literal() {
|
||||
infer_eq(
|
||||
|
@ -3,7 +3,7 @@ mod gen_num {
|
||||
use crate::assert_evals_to;
|
||||
use crate::assert_llvm_evals_to;
|
||||
use indoc::indoc;
|
||||
use roc_std::RocOrder;
|
||||
use roc_std::{RocDec, RocOrder};
|
||||
|
||||
#[test]
|
||||
fn nat_alias() {
|
||||
@ -328,6 +328,22 @@ mod gen_num {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dec_float_alias() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : Dec
|
||||
x = 2.1
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
RocDec::from_str_to_i128_unsafe(&"2.1"),
|
||||
i128
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f64_float_alias() {
|
||||
assert_evals_to!(
|
||||
@ -543,6 +559,27 @@ mod gen_num {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_add_dec() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : Dec
|
||||
x = 2.1
|
||||
|
||||
y : Dec
|
||||
y = 3.1
|
||||
|
||||
z : Dec
|
||||
z = x + y
|
||||
|
||||
z
|
||||
"#
|
||||
),
|
||||
RocDec::from_str_to_i128_unsafe(&"5.2"),
|
||||
i128
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn gen_add_f64() {
|
||||
assert_evals_to!(
|
||||
@ -586,6 +623,26 @@ mod gen_num {
|
||||
f64
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn gen_div_dec() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : Dec
|
||||
x = 10
|
||||
|
||||
y : Dec
|
||||
y = 3
|
||||
|
||||
when x / y is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
"#
|
||||
),
|
||||
RocDec::from_str_to_i128_unsafe(&"3.333333333333333333"),
|
||||
i128
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_int_eq() {
|
||||
@ -613,6 +670,44 @@ mod gen_num {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_dec_eq() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : Dec
|
||||
x = 4
|
||||
|
||||
y : Dec
|
||||
y = 4
|
||||
|
||||
x == y
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_dec_neq() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : Dec
|
||||
x = 4
|
||||
|
||||
y : Dec
|
||||
y = 5
|
||||
|
||||
x != y
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_wrap_int_neq() {
|
||||
assert_evals_to!(
|
||||
@ -643,6 +738,28 @@ mod gen_num {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_sub_dec() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : Dec
|
||||
x = 1.5
|
||||
|
||||
y : Dec
|
||||
y = 2.4
|
||||
|
||||
z : Dec
|
||||
z = 3
|
||||
|
||||
(x - y) - z
|
||||
"#
|
||||
),
|
||||
RocDec::from_str_to_i128_unsafe(&"-3.9"),
|
||||
i128
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_sub_f64() {
|
||||
assert_evals_to!(
|
||||
@ -669,6 +786,27 @@ mod gen_num {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_mul_dec() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : Dec
|
||||
x = 2
|
||||
|
||||
y : Dec
|
||||
y = 4
|
||||
|
||||
z : Dec
|
||||
z = 6
|
||||
|
||||
x * y * z
|
||||
"#
|
||||
),
|
||||
RocDec::from_str_to_i128_unsafe(&"48.0"),
|
||||
i128
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn gen_mul_i64() {
|
||||
assert_evals_to!(
|
||||
|
@ -239,6 +239,16 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
|
||||
},
|
||||
);
|
||||
|
||||
// Decimal : [ @Decimal ]
|
||||
add_alias(
|
||||
Symbol::NUM_DECIMAL,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: vec![],
|
||||
typ: decimal_alias_content(),
|
||||
},
|
||||
);
|
||||
|
||||
// Binary64 : [ @Binary64 ]
|
||||
add_alias(
|
||||
Symbol::NUM_BINARY64,
|
||||
@ -269,6 +279,16 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
|
||||
},
|
||||
);
|
||||
|
||||
// Dec : Float Decimal
|
||||
add_alias(
|
||||
Symbol::NUM_DEC,
|
||||
BuiltinAlias {
|
||||
region: Region::zero(),
|
||||
vars: Vec::new(),
|
||||
typ: float_alias_content(decimal_type()),
|
||||
},
|
||||
);
|
||||
|
||||
// F64 : Float Binary64
|
||||
add_alias(
|
||||
Symbol::NUM_F64,
|
||||
@ -657,6 +677,20 @@ fn unsigned8_alias_content() -> SolvedType {
|
||||
single_private_tag(Symbol::NUM_AT_UNSIGNED8, vec![])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn decimal_alias_content() -> SolvedType {
|
||||
single_private_tag(Symbol::NUM_AT_DECIMAL, vec![])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn decimal_type() -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
Symbol::NUM_DECIMAL,
|
||||
vec![],
|
||||
Box::new(decimal_alias_content()),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn bool_type() -> SolvedType {
|
||||
SolvedType::Alias(
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![crate_type = "lib"]
|
||||
#![no_std]
|
||||
use core::convert::From;
|
||||
use core::ffi::c_void;
|
||||
use core::{fmt, mem, ptr};
|
||||
|
||||
@ -702,3 +703,72 @@ impl<'a, T: Sized + Copy> From<&'a RocCallResult<T>> for Result<T, &'a str> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct RocDec(pub i128);
|
||||
|
||||
impl RocDec {
|
||||
pub const MIN: Self = Self(i128::MIN);
|
||||
pub const MAX: Self = Self(i128::MAX);
|
||||
|
||||
pub const DECIMAL_PLACES: u32 = 18;
|
||||
|
||||
pub const ONE_POINT_ZERO: i128 = 10i128.pow(Self::DECIMAL_PLACES);
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn from_str(value: &str) -> Option<Self> {
|
||||
// Split the string into the parts before and after the "."
|
||||
let mut parts = value.split('.');
|
||||
|
||||
let before_point = match parts.next() {
|
||||
Some(answer) => answer,
|
||||
None => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let after_point = match parts.next() {
|
||||
Some(answer) if answer.len() <= Self::DECIMAL_PLACES as usize => answer,
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// There should have only been one "." in the string!
|
||||
if parts.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Calculate the low digits - the ones after the decimal point.
|
||||
let lo = match after_point.parse::<i128>() {
|
||||
Ok(answer) => {
|
||||
// Translate e.g. the 1 from 0.1 into 10000000000000000000
|
||||
// by "restoring" the elided trailing zeroes to the number!
|
||||
let trailing_zeroes = Self::DECIMAL_PLACES as usize - after_point.len();
|
||||
let lo = answer * 10i128.pow(trailing_zeroes as u32);
|
||||
|
||||
if !before_point.starts_with('-') {
|
||||
lo
|
||||
} else {
|
||||
-lo
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate the high digits - the ones before the decimal point.
|
||||
match before_point.parse::<i128>() {
|
||||
Ok(answer) => match answer.checked_mul(10i128.pow(Self::DECIMAL_PLACES)) {
|
||||
Some(hi) => hi.checked_add(lo).map(RocDec),
|
||||
None => None,
|
||||
},
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str_to_i128_unsafe(val: &str) -> i128 {
|
||||
RocDec::from_str(val).unwrap().0
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user