diff --git a/crates/compiler/builtins/bitcode/src/dec.zig b/crates/compiler/builtins/bitcode/src/dec.zig index 054145fd4d..6ac774f78e 100644 --- a/crates/compiler/builtins/bitcode/src/dec.zig +++ b/crates/compiler/builtins/bitcode/src/dec.zig @@ -391,6 +391,29 @@ pub const RocDec = extern struct { } } + fn powInt(base: RocDec, exponent: i128) RocDec { + if (exponent == 0) { + return RocDec.one_point_zero; + } else if (exponent > 0) { + if (@mod(exponent, 2) == 0) { + const half_power = RocDec.powInt(base, exponent >> 1); // `>> 1` == `/ 2` + return RocDec.mul(half_power, half_power); + } else { + return RocDec.mul(base, RocDec.powInt(base, exponent - 1)); + } + } else { + return RocDec.div(RocDec.one_point_zero, RocDec.powInt(base, -exponent)); + } + } + + fn pow(base: RocDec, exponent: RocDec) RocDec { + if (exponent.trunc().num == exponent.num) { + return base.powInt(@divTrunc(exponent.num, RocDec.one_point_zero_i128)); + } else { + return fromF64(std.math.pow(f64, base.toF64(), exponent.toF64())).?; + } + } + pub fn mul(self: RocDec, other: RocDec) RocDec { const answer = RocDec.mulWithOverflow(self, other); @@ -1358,6 +1381,41 @@ test "round: -0.5" { try expectEqual(RocDec{ .num = -1000000000000000000 }, dec.round()); } +test "powInt: 3.1 ^ 0" { + var roc_str = RocStr.init("3.1", 3); + var dec = RocDec.fromStr(roc_str).?; + + try expectEqual(RocDec.one_point_zero, dec.powInt(0)); +} + +test "powInt: 3.1 ^ 1" { + var roc_str = RocStr.init("3.1", 3); + var dec = RocDec.fromStr(roc_str).?; + + try expectEqual(dec, dec.powInt(1)); +} + +test "powInt: 2 ^ 2" { + var roc_str = RocStr.init("4", 1); + var dec = RocDec.fromStr(roc_str).?; + + try expectEqual(dec, RocDec.two_point_zero.powInt(2)); +} + +test "powInt: 0.5 ^ 2" { + var roc_str = RocStr.init("0.25", 4); + var dec = RocDec.fromStr(roc_str).?; + + try expectEqual(dec, RocDec.zero_point_five.powInt(2)); +} + +test "pow: 0.5 ^ 2.0" { + var roc_str = RocStr.init("0.25", 4); + var dec = RocDec.fromStr(roc_str).?; + + try expectEqual(dec, RocDec.zero_point_five.pow(RocDec.two_point_zero)); +} + // exports pub fn fromStr(arg: RocStr) callconv(.C) num_.NumParseResult(i128) { @@ -1458,6 +1516,10 @@ pub fn logC(arg: RocDec) callconv(.C) i128 { return @call(.always_inline, RocDec.log, .{arg}).num; } +pub fn powC(arg1: RocDec, arg2: RocDec) callconv(.C) i128 { + return @call(.always_inline, RocDec.pow, .{ arg1, arg2 }).num; +} + pub fn sinC(arg: RocDec) callconv(.C) i128 { return @call(.always_inline, RocDec.sin, .{arg}).num; } diff --git a/crates/compiler/builtins/bitcode/src/main.zig b/crates/compiler/builtins/bitcode/src/main.zig index edc35087ef..236848c01a 100644 --- a/crates/compiler/builtins/bitcode/src/main.zig +++ b/crates/compiler/builtins/bitcode/src/main.zig @@ -36,6 +36,7 @@ comptime { exportDecFn(dec.fromStr, "from_str"); exportDecFn(dec.fromU64C, "from_u64"); exportDecFn(dec.logC, "log"); + exportDecFn(dec.powC, "pow"); exportDecFn(dec.mulC, "mul_with_overflow"); exportDecFn(dec.mulOrPanicC, "mul_or_panic"); exportDecFn(dec.mulSaturatedC, "mul_saturated"); diff --git a/crates/compiler/builtins/src/bitcode.rs b/crates/compiler/builtins/src/bitcode.rs index 0dd09aaea7..9a519eadd9 100644 --- a/crates/compiler/builtins/src/bitcode.rs +++ b/crates/compiler/builtins/src/bitcode.rs @@ -408,6 +408,7 @@ pub const DEC_FROM_INT: IntrinsicName = int_intrinsic!("roc_builtins.dec.from_in pub const DEC_FROM_STR: &str = "roc_builtins.dec.from_str"; pub const DEC_FROM_U64: &str = "roc_builtins.dec.from_u64"; pub const DEC_LOG: &str = "roc_builtins.dec.log"; +pub const DEC_POW: &str = "roc_builtins.dec.pow"; pub const DEC_MUL_OR_PANIC: &str = "roc_builtins.dec.mul_or_panic"; pub const DEC_MUL_SATURATED: &str = "roc_builtins.dec.mul_saturated"; pub const DEC_MUL_WITH_OVERFLOW: &str = "roc_builtins.dec.mul_with_overflow"; diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index d24796c5e1..50588e349c 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -1099,7 +1099,7 @@ trait Backend<'a> { LayoutRepr::Builtin(Builtin::Float(float_width)) => { &bitcode::NUM_POW[float_width] } - LayoutRepr::DEC => todo!("exponentiation for decimals"), + LayoutRepr::DEC => bitcode::DEC_POW, _ => unreachable!("invalid layout for NumPow"), }; diff --git a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs index 4d96e21788..e70775fbc1 100644 --- a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs +++ b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs @@ -2059,6 +2059,54 @@ fn dec_unary_op<'ctx>( } } +fn dec_binary_op<'ctx>( + env: &Env<'_, 'ctx, '_>, + fn_name: &str, + dec1: BasicValueEnum<'ctx>, + dec2: BasicValueEnum<'ctx>, +) -> BasicValueEnum<'ctx> { + use roc_target::Architecture::*; + use roc_target::OperatingSystem::*; + + let dec1 = dec1.into_int_value(); + let dec2 = dec2.into_int_value(); + + match env.target_info { + TargetInfo { + architecture: X86_64 | X86_32, + operating_system: Unix, + } => { + let (low1, high1) = dec_split_into_words(env, dec1); + let (low2, high2) = dec_split_into_words(env, dec2); + let lowr_highr = call_bitcode_fn( + env, + &[low1.into(), high1.into(), low2.into(), high2.into()], + fn_name, + ); + + let block = env.builder.get_insert_block().expect("to be in a function"); + let parent = block.get_parent().expect("to be in a function"); + + let ptr = + create_entry_block_alloca(env, parent, env.context.i128_type().into(), "to_i128"); + env.builder.build_store(ptr, lowr_highr).unwrap(); + + env.builder + .build_load(env.context.i128_type(), ptr, "to_i128") + .unwrap() + } + TargetInfo { + architecture: Wasm32, + operating_system: Unix, + } => call_bitcode_fn(env, &[dec1.into(), dec2.into()], fn_name), + _ => call_bitcode_fn( + env, + &[dec_alloca(env, dec1), dec_alloca(env, dec2)], + fn_name, + ), + } +} + fn dec_binop_with_overflow<'ctx>( env: &Env<'_, 'ctx, '_>, fn_name: &str, @@ -2277,6 +2325,7 @@ fn build_dec_binop<'a, 'ctx>( &[lhs, rhs], &bitcode::NUM_GREATER_THAN_OR_EQUAL[IntWidth::I128], ), + NumPow => dec_binary_op(env, bitcode::DEC_POW, lhs, rhs), _ => { unreachable!("Unrecognized dec binary operation: {:?}", op); } diff --git a/crates/compiler/gen_wasm/src/low_level.rs b/crates/compiler/gen_wasm/src/low_level.rs index 3251cc13ba..02bad893da 100644 --- a/crates/compiler/gen_wasm/src/low_level.rs +++ b/crates/compiler/gen_wasm/src/low_level.rs @@ -1585,6 +1585,10 @@ impl<'a> LowLevelCall<'a> { LayoutRepr::Builtin(Builtin::Float(width)) => { self.load_args_and_call_zig(backend, &bitcode::NUM_POW[width]); } + LayoutRepr::Builtin(Builtin::Decimal) => { + self.load_args_and_call_zig(backend, bitcode::DEC_POW); + } + _ => panic_ret_type(), }, diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index 5ab3b19aed..2c2a26cc54 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -1819,10 +1819,16 @@ fn float_compare() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] -fn pow() { +fn pow_f64() { assert_evals_to!("Num.pow 2.0f64 2.0f64", 4.0, f64); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn pow_dec() { + assert_evals_to!("Num.pow 2.0dec 2.0dec", RocDec::from(4), RocDec); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn round_f64() {