Merge pull request #1435 from rtfeldman/add-dec-types

Add Dec types & basic operations
This commit is contained in:
Jared Ramirez 2021-07-22 12:57:33 -07:00 committed by GitHub
commit 63932b9b73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 766 additions and 395 deletions

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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";

View File

@ -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

View File

@ -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();

View File

@ -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))

View File

@ -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"),

View File

@ -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()
}

View File

@ -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" => {

View File

@ -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)?;

View File

@ -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, _, _) => {

View File

@ -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))

View File

@ -721,10 +721,10 @@ mod test_reporting {
these names seem close though:
Decimal
Dec
Result
Num
Set
U8
"#
),
);

View File

@ -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(

View File

@ -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!(

View File

@ -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(

View File

@ -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
}
}