make decimal math ops correctly report overflow

This commit is contained in:
Folkert 2021-07-18 22:04:32 +02:00
parent 2b5ec3dcf1
commit 23ea151d5f
8 changed files with 155 additions and 165 deletions

View file

@ -1,9 +1,11 @@
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 = extern struct {
num: i128,
@ -215,29 +217,41 @@ pub const RocDec = extern 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 };
} else {
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 {
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 };
} else {
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 {
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;
@ -246,30 +260,40 @@ pub const RocDec = extern 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 {
@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 {
@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;
}
}
@ -1039,16 +1063,16 @@ 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) i128 {
return @call(.{ .modifier = always_inline }, RocDec.add, .{ arg1, arg2 }).num;
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) i128 {
return @call(.{ .modifier = always_inline }, RocDec.sub, .{ arg1, arg2 }).num;
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) i128 {
return @call(.{ .modifier = always_inline }, RocDec.mul, .{ arg1, arg2 }).num;
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 {

View file

@ -10,9 +10,9 @@ comptime {
exportDecFn(dec.eqC, "eq");
exportDecFn(dec.neqC, "neq");
exportDecFn(dec.negateC, "negate");
exportDecFn(dec.addC, "add");
exportDecFn(dec.subC, "sub");
exportDecFn(dec.mulC, "mul");
exportDecFn(dec.addC, "add_with_overflow");
exportDecFn(dec.subC, "sub_with_overflow");
exportDecFn(dec.mulC, "mul_with_overflow");
exportDecFn(dec.divC, "div");
}

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;