Merge pull request #3525 from rtfeldman/3522

Num.toStr for f32, f64, Dec
This commit is contained in:
Richard Feldman 2022-07-13 21:30:05 -04:00 committed by GitHub
commit da7c7ac5f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 159 additions and 45 deletions

View file

@ -131,7 +131,7 @@ pub const RocDec = extern struct {
return (c -% 48) <= 9;
}
pub fn toStr(self: RocDec) ?RocStr {
pub fn toStr(self: RocDec) RocStr {
// Special case
if (self.num == 0) {
return RocStr.init("0.0", 3);
@ -843,7 +843,7 @@ test "toStr: 123.45" {
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "123.45"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: -123.45" {
@ -851,7 +851,7 @@ test "toStr: -123.45" {
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "-123.45"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: 123.0" {
@ -859,7 +859,7 @@ test "toStr: 123.0" {
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "123.0"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: -123.0" {
@ -867,7 +867,7 @@ test "toStr: -123.0" {
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "-123.0"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: 0.45" {
@ -875,7 +875,7 @@ test "toStr: 0.45" {
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "0.45"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: -0.45" {
@ -883,7 +883,7 @@ test "toStr: -0.45" {
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "-0.45"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: 0.00045" {
@ -891,7 +891,7 @@ test "toStr: 0.00045" {
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "0.00045"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: -0.00045" {
@ -899,7 +899,7 @@ test "toStr: -0.00045" {
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "-0.00045"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: -111.123456" {
@ -907,7 +907,7 @@ test "toStr: -111.123456" {
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "-111.123456"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: 123.1111111" {
@ -915,57 +915,57 @@ test "toStr: 123.1111111" {
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "123.1111111"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
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.?.deinit();
defer res_roc_str.?.deinit();
errdefer res_roc_str.deinit();
defer res_roc_str.deinit();
const res_slice: []const u8 = "123.111111111111"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
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.?.deinit();
defer res_roc_str.?.deinit();
errdefer res_roc_str.deinit();
defer res_roc_str.deinit();
const res_slice: []const u8 = "123.111111111111444444"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
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.?.deinit();
defer res_roc_str.?.deinit();
errdefer res_roc_str.deinit();
defer res_roc_str.deinit();
const res_slice: []const u8 = "12345678912345678912.111111111111111111"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
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.?.deinit();
defer res_roc_str.?.deinit();
errdefer res_roc_str.deinit();
defer res_roc_str.deinit();
const res_slice: []const u8 = "170141183460469231731.687303715884105727"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
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.?.deinit();
defer res_roc_str.?.deinit();
errdefer res_roc_str.deinit();
defer res_roc_str.deinit();
const res_slice: []const u8 = "-170141183460469231731.687303715884105728"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "toStr: 0" {
@ -973,7 +973,7 @@ test "toStr: 0" {
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "0.0"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
}
test "add: 0" {
@ -1065,6 +1065,10 @@ pub fn fromStr(arg: RocStr) callconv(.C) num_.NumParseResult(i128) {
}
}
pub fn toStr(arg: RocDec) callconv(.C) RocStr {
return @call(.{ .modifier = always_inline }, RocDec.toStr, .{arg});
}
pub fn fromF64C(arg: f64) callconv(.C) i128 {
return if (@call(.{ .modifier = always_inline }, RocDec.fromF64, .{arg})) |dec| dec.num else @panic("TODO runtime exception failing convert f64 to RocDec");
}

View file

@ -12,6 +12,7 @@ const dec = @import("dec.zig");
comptime {
exportDecFn(dec.fromStr, "from_str");
exportDecFn(dec.toStr, "to_str");
exportDecFn(dec.fromF64C, "from_f64");
exportDecFn(dec.eqC, "eq");
exportDecFn(dec.neqC, "neq");
@ -153,7 +154,6 @@ comptime {
exportStrFn(str.strConcatC, "concat");
exportStrFn(str.strJoinWithC, "joinWith");
exportStrFn(str.strNumberOfBytes, "number_of_bytes");
exportStrFn(str.strFromFloatC, "from_float");
exportStrFn(str.strEqual, "equal");
exportStrFn(str.substringUnsafe, "substring_unsafe");
exportStrFn(str.getUnsafe, "get_unsafe");
@ -174,6 +174,7 @@ comptime {
}
inline for (FLOATS) |T| {
str.exportFromFloat(T, ROC_BUILTINS ++ "." ++ STR ++ ".from_float.");
num.exportParseFloat(T, ROC_BUILTINS ++ "." ++ STR ++ ".to_float.");
}
}

View file

@ -749,12 +749,18 @@ fn strFromIntHelp(comptime T: type, int: T) RocStr {
}
// Str.fromFloat
pub fn strFromFloatC(float: f64) callconv(.C) RocStr {
return @call(.{ .modifier = always_inline }, strFromFloatHelp, .{ f64, float });
pub fn exportFromFloat(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(float: T) callconv(.C) RocStr {
return @call(.{ .modifier = always_inline }, strFromFloatHelp, .{ T, float });
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
fn strFromFloatHelp(comptime T: type, float: T) RocStr {
var buf: [100]u8 = undefined;
var buf: [400]u8 = undefined;
const result = std.fmt.bufPrint(&buf, "{d}", .{float}) catch unreachable;
return RocStr.init(&buf, result.len);

View file

@ -320,7 +320,7 @@ pub const STR_STARTS_WITH_SCALAR: &str = "roc_builtins.str.starts_with_scalar";
pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with";
pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes";
pub const STR_FROM_INT: IntrinsicName = int_intrinsic!("roc_builtins.str.from_int");
pub const STR_FROM_FLOAT: &str = "roc_builtins.str.from_float";
pub const STR_FROM_FLOAT: IntrinsicName = float_intrinsic!("roc_builtins.str.from_float");
pub const STR_TO_INT: IntrinsicName = int_intrinsic!("roc_builtins.str.to_int");
pub const STR_TO_FLOAT: IntrinsicName = float_intrinsic!("roc_builtins.str.to_float");
pub const STR_TO_DECIMAL: &str = "roc_builtins.str.to_decimal";
@ -373,6 +373,7 @@ pub const LIST_APPEND_UNSAFE: &str = "roc_builtins.list.append_unsafe";
pub const LIST_RESERVE: &str = "roc_builtins.list.reserve";
pub const DEC_FROM_STR: &str = "roc_builtins.dec.from_str";
pub const DEC_TO_STR: &str = "roc_builtins.dec.to_str";
pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64";
pub const DEC_EQ: &str = "roc_builtins.dec.eq";
pub const DEC_NEQ: &str = "roc_builtins.dec.neq";

View file

@ -14,7 +14,7 @@ use crate::llvm::build_list::{
list_prepend, list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap,
list_symbol_to_c_abi, list_to_c_abi, list_with_capacity, pass_update_mode,
};
use crate::llvm::build_str::{str_from_float, str_from_int};
use crate::llvm::build_str::{dec_to_str, str_from_float, str_from_int};
use crate::llvm::compare::{generic_eq, generic_neq};
use crate::llvm::convert::{
self, argument_type_from_layout, basic_type_from_builtin, basic_type_from_layout,
@ -5497,7 +5497,14 @@ fn run_low_level<'a, 'ctx, 'env>(
// Str.fromFloat : Float * -> Str
debug_assert_eq!(args.len(), 1);
str_from_float(env, scope, args[0])
let (float, float_layout) = load_symbol_and_layout(scope, &args[0]);
let float_width = match float_layout {
Layout::Builtin(Builtin::Float(float_width)) => *float_width,
_ => unreachable!(),
};
str_from_float(env, float, float_width)
}
StrFromUtf8Range => {
debug_assert_eq!(args.len(), 3);
@ -5831,9 +5838,10 @@ fn run_low_level<'a, 'ctx, 'env>(
str_from_int(env, int, *int_width)
}
Layout::Builtin(Builtin::Float(_float_width)) => {
str_from_float(env, scope, args[0])
Layout::Builtin(Builtin::Float(float_width)) => {
str_from_float(env, num, *float_width)
}
Layout::Builtin(Builtin::Decimal) => dec_to_str(env, num),
_ => unreachable!(),
}
}

View file

@ -3,7 +3,7 @@ use crate::llvm::build::{Env, Scope};
use inkwell::builder::Builder;
use inkwell::values::{BasicValueEnum, IntValue, PointerValue, StructValue};
use inkwell::AddressSpace;
use roc_builtins::bitcode::{self, IntWidth};
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout};
use roc_target::PtrWidth;
@ -103,15 +103,37 @@ pub fn decode_from_utf8_result<'a, 'ctx, 'env>(
}
}
/// Str.fromFloat : Int -> Str
/// Str.fromFloat : Float * -> Str
pub fn str_from_float<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
int_symbol: Symbol,
float: BasicValueEnum<'ctx>,
float_width: FloatWidth,
) -> BasicValueEnum<'ctx> {
let float = load_symbol(scope, &int_symbol);
call_str_bitcode_fn(env, &[float], &bitcode::STR_FROM_FLOAT[float_width])
}
call_str_bitcode_fn(env, &[float], bitcode::STR_FROM_FLOAT)
/// Dec.toStr : Dec -> Str
pub fn dec_to_str<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
dec: BasicValueEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
let dec = dec.into_int_value();
let int_64 = env.context.i128_type().const_int(64, false);
let int_64_type = env.context.i64_type();
let dec_right_shift = env
.builder
.build_right_shift(dec, int_64, false, "dec_left_bits");
let right_bits = env.builder.build_int_cast(dec, int_64_type, "");
let left_bits = env.builder.build_int_cast(dec_right_shift, int_64_type, "");
call_str_bitcode_fn(
env,
&[right_bits.into(), left_bits.into()],
bitcode::DEC_TO_STR,
)
}
/// Str.equal : Str, Str -> Bool

View file

@ -2029,15 +2029,15 @@ impl<'a> LowLevelCall<'a> {
FloatWidth::F32 => {
self.load_args(backend);
backend.code_builder.f64_promote_f32();
self.load_args_and_call_zig(backend, bitcode::STR_FROM_FLOAT);
self.load_args_and_call_zig(backend, &bitcode::STR_FROM_FLOAT[width]);
}
FloatWidth::F64 => {
self.load_args_and_call_zig(backend, bitcode::STR_FROM_FLOAT);
self.load_args_and_call_zig(backend, &bitcode::STR_FROM_FLOAT[width]);
}
FloatWidth::F128 => todo!("F128 to Str"),
},
Layout::Builtin(Builtin::Decimal) => {
todo!("Decimal to Str")
self.load_args_and_call_zig(backend, bitcode::DEC_TO_STR)
}
x => internal_error!("NumToStr is not defined for {:?}", x),
}

View file

@ -3054,6 +3054,78 @@ fn num_to_str_i64() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn num_to_str_f32() {
use roc_std::RocStr;
assert_evals_to!(r#"Num.toStr -10.75f32"#, RocStr::from("-10.75"), RocStr);
assert_evals_to!(r#"Num.toStr -1.75f32"#, RocStr::from("-1.75"), RocStr);
assert_evals_to!(r#"Num.toStr 0f32"#, RocStr::from("0"), RocStr);
assert_evals_to!(r#"Num.toStr 1.75f32"#, RocStr::from("1.75"), RocStr);
assert_evals_to!(r#"Num.toStr 10.75f32"#, RocStr::from("10.75"), RocStr);
assert_evals_to!(
r#"Num.toStr Num.maxF32"#,
RocStr::from("340282346638528860000000000000000000000"),
RocStr
);
assert_evals_to!(
r#"Num.toStr Num.minF32"#,
RocStr::from("-340282346638528860000000000000000000000"),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn num_to_str_f64() {
use roc_std::RocStr;
assert_evals_to!(r#"Num.toStr -10.75f64"#, RocStr::from("-10.75"), RocStr);
assert_evals_to!(r#"Num.toStr -1.75f64"#, RocStr::from("-1.75"), RocStr);
assert_evals_to!(r#"Num.toStr 0f64"#, RocStr::from("0"), RocStr);
assert_evals_to!(r#"Num.toStr 1.75f64"#, RocStr::from("1.75"), RocStr);
assert_evals_to!(r#"Num.toStr 10.75f64"#, RocStr::from("10.75"), RocStr);
assert_evals_to!(
r#"Num.toStr Num.maxF64"#,
RocStr::from(f64::MAX.to_string().as_str()),
RocStr
);
assert_evals_to!(
r#"Num.toStr Num.minF64"#,
RocStr::from(f64::MIN.to_string().as_str()),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn num_to_str_dec() {
use roc_std::RocStr;
assert_evals_to!(r#"Num.toStr -10.75dec"#, RocStr::from("-10.75"), RocStr);
assert_evals_to!(r#"Num.toStr -1.75dec"#, RocStr::from("-1.75"), RocStr);
assert_evals_to!(r#"Num.toStr 0dec"#, RocStr::from("0.0"), RocStr);
assert_evals_to!(r#"Num.toStr 1.75dec"#, RocStr::from("1.75"), RocStr);
assert_evals_to!(r#"Num.toStr 10.75dec"#, RocStr::from("10.75"), RocStr);
assert_evals_to!(
r#"Num.toStr 170141183460469.105727dec"#,
RocStr::from("170141183460469.105727"),
RocStr
);
assert_evals_to!(
r#"Num.toStr -170141183460469.105727dec"#,
RocStr::from("-170141183460469.105727"),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn u8_addition_greater_than_i8() {