mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 06:55:15 +00:00
1285 lines
40 KiB
Zig
1285 lines
40 KiB
Zig
const std = @import("std");
|
|
const str = @import("str.zig");
|
|
const num_ = @import("num.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;
|
|
const roc_panic = @import("panic.zig").panic_help;
|
|
const U256 = num_.U256;
|
|
const mul_u128 = num_.mul_u128;
|
|
|
|
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 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) };
|
|
|
|
pub const one_point_zero_i128: i128 = math.pow(i128, 10, RocDec.decimal_places);
|
|
pub const one_point_zero: RocDec = .{ .num = one_point_zero_i128 };
|
|
|
|
pub fn fromU64(num: u64) RocDec {
|
|
return .{ .num = num * one_point_zero_i128 };
|
|
}
|
|
|
|
pub fn fromF64(num: f64) ?RocDec {
|
|
var result: f64 = num * comptime @intToFloat(f64, one_point_zero_i128);
|
|
|
|
if (result > comptime @intToFloat(f64, math.maxInt(i128))) {
|
|
return null;
|
|
}
|
|
|
|
if (result < comptime @intToFloat(f64, math.minInt(i128))) {
|
|
return null;
|
|
}
|
|
|
|
var ret: RocDec = .{ .num = @floatToInt(i128, result) };
|
|
return ret;
|
|
}
|
|
|
|
pub fn toF64(dec: RocDec) f64 {
|
|
return @intToFloat(f64, dec.num) / comptime @intToFloat(f64, one_point_zero_i128);
|
|
}
|
|
|
|
// TODO: If Str.toDec eventually supports more error types, return errors here.
|
|
// For now, just return null which will give the default error.
|
|
pub fn fromStr(roc_str: RocStr) ?RocDec {
|
|
if (roc_str.isEmpty()) {
|
|
return null;
|
|
}
|
|
|
|
const length = roc_str.len();
|
|
|
|
const roc_str_slice = roc_str.asSlice();
|
|
|
|
var is_negative: bool = roc_str_slice[0] == '-';
|
|
var initial_index: usize = if (is_negative) 1 else 0;
|
|
|
|
var point_index: ?usize = null;
|
|
var index: usize = initial_index;
|
|
while (index < length) {
|
|
var byte: u8 = roc_str_slice[index];
|
|
if (byte == '.' and point_index == null) {
|
|
point_index = index;
|
|
index += 1;
|
|
continue;
|
|
}
|
|
|
|
if (!isDigit(byte)) {
|
|
return null;
|
|
}
|
|
index += 1;
|
|
}
|
|
|
|
var before_str_length = length;
|
|
var after_val_i128: ?i128 = null;
|
|
if (point_index) |pi| {
|
|
before_str_length = pi;
|
|
|
|
var after_str_len = (length - 1) - pi;
|
|
if (after_str_len > decimal_places) {
|
|
// TODO: runtime exception for too many decimal places!
|
|
return null;
|
|
}
|
|
var diff_decimal_places = decimal_places - after_str_len;
|
|
|
|
var after_str = roc_str_slice[pi + 1 .. length];
|
|
var after_u64 = std.fmt.parseUnsigned(u64, after_str, 10) catch null;
|
|
after_val_i128 = if (after_u64) |f| @intCast(i128, f) * math.pow(i128, 10, diff_decimal_places) else null;
|
|
}
|
|
|
|
var before_str = roc_str_slice[initial_index..before_str_length];
|
|
var before_val_not_adjusted = std.fmt.parseUnsigned(i128, before_str, 10) catch null;
|
|
|
|
var before_val_i128: ?i128 = null;
|
|
if (before_val_not_adjusted) |before| {
|
|
var result: i128 = undefined;
|
|
var overflowed = @mulWithOverflow(i128, before, one_point_zero_i128, &result);
|
|
if (overflowed) {
|
|
// TODO: runtime exception for overflow!
|
|
return null;
|
|
}
|
|
before_val_i128 = result;
|
|
}
|
|
|
|
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) {
|
|
// TODO: runtime exception for overflow!
|
|
return null;
|
|
}
|
|
break :blk .{ .num = result };
|
|
} else {
|
|
break :blk .{ .num = before };
|
|
}
|
|
} else if (after_val_i128) |after| {
|
|
break :blk .{ .num = after };
|
|
} else {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
if (is_negative) {
|
|
return dec.negate();
|
|
} else {
|
|
return dec;
|
|
}
|
|
}
|
|
|
|
inline fn isDigit(c: u8) bool {
|
|
return (c -% 48) <= 9;
|
|
}
|
|
|
|
pub fn toStr(self: RocDec) RocStr {
|
|
// Special case
|
|
if (self.num == 0) {
|
|
return RocStr.init("0.0", 3);
|
|
}
|
|
|
|
const num = self.num;
|
|
const is_negative = num < 0;
|
|
|
|
// 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, .lower, .{});
|
|
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_offset: usize = 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;
|
|
|
|
for (digit_bytes[0..before_digits_offset]) |c| {
|
|
str_bytes[position] = c;
|
|
position += 1;
|
|
}
|
|
} else {
|
|
// otherwise there are no actual digits before the decimal point
|
|
// but we format it with a '0'
|
|
str_bytes[position] = '0';
|
|
position += 1;
|
|
}
|
|
|
|
// we've done everything before the decimal point, so now we can put the decimal point in
|
|
str_bytes[position] = '.';
|
|
position += 1;
|
|
|
|
const trailing_zeros: u6 = count_trailing_zeros_base10(num);
|
|
if (trailing_zeros >= decimal_places) {
|
|
// add just a single zero if all decimal digits are zero
|
|
str_bytes[position] = '0';
|
|
position += 1;
|
|
} else {
|
|
// Figure out if we need to prepend any zeros to the after decimal point
|
|
// 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;
|
|
|
|
var i: usize = 0;
|
|
while (i < after_zeros_num) : (i += 1) {
|
|
str_bytes[position] = '0';
|
|
position += 1;
|
|
}
|
|
|
|
// 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 toI128(self: RocDec) i128 {
|
|
return self.num;
|
|
}
|
|
|
|
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 {
|
|
var negated = math.negate(self.num) catch null;
|
|
return if (negated) |n| .{ .num = n } else null;
|
|
}
|
|
|
|
pub fn addWithOverflow(self: RocDec, other: RocDec) WithOverflow(RocDec) {
|
|
var answer: i128 = undefined;
|
|
const overflowed = @addWithOverflow(i128, self.num, other.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) {
|
|
roc_panic("Decimal addition overflowed!", 0);
|
|
unreachable;
|
|
} else {
|
|
return answer.value;
|
|
}
|
|
}
|
|
|
|
pub fn addSaturated(self: RocDec, other: RocDec) RocDec {
|
|
const answer = RocDec.addWithOverflow(self, other);
|
|
if (answer.has_overflowed) {
|
|
// We can unambiguously tell which way it wrapped, because we have 129 bits including the overflow bit
|
|
if (answer.value.num < 0) {
|
|
return RocDec.max;
|
|
} else {
|
|
return RocDec.min;
|
|
}
|
|
} else {
|
|
return answer.value;
|
|
}
|
|
}
|
|
|
|
pub fn subWithOverflow(self: RocDec, other: RocDec) WithOverflow(RocDec) {
|
|
var answer: i128 = undefined;
|
|
const overflowed = @subWithOverflow(i128, self.num, other.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) {
|
|
roc_panic("Decimal subtraction overflowed!", 0);
|
|
unreachable;
|
|
} else {
|
|
return answer.value;
|
|
}
|
|
}
|
|
|
|
pub fn subSaturated(self: RocDec, other: RocDec) RocDec {
|
|
const answer = RocDec.subWithOverflow(self, other);
|
|
if (answer.has_overflowed) {
|
|
if (answer.value.num < 0) {
|
|
return RocDec.max;
|
|
} else {
|
|
return RocDec.min;
|
|
}
|
|
} else {
|
|
return answer.value;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
const is_answer_negative = (self_i128 < 0) != (other_i128 < 0);
|
|
|
|
const self_u128 = @intCast(u128, math.absInt(self_i128) catch {
|
|
if (other_i128 == 0) {
|
|
return .{ .value = RocDec{ .num = 0 }, .has_overflowed = false };
|
|
} else if (other_i128 == RocDec.one_point_zero.num) {
|
|
return .{ .value = self, .has_overflowed = false };
|
|
} else if (is_answer_negative) {
|
|
return .{ .value = RocDec.min, .has_overflowed = true };
|
|
} else {
|
|
return .{ .value = RocDec.max, .has_overflowed = true };
|
|
}
|
|
});
|
|
|
|
const other_u128 = @intCast(u128, math.absInt(other_i128) catch {
|
|
if (self_i128 == 0) {
|
|
return .{ .value = RocDec{ .num = 0 }, .has_overflowed = false };
|
|
} else if (self_i128 == RocDec.one_point_zero.num) {
|
|
return .{ .value = other, .has_overflowed = false };
|
|
} else if (is_answer_negative) {
|
|
return .{ .value = RocDec.min, .has_overflowed = true };
|
|
} else {
|
|
return .{ .value = RocDec.max, .has_overflowed = true };
|
|
}
|
|
});
|
|
|
|
const unsigned_answer: i128 = mul_and_decimalize(self_u128, other_u128);
|
|
|
|
if (is_answer_negative) {
|
|
return .{ .value = RocDec{ .num = -unsigned_answer }, .has_overflowed = false };
|
|
} else {
|
|
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) {
|
|
roc_panic("Decimal multiplication overflowed!", 0);
|
|
unreachable;
|
|
} else {
|
|
return answer.value;
|
|
}
|
|
}
|
|
|
|
pub fn mulSaturated(self: RocDec, other: RocDec) RocDec {
|
|
const answer = RocDec.mulWithOverflow(self, other);
|
|
return answer.value;
|
|
}
|
|
|
|
pub fn div(self: RocDec, other: RocDec) RocDec {
|
|
const numerator_i128 = self.num;
|
|
const denominator_i128 = other.num;
|
|
|
|
// (0 / n) is always 0
|
|
if (numerator_i128 == 0) {
|
|
return RocDec{ .num = 0 };
|
|
}
|
|
|
|
// (n / 0) is an error
|
|
if (denominator_i128 == 0) {
|
|
@panic("TODO runtime exception for dividing by 0!");
|
|
}
|
|
|
|
// If they're both negative, or if neither is negative, the final answer
|
|
// is positive or zero. If one is negative and the denominator isn't, the
|
|
// final answer is negative (or zero, in which case final sign won't matter).
|
|
//
|
|
// It's important that we do this in terms of negatives, because doing
|
|
// it in terms of positives can cause bugs when one is zero.
|
|
const is_answer_negative = (numerator_i128 < 0) != (denominator_i128 < 0);
|
|
|
|
// Break the two i128s into two { hi: u64, lo: u64 } tuples, discarding
|
|
// the sign for now.
|
|
//
|
|
// We'll multiply all 4 combinations of these (hi1 x lo1, hi2 x lo2,
|
|
// hi1 x lo2, hi2 x lo1) and add them as appropriate, then apply the
|
|
// appropriate sign at the very end.
|
|
//
|
|
// We do checked_abs because if we had -i128::MAX before, this will overflow.
|
|
|
|
const numerator_abs_i128 = math.absInt(numerator_i128) catch {
|
|
// Currently, if you try to do multiplication on i64::MIN, panic
|
|
// unless you're specifically multiplying by 0 or 1.
|
|
//
|
|
// Maybe we could support more cases in the future
|
|
if (denominator_i128 == one_point_zero_i128) {
|
|
return self;
|
|
} else {
|
|
@panic("TODO runtime exception for overflow when dividing!");
|
|
}
|
|
};
|
|
const numerator_u128 = @intCast(u128, numerator_abs_i128);
|
|
|
|
const denominator_abs_i128 = math.absInt(denominator_i128) catch {
|
|
// Currently, if you try to do multiplication on i64::MIN, panic
|
|
// unless you're specifically multiplying by 0 or 1.
|
|
//
|
|
// Maybe we could support more cases in the future
|
|
if (numerator_i128 == one_point_zero_i128) {
|
|
return other;
|
|
} else {
|
|
@panic("TODO runtime exception for overflow when dividing!");
|
|
}
|
|
};
|
|
const denominator_u128 = @intCast(u128, denominator_abs_i128);
|
|
|
|
const numerator_u256: U256 = mul_u128(numerator_u128, math.pow(u128, 10, decimal_places));
|
|
const answer = div_u256_by_u128(numerator_u256, denominator_u128);
|
|
|
|
var unsigned_answer: i128 = undefined;
|
|
if (answer.hi == 0 and answer.lo <= math.maxInt(i128)) {
|
|
unsigned_answer = @intCast(i128, answer.lo);
|
|
} else {
|
|
@panic("TODO runtime exception for overflow when dividing!");
|
|
}
|
|
|
|
return RocDec{ .num = if (is_answer_negative) -unsigned_answer else unsigned_answer };
|
|
}
|
|
|
|
fn mod2pi(self: RocDec) RocDec {
|
|
// This is made to be used before calling trig functions that work on the range 0 to 2*pi.
|
|
// It should be reasonable fast (much faster than calling @mod) and much more accurate as well.
|
|
// b is 2*pi as a dec. which is 6.2831853071795864769252867665590057684
|
|
// as dec is times 10^18 so 6283185307179586476.9252867665590057684
|
|
const b0: u64 = 6283185307179586476;
|
|
// Fraction that reprensents 64 bits of precision past what dec normally supports.
|
|
// 0.9252867665590057684 as binary to 64 places.
|
|
const b1: u64 = 0b1110110011011111100101111111000111001010111000100101011111110111;
|
|
|
|
// This is dec/(b0+1), but as a multiplication.
|
|
// So dec * (1/(b0+1)). This is way faster.
|
|
const dec = self.num;
|
|
const tmp = @intCast(i128, num_.mul_u128(math.absCast(dec), 249757942369376157886101012127821356963).hi >> (190 - 128));
|
|
const q0 = if (dec < 0) -tmp else tmp;
|
|
|
|
const upper = q0 * b0;
|
|
var lower: i128 = undefined;
|
|
const overflow = @mulWithOverflow(i128, q0, b1, &lower);
|
|
// TODO: maybe write this out branchlessly.
|
|
// Currently is is probably cmovs, but could be just math?
|
|
const q0_sign: i128 =
|
|
if (q0 > 0) 1 else -1;
|
|
const overflow_val: i128 = if (overflow) q0_sign << 64 else 0;
|
|
const full = upper + @intCast(i128, lower >> 64) + overflow_val;
|
|
|
|
var out = dec - full;
|
|
if (out < 0) {
|
|
out += b0;
|
|
}
|
|
|
|
return RocDec{ .num = out };
|
|
}
|
|
|
|
// I belive the output of the trig functions is always in range of Dec.
|
|
// If not, we probably should just make it saturate the Dec.
|
|
// I don't think this should crash or return errors.
|
|
pub fn sin(self: RocDec) RocDec {
|
|
return fromF64(math.sin(self.mod2pi().toF64())).?;
|
|
}
|
|
|
|
pub fn cos(self: RocDec) RocDec {
|
|
return fromF64(math.cos(self.mod2pi().toF64())).?;
|
|
}
|
|
|
|
pub fn tan(self: RocDec) RocDec {
|
|
return fromF64(math.tan(self.mod2pi().toF64())).?;
|
|
}
|
|
|
|
pub fn asin(self: RocDec) RocDec {
|
|
return fromF64(math.asin(self.toF64())).?;
|
|
}
|
|
|
|
pub fn acos(self: RocDec) RocDec {
|
|
return fromF64(math.acos(self.toF64())).?;
|
|
}
|
|
|
|
pub fn atan(self: RocDec) RocDec {
|
|
return fromF64(math.atan(self.toF64())).?;
|
|
}
|
|
};
|
|
|
|
// 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;
|
|
}
|
|
|
|
fn mul_and_decimalize(a: u128, b: u128) i128 {
|
|
const answer_u256 = mul_u128(a, b);
|
|
|
|
var lhs_hi = answer_u256.hi;
|
|
var lhs_lo = answer_u256.lo;
|
|
|
|
// Divide - or just add 1, multiply by floor(2^315/10^18), then right shift 315 times.
|
|
// floor(2^315/10^18) is 66749594872528440074844428317798503581334516323645399060845050244444366430645
|
|
|
|
// Add 1.
|
|
// This can't overflow because the initial numbers are only 127bit due to removing the sign bit.
|
|
var overflowed = @addWithOverflow(u128, lhs_lo, 1, &lhs_lo);
|
|
lhs_hi = blk: {
|
|
if (overflowed) {
|
|
break :blk lhs_hi + 1;
|
|
} else {
|
|
break :blk lhs_hi + 0;
|
|
}
|
|
};
|
|
|
|
// This needs to do multiplication in a way that expands,
|
|
// since we throw away 315 bits we care only about the higher end, not lower.
|
|
// So like need to do high low mult with 2 U256's and then bitshift.
|
|
// I bet this has a lot of room for multiplication optimization.
|
|
const rhs_hi: u128 = 0x9392ee8e921d5d073aff322e62439fcf;
|
|
const rhs_lo: u128 = 0x32d7f344649470f90cac0c573bf9e1b5;
|
|
|
|
const ea = mul_u128(lhs_lo, rhs_lo);
|
|
const gf = mul_u128(lhs_hi, rhs_lo);
|
|
const jh = mul_u128(lhs_lo, rhs_hi);
|
|
const lk = mul_u128(lhs_hi, rhs_hi);
|
|
|
|
const e = ea.hi;
|
|
// const _a = ea.lo;
|
|
|
|
const g = gf.hi;
|
|
const f = gf.lo;
|
|
|
|
const j = jh.hi;
|
|
const h = jh.lo;
|
|
|
|
const l = lk.hi;
|
|
const k = lk.lo;
|
|
|
|
// b = e + f + h
|
|
var e_plus_f: u128 = undefined;
|
|
overflowed = @addWithOverflow(u128, e, f, &e_plus_f);
|
|
var b_carry1: u128 = undefined;
|
|
if (overflowed) {
|
|
b_carry1 = 1;
|
|
} else {
|
|
b_carry1 = 0;
|
|
}
|
|
|
|
var idk: u128 = undefined;
|
|
overflowed = @addWithOverflow(u128, e_plus_f, h, &idk);
|
|
var b_carry2: u128 = undefined;
|
|
if (overflowed) {
|
|
b_carry2 = 1;
|
|
} else {
|
|
b_carry2 = 0;
|
|
}
|
|
|
|
// c = carry + g + j + k // it doesn't say +k but I think it should be?
|
|
var g_plus_j: u128 = undefined;
|
|
overflowed = @addWithOverflow(u128, g, j, &g_plus_j);
|
|
var c_carry1: u128 = undefined;
|
|
if (overflowed) {
|
|
c_carry1 = 1;
|
|
} else {
|
|
c_carry1 = 0;
|
|
}
|
|
|
|
var g_plus_j_plus_k: u128 = undefined;
|
|
overflowed = @addWithOverflow(u128, g_plus_j, k, &g_plus_j_plus_k);
|
|
var c_carry2: u128 = undefined;
|
|
if (overflowed) {
|
|
c_carry2 = 1;
|
|
} else {
|
|
c_carry2 = 0;
|
|
}
|
|
|
|
var c_without_bcarry2: u128 = undefined;
|
|
overflowed = @addWithOverflow(u128, g_plus_j_plus_k, b_carry1, &c_without_bcarry2);
|
|
var c_carry3: u128 = undefined;
|
|
if (overflowed) {
|
|
c_carry3 = 1;
|
|
} else {
|
|
c_carry3 = 0;
|
|
}
|
|
|
|
var c: u128 = undefined;
|
|
overflowed = @addWithOverflow(u128, c_without_bcarry2, b_carry2, &c);
|
|
var c_carry4: u128 = undefined;
|
|
if (overflowed) {
|
|
c_carry4 = 1;
|
|
} else {
|
|
c_carry4 = 0;
|
|
}
|
|
|
|
// d = carry + l
|
|
var d: u128 = undefined;
|
|
overflowed = @addWithOverflow(u128, l, c_carry1, &d);
|
|
overflowed = overflowed or @addWithOverflow(u128, d, c_carry2, &d);
|
|
overflowed = overflowed or @addWithOverflow(u128, d, c_carry3, &d);
|
|
overflowed = overflowed or @addWithOverflow(u128, d, c_carry4, &d);
|
|
|
|
if (overflowed) {
|
|
@panic("TODO runtime exception for overflow!");
|
|
}
|
|
|
|
// Final 512bit value is d, c, b, a
|
|
// need to left shift 321 times
|
|
// 315 - 256 is 59. So left shift d, c 59 times.
|
|
return @intCast(i128, c >> 59 | (d << (128 - 59)));
|
|
}
|
|
|
|
// Multiply two 128-bit ints and divide the result by 10^DECIMAL_PLACES
|
|
//
|
|
// Adapted from https://github.com/nlordell/ethnum-rs/blob/c9ed57e131bffde7bcc8274f376e5becf62ef9ac/src/intrinsics/native/divmod.rs
|
|
// Copyright (c) 2020 Nicholas Rodrigues Lordello
|
|
// Licensed under the Apache License version 2.0
|
|
//
|
|
// When translating this to Zig, we often have to use math.shr/shl instead of >>/<<
|
|
// This is because casting to the right types for Zig can be kind of tricky.
|
|
// See https://github.com/ziglang/zig/issues/7605
|
|
fn div_u256_by_u128(numer: U256, denom: u128) U256 {
|
|
const N_UDWORD_BITS: u8 = 128;
|
|
const N_UTWORD_BITS: u9 = 256;
|
|
|
|
var q: U256 = undefined;
|
|
var r: U256 = undefined;
|
|
var sr: u8 = undefined;
|
|
|
|
// special case
|
|
if (numer.hi == 0) {
|
|
// 0 X
|
|
// ---
|
|
// 0 X
|
|
return .{
|
|
.hi = 0,
|
|
.lo = numer.lo / denom,
|
|
};
|
|
}
|
|
|
|
// numer.hi != 0
|
|
if (denom == 0) {
|
|
// K X
|
|
// ---
|
|
// 0 0
|
|
return .{
|
|
.hi = 0,
|
|
.lo = numer.hi / denom,
|
|
};
|
|
} else {
|
|
// K X
|
|
// ---
|
|
// 0 K
|
|
// NOTE: Modified from `if (d.low() & (d.low() - 1)) == 0`.
|
|
if (math.isPowerOfTwo(denom)) {
|
|
// if d is a power of 2
|
|
if (denom == 1) {
|
|
return numer;
|
|
}
|
|
|
|
sr = @ctz(u128, denom);
|
|
|
|
return .{
|
|
.hi = math.shr(u128, numer.hi, sr),
|
|
.lo = math.shl(u128, numer.hi, N_UDWORD_BITS - sr) | math.shr(u128, numer.lo, sr),
|
|
};
|
|
}
|
|
|
|
// K X
|
|
// ---
|
|
// 0 K
|
|
var denom_leading_zeros = @clz(u128, denom);
|
|
var numer_hi_leading_zeros = @clz(u128, numer.hi);
|
|
sr = 1 + N_UDWORD_BITS + denom_leading_zeros - numer_hi_leading_zeros;
|
|
// 2 <= sr <= N_UTWORD_BITS - 1
|
|
// q.all = n.all << (N_UTWORD_BITS - sr);
|
|
// r.all = n.all >> sr;
|
|
// #[allow(clippy::comparison_chain)]
|
|
if (sr == N_UDWORD_BITS) {
|
|
q = .{
|
|
.hi = numer.lo,
|
|
.lo = 0,
|
|
};
|
|
r = .{
|
|
.hi = 0,
|
|
.lo = numer.hi,
|
|
};
|
|
} else if (sr < N_UDWORD_BITS) {
|
|
// 2 <= sr <= N_UDWORD_BITS - 1
|
|
q = .{
|
|
.hi = math.shl(u128, numer.lo, N_UDWORD_BITS - sr),
|
|
.lo = 0,
|
|
};
|
|
r = .{
|
|
.hi = math.shr(u128, numer.hi, sr),
|
|
.lo = math.shl(u128, numer.hi, N_UDWORD_BITS - sr) | math.shr(u128, numer.lo, sr),
|
|
};
|
|
} else {
|
|
// N_UDWORD_BITS + 1 <= sr <= N_UTWORD_BITS - 1
|
|
q = .{
|
|
.hi = math.shl(u128, numer.hi, N_UTWORD_BITS - sr) | math.shr(u128, numer.lo, sr - N_UDWORD_BITS),
|
|
.lo = math.shl(u128, numer.lo, N_UTWORD_BITS - sr),
|
|
};
|
|
r = .{
|
|
.hi = 0,
|
|
.lo = math.shr(u128, numer.hi, sr - N_UDWORD_BITS),
|
|
};
|
|
}
|
|
}
|
|
|
|
// Not a special case
|
|
// q and r are initialized with:
|
|
// q.all = n.all << (N_UTWORD_BITS - sr);
|
|
// r.all = n.all >> sr;
|
|
// 1 <= sr <= N_UTWORD_BITS - 1
|
|
var carry: u128 = 0;
|
|
|
|
while (sr > 0) {
|
|
// r:q = ((r:q) << 1) | carry
|
|
r.hi = (r.hi << 1) | (r.lo >> (N_UDWORD_BITS - 1));
|
|
r.lo = (r.lo << 1) | (q.hi >> (N_UDWORD_BITS - 1));
|
|
q.hi = (q.hi << 1) | (q.lo >> (N_UDWORD_BITS - 1));
|
|
q.lo = (q.lo << 1) | carry;
|
|
|
|
// carry = 0;
|
|
// if (r.all >= d.all)
|
|
// {
|
|
// r.all -= d.all;
|
|
// carry = 1;
|
|
// }
|
|
// NOTE: Modified from `(d - r - 1) >> (N_UTWORD_BITS - 1)` to be an
|
|
// **arithmetic** shift.
|
|
|
|
var lo: u128 = undefined;
|
|
var lo_overflowed: bool = undefined;
|
|
var hi: u128 = undefined;
|
|
|
|
lo_overflowed = @subWithOverflow(u128, denom, r.lo, &lo);
|
|
hi = 0 -% @intCast(u128, @bitCast(u1, lo_overflowed)) -% r.hi;
|
|
|
|
lo_overflowed = @subWithOverflow(u128, lo, 1, &lo);
|
|
hi = hi -% @intCast(u128, @bitCast(u1, lo_overflowed));
|
|
|
|
// NOTE: this U256 was originally created by:
|
|
//
|
|
// ((hi as i128) >> 127).as_u256()
|
|
//
|
|
// As an implementation of `as_u256`, we wrap a negative value around to the maximum value of U256.
|
|
|
|
var s_u128 = math.shr(u128, hi, 127);
|
|
var s_hi: u128 = undefined;
|
|
var s_lo: u128 = undefined;
|
|
if (s_u128 == 1) {
|
|
s_hi = math.maxInt(u128);
|
|
s_lo = math.maxInt(u128);
|
|
} else {
|
|
s_hi = 0;
|
|
s_lo = 0;
|
|
}
|
|
var s = .{
|
|
.hi = s_hi,
|
|
.lo = s_lo,
|
|
};
|
|
|
|
carry = s.lo & 1;
|
|
|
|
// var (lo, carry) = r.lo.overflowing_sub(denom & s.lo);
|
|
lo_overflowed = @subWithOverflow(u128, r.lo, (denom & s.lo), &lo);
|
|
hi = r.hi -% @intCast(u128, @bitCast(u1, lo_overflowed));
|
|
|
|
r = .{ .hi = hi, .lo = lo };
|
|
|
|
sr -= 1;
|
|
}
|
|
|
|
var hi = (q.hi << 1) | (q.lo >> (127));
|
|
var lo = (q.lo << 1) | carry;
|
|
|
|
return .{ .hi = hi, .lo = lo };
|
|
}
|
|
|
|
const testing = std.testing;
|
|
const expectEqual = testing.expectEqual;
|
|
const expectError = testing.expectError;
|
|
const expectEqualSlices = testing.expectEqualSlices;
|
|
const expect = testing.expect;
|
|
|
|
test "fromU64" {
|
|
var dec = RocDec.fromU64(25);
|
|
|
|
try expectEqual(RocDec{ .num = 25000000000000000000 }, dec);
|
|
}
|
|
|
|
test "fromF64" {
|
|
var dec = RocDec.fromF64(25.5);
|
|
try expectEqual(RocDec{ .num = 25500000000000000000 }, dec.?);
|
|
}
|
|
|
|
test "fromF64 overflow" {
|
|
var dec = RocDec.fromF64(1e308);
|
|
try expectEqual(dec, null);
|
|
}
|
|
|
|
test "fromStr: empty" {
|
|
var roc_str = RocStr.init("", 0);
|
|
var dec = RocDec.fromStr(roc_str);
|
|
|
|
try expectEqual(dec, null);
|
|
}
|
|
|
|
test "fromStr: 0" {
|
|
var roc_str = RocStr.init("0", 1);
|
|
var dec = RocDec.fromStr(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = 0 }, dec.?);
|
|
}
|
|
|
|
test "fromStr: 1" {
|
|
var roc_str = RocStr.init("1", 1);
|
|
var dec = RocDec.fromStr(roc_str);
|
|
|
|
try expectEqual(RocDec.one_point_zero, dec.?);
|
|
}
|
|
|
|
test "fromStr: 123.45" {
|
|
var roc_str = RocStr.init("123.45", 6);
|
|
var dec = RocDec.fromStr(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = 123450000000000000000 }, dec.?);
|
|
}
|
|
|
|
test "fromStr: .45" {
|
|
var roc_str = RocStr.init(".45", 3);
|
|
var dec = RocDec.fromStr(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = 450000000000000000 }, dec.?);
|
|
}
|
|
|
|
test "fromStr: 0.45" {
|
|
var roc_str = RocStr.init("0.45", 4);
|
|
var dec = RocDec.fromStr(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = 450000000000000000 }, dec.?);
|
|
}
|
|
|
|
test "fromStr: 123" {
|
|
var roc_str = RocStr.init("123", 3);
|
|
var dec = RocDec.fromStr(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = 123000000000000000000 }, dec.?);
|
|
}
|
|
|
|
test "fromStr: -.45" {
|
|
var roc_str = RocStr.init("-.45", 4);
|
|
var dec = RocDec.fromStr(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = -450000000000000000 }, dec.?);
|
|
}
|
|
|
|
test "fromStr: -0.45" {
|
|
var roc_str = RocStr.init("-0.45", 5);
|
|
var dec = RocDec.fromStr(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = -450000000000000000 }, dec.?);
|
|
}
|
|
|
|
test "fromStr: -123" {
|
|
var roc_str = RocStr.init("-123", 4);
|
|
var dec = RocDec.fromStr(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = -123000000000000000000 }, dec.?);
|
|
}
|
|
|
|
test "fromStr: -123.45" {
|
|
var roc_str = RocStr.init("-123.45", 7);
|
|
var dec = RocDec.fromStr(roc_str);
|
|
|
|
try expectEqual(RocDec{ .num = -123450000000000000000 }, dec.?);
|
|
}
|
|
|
|
test "fromStr: abc" {
|
|
var roc_str = RocStr.init("abc", 3);
|
|
var dec = RocDec.fromStr(roc_str);
|
|
|
|
try expectEqual(dec, null);
|
|
}
|
|
|
|
test "fromStr: 123.abc" {
|
|
var roc_str = RocStr.init("123.abc", 7);
|
|
var dec = RocDec.fromStr(roc_str);
|
|
|
|
try expectEqual(dec, null);
|
|
}
|
|
|
|
test "fromStr: abc.123" {
|
|
var roc_str = RocStr.init("abc.123", 7);
|
|
var dec = RocDec.fromStr(roc_str);
|
|
|
|
try expectEqual(dec, null);
|
|
}
|
|
|
|
test "fromStr: .123.1" {
|
|
var roc_str = RocStr.init(".123.1", 6);
|
|
var dec = RocDec.fromStr(roc_str);
|
|
|
|
try expectEqual(dec, null);
|
|
}
|
|
|
|
test "toStr: 100.00" {
|
|
var dec: RocDec = .{ .num = 100000000000000000000 };
|
|
var res_roc_str = dec.toStr();
|
|
|
|
const res_slice: []const u8 = "100.0"[0..];
|
|
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
|
|
}
|
|
|
|
test "toStr: 123.45" {
|
|
var dec: RocDec = .{ .num = 123450000000000000000 };
|
|
var res_roc_str = dec.toStr();
|
|
|
|
const res_slice: []const u8 = "123.45"[0..];
|
|
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
|
|
}
|
|
|
|
test "toStr: -123.45" {
|
|
var dec: RocDec = .{ .num = -123450000000000000000 };
|
|
var res_roc_str = dec.toStr();
|
|
|
|
const res_slice: []const u8 = "-123.45"[0..];
|
|
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
|
|
}
|
|
|
|
test "toStr: 123.0" {
|
|
var dec: RocDec = .{ .num = 123000000000000000000 };
|
|
var res_roc_str = dec.toStr();
|
|
|
|
const res_slice: []const u8 = "123.0"[0..];
|
|
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
|
|
}
|
|
|
|
test "toStr: -123.0" {
|
|
var dec: RocDec = .{ .num = -123000000000000000000 };
|
|
var res_roc_str = dec.toStr();
|
|
|
|
const res_slice: []const u8 = "-123.0"[0..];
|
|
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
|
|
}
|
|
|
|
test "toStr: 0.45" {
|
|
var dec: RocDec = .{ .num = 450000000000000000 };
|
|
var res_roc_str = dec.toStr();
|
|
|
|
const res_slice: []const u8 = "0.45"[0..];
|
|
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
|
|
}
|
|
|
|
test "toStr: -0.45" {
|
|
var dec: RocDec = .{ .num = -450000000000000000 };
|
|
var res_roc_str = dec.toStr();
|
|
|
|
const res_slice: []const u8 = "-0.45"[0..];
|
|
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
|
|
}
|
|
|
|
test "toStr: 0.00045" {
|
|
var dec: RocDec = .{ .num = 000450000000000000 };
|
|
var res_roc_str = dec.toStr();
|
|
|
|
const res_slice: []const u8 = "0.00045"[0..];
|
|
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
|
|
}
|
|
|
|
test "toStr: -0.00045" {
|
|
var dec: RocDec = .{ .num = -000450000000000000 };
|
|
var res_roc_str = dec.toStr();
|
|
|
|
const res_slice: []const u8 = "-0.00045"[0..];
|
|
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
|
|
}
|
|
|
|
test "toStr: -111.123456" {
|
|
var dec: RocDec = .{ .num = -111123456000000000000 };
|
|
var res_roc_str = dec.toStr();
|
|
|
|
const res_slice: []const u8 = "-111.123456"[0..];
|
|
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
|
|
}
|
|
|
|
test "toStr: 123.1111111" {
|
|
var dec: RocDec = .{ .num = 123111111100000000000 };
|
|
var res_roc_str = dec.toStr();
|
|
|
|
const res_slice: []const u8 = "123.1111111"[0..];
|
|
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
|
|
}
|
|
|
|
test "toStr: 123.1111111111111 (big str)" {
|
|
var dec: RocDec = .{ .num = 123111111111111000000 };
|
|
var res_roc_str = dec.toStr();
|
|
errdefer res_roc_str.decref();
|
|
defer res_roc_str.decref();
|
|
|
|
const res_slice: []const u8 = "123.111111111111"[0..];
|
|
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
|
|
}
|
|
|
|
test "toStr: 123.111111111111444444 (max number of decimal places)" {
|
|
var dec: RocDec = .{ .num = 123111111111111444444 };
|
|
var res_roc_str = dec.toStr();
|
|
errdefer res_roc_str.decref();
|
|
defer res_roc_str.decref();
|
|
|
|
const res_slice: []const u8 = "123.111111111111444444"[0..];
|
|
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
|
|
}
|
|
|
|
test "toStr: 12345678912345678912.111111111111111111 (max number of digits)" {
|
|
var dec: RocDec = .{ .num = 12345678912345678912111111111111111111 };
|
|
var res_roc_str = dec.toStr();
|
|
errdefer res_roc_str.decref();
|
|
defer res_roc_str.decref();
|
|
|
|
const res_slice: []const u8 = "12345678912345678912.111111111111111111"[0..];
|
|
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.decref();
|
|
defer res_roc_str.decref();
|
|
|
|
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.decref();
|
|
defer res_roc_str.decref();
|
|
|
|
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();
|
|
|
|
const res_slice: []const u8 = "0.0"[0..];
|
|
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
|
|
}
|
|
|
|
test "add: 0" {
|
|
var dec: RocDec = .{ .num = 0 };
|
|
|
|
try expectEqual(RocDec{ .num = 0 }, dec.add(.{ .num = 0 }));
|
|
}
|
|
|
|
test "add: 1" {
|
|
var dec: RocDec = .{ .num = 0 };
|
|
|
|
try expectEqual(RocDec{ .num = 1 }, dec.add(.{ .num = 1 }));
|
|
}
|
|
|
|
test "sub: 0" {
|
|
var dec: RocDec = .{ .num = 1 };
|
|
|
|
try expectEqual(RocDec{ .num = 1 }, dec.sub(.{ .num = 0 }));
|
|
}
|
|
|
|
test "sub: 1" {
|
|
var dec: RocDec = .{ .num = 1 };
|
|
|
|
try expectEqual(RocDec{ .num = 0 }, dec.sub(.{ .num = 1 }));
|
|
}
|
|
|
|
test "mul: by 0" {
|
|
var dec: RocDec = .{ .num = 0 };
|
|
|
|
try expectEqual(RocDec{ .num = 0 }, dec.mul(.{ .num = 0 }));
|
|
}
|
|
|
|
test "mul: by 1" {
|
|
var dec: RocDec = RocDec.fromU64(15);
|
|
|
|
try expectEqual(RocDec.fromU64(15), dec.mul(RocDec.fromU64(1)));
|
|
}
|
|
|
|
test "mul: by 2" {
|
|
var dec: RocDec = RocDec.fromU64(15);
|
|
|
|
try expectEqual(RocDec.fromU64(30), dec.mul(RocDec.fromU64(2)));
|
|
}
|
|
|
|
test "div: 0 / 2" {
|
|
var dec: RocDec = RocDec.fromU64(0);
|
|
|
|
try expectEqual(RocDec.fromU64(0), dec.div(RocDec.fromU64(2)));
|
|
}
|
|
|
|
test "div: 2 / 2" {
|
|
var dec: RocDec = RocDec.fromU64(2);
|
|
|
|
try expectEqual(RocDec.fromU64(1), dec.div(RocDec.fromU64(2)));
|
|
}
|
|
|
|
test "div: 20 / 2" {
|
|
var dec: RocDec = RocDec.fromU64(20);
|
|
|
|
try expectEqual(RocDec.fromU64(10), dec.div(RocDec.fromU64(2)));
|
|
}
|
|
|
|
test "div: 8 / 5" {
|
|
var dec: RocDec = RocDec.fromU64(8);
|
|
var res: RocDec = RocDec.fromStr(RocStr.init("1.6", 3)).?;
|
|
try expectEqual(res, dec.div(RocDec.fromU64(5)));
|
|
}
|
|
|
|
test "div: 10 / 3" {
|
|
var numer: RocDec = RocDec.fromU64(10);
|
|
var denom: RocDec = RocDec.fromU64(3);
|
|
|
|
var roc_str = RocStr.init("3.333333333333333333", 20);
|
|
errdefer roc_str.decref();
|
|
defer roc_str.decref();
|
|
|
|
var res: RocDec = RocDec.fromStr(roc_str).?;
|
|
|
|
try expectEqual(res, numer.div(denom));
|
|
}
|
|
|
|
test "div: 341 / 341" {
|
|
var number1: RocDec = RocDec.fromU64(341);
|
|
var number2: RocDec = RocDec.fromU64(341);
|
|
try expectEqual(RocDec.fromU64(1), number1.div(number2));
|
|
}
|
|
|
|
test "div: 342 / 343" {
|
|
var number1: RocDec = RocDec.fromU64(342);
|
|
var number2: RocDec = RocDec.fromU64(343);
|
|
var roc_str = RocStr.init("0.997084548104956268", 20);
|
|
try expectEqual(RocDec.fromStr(roc_str), number1.div(number2));
|
|
}
|
|
|
|
test "div: 680 / 340" {
|
|
var number1: RocDec = RocDec.fromU64(680);
|
|
var number2: RocDec = RocDec.fromU64(340);
|
|
try expectEqual(RocDec.fromU64(2), number1.div(number2));
|
|
}
|
|
|
|
test "div: 500 / 1000" {
|
|
var number1: RocDec = RocDec.fromU64(500);
|
|
var number2: RocDec = RocDec.fromU64(1000);
|
|
var roc_str = RocStr.init("0.5", 3);
|
|
try expectEqual(RocDec.fromStr(roc_str), number1.div(number2));
|
|
}
|
|
|
|
// exports
|
|
|
|
pub fn fromStr(arg: RocStr) callconv(.C) num_.NumParseResult(i128) {
|
|
if (@call(.{ .modifier = always_inline }, RocDec.fromStr, .{arg})) |dec| {
|
|
return .{ .errorcode = 0, .value = dec.num };
|
|
} else {
|
|
return .{ .errorcode = 1, .value = 0 };
|
|
}
|
|
}
|
|
|
|
pub fn toStr(arg: RocDec) callconv(.C) RocStr {
|
|
return @call(.{ .modifier = always_inline }, RocDec.toStr, .{arg});
|
|
}
|
|
|
|
pub fn fromF64C(arg: f64) callconv(.C) i128 {
|
|
if (@call(.{ .modifier = always_inline }, RocDec.fromF64, .{arg})) |dec| {
|
|
return dec.num;
|
|
} else {
|
|
@panic("TODO runtime exception failing convert f64 to RocDec");
|
|
}
|
|
}
|
|
|
|
pub fn fromF32C(arg_f32: f32) callconv(.C) i128 {
|
|
const arg_f64 = arg_f32;
|
|
if (@call(.{ .modifier = always_inline }, RocDec.fromF64, .{arg_f64})) |dec| {
|
|
return dec.num;
|
|
} else {
|
|
@panic("TODO runtime exception failing convert f64 to RocDec");
|
|
}
|
|
}
|
|
|
|
pub fn toF64(arg: RocDec) callconv(.C) f64 {
|
|
return @call(.{ .modifier = always_inline }, RocDec.toF64, .{arg});
|
|
}
|
|
|
|
pub fn exportFromInt(comptime T: type, comptime name: []const u8) void {
|
|
comptime var f = struct {
|
|
fn func(self: T) callconv(.C) i128 {
|
|
const this = @intCast(i128, self);
|
|
|
|
var result: i128 = undefined;
|
|
|
|
if (@mulWithOverflow(i128, this, RocDec.one_point_zero_i128, &result)) {
|
|
@panic("TODO runtime exception failing convert integer to RocDec");
|
|
} else {
|
|
return result;
|
|
}
|
|
}
|
|
}.func;
|
|
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
|
}
|
|
|
|
pub fn fromU64C(arg: u64) callconv(.C) i128 {
|
|
return @call(.{ .modifier = always_inline }, RocDec.fromU64, .{arg}).toI128();
|
|
}
|
|
|
|
pub fn toI128(arg: RocDec) callconv(.C) i128 {
|
|
return @call(.{ .modifier = always_inline }, RocDec.toI128, .{arg});
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
pub fn addOrPanicC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec {
|
|
return @call(.{ .modifier = always_inline }, RocDec.add, .{ arg1, arg2 });
|
|
}
|
|
|
|
pub fn addSaturatedC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec {
|
|
return @call(.{ .modifier = always_inline }, RocDec.addSaturated, .{ arg1, arg2 });
|
|
}
|
|
|
|
pub fn subOrPanicC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec {
|
|
return @call(.{ .modifier = always_inline }, RocDec.sub, .{ arg1, arg2 });
|
|
}
|
|
|
|
pub fn subSaturatedC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec {
|
|
return @call(.{ .modifier = always_inline }, RocDec.subSaturated, .{ arg1, arg2 });
|
|
}
|
|
|
|
pub fn mulOrPanicC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec {
|
|
return @call(.{ .modifier = always_inline }, RocDec.mul, .{ arg1, arg2 });
|
|
}
|
|
|
|
pub fn mulSaturatedC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec {
|
|
return @call(.{ .modifier = always_inline }, RocDec.mulSaturated, .{ arg1, arg2 });
|
|
}
|