diff --git a/crates/compiler/builtins/bitcode/src/dec.zig b/crates/compiler/builtins/bitcode/src/dec.zig index cbabdb14ee..054145fd4d 100644 --- a/crates/compiler/builtins/bitcode/src/dec.zig +++ b/crates/compiler/builtins/bitcode/src/dec.zig @@ -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 }); +} diff --git a/crates/compiler/builtins/bitcode/src/main.zig b/crates/compiler/builtins/bitcode/src/main.zig index 93402b98cc..edc35087ef 100644 --- a/crates/compiler/builtins/bitcode/src/main.zig +++ b/crates/compiler/builtins/bitcode/src/main.zig @@ -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."); } } diff --git a/crates/compiler/builtins/src/bitcode.rs b/crates/compiler/builtins/src/bitcode.rs index 4a5f98dc94..0dd09aaea7 100644 --- a/crates/compiler/builtins/src/bitcode.rs +++ b/crates/compiler/builtins/src/bitcode.rs @@ -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"; diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index bda18231a1..d24796c5e1 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -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, diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index cd039d7234..5ab3b19aed 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -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]