Dec floor/ceiling/round

This commit is contained in:
Folkert 2024-01-29 13:39:38 +01:00
parent a7212ceb7f
commit db3b40a07b
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
5 changed files with 176 additions and 44 deletions

View file

@ -24,6 +24,9 @@ pub const RocDec = extern struct {
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 const two_point_zero: RocDec = RocDec.add(RocDec.one_point_zero, RocDec.one_point_zero);
pub const zero_point_five: RocDec = RocDec.div(RocDec.one_point_zero, RocDec.two_point_zero);
pub fn fromU64(num: u64) RocDec {
return .{ .num = num * one_point_zero_i128 };
}
@ -351,6 +354,43 @@ pub const RocDec = extern struct {
return RocDec{ .num = sign * digits };
}
// Returns the nearest integer to self. If a value is half-way between two integers, round away from 0.0.
fn round(arg1: RocDec) RocDec {
// this rounds towards zero
const tmp = arg1.trunc();
const sign = std.math.sign(arg1.num);
const abs_fract = sign * arg1.fract().num;
if (abs_fract >= RocDec.zero_point_five.num) {
return RocDec.add(tmp, RocDec{ .num = sign * RocDec.one_point_zero.num });
} else {
return tmp;
}
}
// Returns the largest integer less than or equal to itself
fn floor(arg1: RocDec) RocDec {
const tmp = arg1.trunc();
if (arg1.num < 0 and arg1.fract().num != 0) {
return RocDec.sub(tmp, RocDec.one_point_zero);
} else {
return tmp;
}
}
// Returns the smallest integer greater than or equal to itself
fn ceiling(arg1: RocDec) RocDec {
const tmp = arg1.trunc();
if (arg1.num > 0 and arg1.fract().num != 0) {
return RocDec.add(tmp, RocDec.one_point_zero);
} else {
return tmp;
}
}
pub fn mul(self: RocDec, other: RocDec) RocDec {
const answer = RocDec.mulWithOverflow(self, other);
@ -1290,6 +1330,34 @@ test "trunc: -0.00045" {
try expectEqual(RocDec{ .num = 0 }, res);
}
test "round: 123.45" {
var roc_str = RocStr.init("123.45", 6);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 123000000000000000000 }, dec.round());
}
test "round: -123.45" {
var roc_str = RocStr.init("-123.45", 7);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = -123000000000000000000 }, dec.round());
}
test "round: 0.5" {
var roc_str = RocStr.init("0.5", 3);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec.one_point_zero, dec.round());
}
test "round: -0.5" {
var roc_str = RocStr.init("-0.5", 4);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = -1000000000000000000 }, dec.round());
}
// exports
pub fn fromStr(arg: RocStr) callconv(.C) num_.NumParseResult(i128) {
@ -1437,3 +1505,30 @@ pub fn mulOrPanicC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec {
pub fn mulSaturatedC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec {
return @call(.always_inline, RocDec.mulSaturated, .{ arg1, arg2 });
}
pub fn exportRound(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: RocDec) callconv(.C) T {
return @as(T, @intCast(@divFloor(input.round().num, RocDec.one_point_zero_i128)));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportFloor(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: RocDec) callconv(.C) T {
return @as(T, @intCast(@divFloor(input.floor().num, RocDec.one_point_zero_i128)));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportCeiling(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: RocDec) callconv(.C) T {
return @as(T, @intCast(@divFloor(input.ceiling().num, RocDec.one_point_zero_i128)));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}

View file

@ -52,6 +52,10 @@ comptime {
inline for (INTEGERS) |T| {
dec.exportFromInt(T, ROC_BUILTINS ++ ".dec.from_int.");
dec.exportRound(T, ROC_BUILTINS ++ ".dec.round.");
dec.exportFloor(T, ROC_BUILTINS ++ ".dec.floor.");
dec.exportCeiling(T, ROC_BUILTINS ++ ".dec.ceiling.");
}
}

View file

@ -420,6 +420,9 @@ pub const DEC_SUB_WITH_OVERFLOW: &str = "roc_builtins.dec.sub_with_overflow";
pub const DEC_TAN: &str = "roc_builtins.dec.tan";
pub const DEC_TO_I128: &str = "roc_builtins.dec.to_i128";
pub const DEC_TO_STR: &str = "roc_builtins.dec.to_str";
pub const DEC_ROUND: IntrinsicName = int_intrinsic!("roc_builtins.dec.round");
pub const DEC_FLOOR: IntrinsicName = int_intrinsic!("roc_builtins.dec.floor");
pub const DEC_CEILING: IntrinsicName = int_intrinsic!("roc_builtins.dec.ceiling");
pub const UTILS_DBG_IMPL: &str = "roc_builtins.utils.dbg_impl";
pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";

View file

@ -1106,30 +1106,36 @@ trait Backend<'a> {
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
}
LowLevel::NumRound => {
let repr = self.interner().get_repr(*ret_layout);
let LayoutRepr::Builtin(Builtin::Int(int_width)) = repr else {
unreachable!("invalid return layout for NumRound")
};
let intrinsic = match arg_layouts[0] {
Layout::F32 => &bitcode::NUM_ROUND_F32[int_width],
Layout::F64 => &bitcode::NUM_ROUND_F64[int_width],
Layout::DEC => &bitcode::DEC_ROUND[int_width],
_ => unreachable!("invalid layout for NumRound"),
};
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
}
LowLevel::NumFloor => {
let repr = self.interner().get_repr(*ret_layout);
let LayoutRepr::Builtin(Builtin::Int(int_width)) = repr else {
unreachable!("invalid return layout for NumFloor")
};
match arg_layouts[0] {
Layout::F32 => self.build_fn_call(
sym,
bitcode::NUM_FLOOR_F32[int_width].to_string(),
args,
arg_layouts,
ret_layout,
),
Layout::F64 => self.build_fn_call(
sym,
bitcode::NUM_FLOOR_F64[int_width].to_string(),
args,
arg_layouts,
ret_layout,
),
Layout::DEC => todo!("NumFloor for decimals"),
let intrinsic = match arg_layouts[0] {
Layout::F32 => &bitcode::NUM_FLOOR_F32[int_width],
Layout::F64 => &bitcode::NUM_FLOOR_F64[int_width],
Layout::DEC => &bitcode::DEC_FLOOR[int_width],
_ => unreachable!("invalid layout for NumFloor"),
}
};
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
}
LowLevel::NumCeiling => {
@ -1138,24 +1144,14 @@ trait Backend<'a> {
unreachable!("invalid return layout for NumCeiling")
};
match arg_layouts[0] {
Layout::F32 => self.build_fn_call(
sym,
bitcode::NUM_CEILING_F32[int_width].to_string(),
args,
arg_layouts,
ret_layout,
),
Layout::F64 => self.build_fn_call(
sym,
bitcode::NUM_CEILING_F64[int_width].to_string(),
args,
arg_layouts,
ret_layout,
),
Layout::DEC => todo!("NumCeiling for decimals"),
let intrinsic = match arg_layouts[0] {
Layout::F32 => &bitcode::NUM_CEILING_F32[int_width],
Layout::F64 => &bitcode::NUM_CEILING_F64[int_width],
Layout::DEC => &bitcode::DEC_CEILING[int_width],
_ => unreachable!("invalid layout for NumCeiling"),
}
};
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
}
LowLevel::NumSub => {
@ -1494,13 +1490,6 @@ trait Backend<'a> {
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
}
LowLevel::NumRound => self.build_fn_call(
sym,
bitcode::NUM_ROUND_F64[IntWidth::I64].to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::ListLen => {
debug_assert_eq!(
1,

View file

@ -1825,15 +1825,56 @@ fn pow() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn ceiling() {
assert_evals_to!("Num.ceiling 1.1f64", 2, i64);
fn round_f64() {
assert_evals_to!("Num.round 1.9f64", 2, i64);
assert_evals_to!("Num.round -1.9f64", -2, i64);
assert_evals_to!("Num.round 0.5f64", 1, i64);
assert_evals_to!("Num.round -0.5f64", -1, i64);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn floor() {
fn round_dec() {
assert_evals_to!("Num.round 1.9dec", 2, i64);
assert_evals_to!("Num.round -1.9dec", -2, i64);
assert_evals_to!("Num.round 0.5dec", 1, i64);
assert_evals_to!("Num.round -0.5dec", -1, i64);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn ceiling_f64() {
assert_evals_to!("Num.ceiling 1.9f64", 2, i64);
assert_evals_to!("Num.ceiling -1.9f64", -1, i64);
assert_evals_to!("Num.ceiling 0.5f64", 1, i64);
assert_evals_to!("Num.ceiling -0.5f64", 0, i64);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn ceiling_dec() {
assert_evals_to!("Num.ceiling 1.9dec", 2, i64);
assert_evals_to!("Num.ceiling -1.9dec", -1, i64);
assert_evals_to!("Num.ceiling 0.5dec", 1, i64);
assert_evals_to!("Num.ceiling -0.5dec", 0, i64);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn floor_f64() {
assert_evals_to!("Num.floor 1.9f64", 1, i64);
assert_evals_to!("Num.floor -1.9f64", -2, i64);
assert_evals_to!("Num.floor 0.5f64", 0, i64);
assert_evals_to!("Num.floor -0.5f64", -1, i64);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn floor_dec() {
assert_evals_to!("Num.floor 1.9dec", 1, i64);
assert_evals_to!("Num.floor -1.9dec", -2, i64);
assert_evals_to!("Num.floor 0.5dec", 0, i64);
assert_evals_to!("Num.floor -0.5dec", -1, i64);
}
#[test]