Merge pull request #930 from zig-lang/float-printing
Finish and fix float printing
This commit is contained in:
commit
0bb054e5e7
|
@ -12,13 +12,79 @@ pub const FloatDecimal = struct {
|
|||
exp: i32,
|
||||
};
|
||||
|
||||
pub const RoundMode = enum {
|
||||
// Round only the fractional portion (e.g. 1234.23 has precision 2)
|
||||
Decimal,
|
||||
// Round the entire whole/fractional portion (e.g. 1.23423e3 has precision 5)
|
||||
Scientific,
|
||||
};
|
||||
|
||||
/// Round a FloatDecimal as returned by errol3 to the specified fractional precision.
|
||||
/// All digits after the specified precision should be considered invalid.
|
||||
pub fn roundToPrecision(float_decimal: &FloatDecimal, precision: usize, mode: RoundMode) void {
|
||||
// The round digit refers to the index which we should look at to determine
|
||||
// whether we need to round to match the specified precision.
|
||||
var round_digit: usize = 0;
|
||||
|
||||
switch (mode) {
|
||||
RoundMode.Decimal => {
|
||||
if (float_decimal.exp >= 0) {
|
||||
round_digit = precision + usize(float_decimal.exp);
|
||||
} else {
|
||||
// if a small negative exp, then adjust we need to offset by the number
|
||||
// of leading zeros that will occur.
|
||||
const min_exp_required = usize(-float_decimal.exp);
|
||||
if (precision > min_exp_required) {
|
||||
round_digit = precision - min_exp_required;
|
||||
}
|
||||
}
|
||||
},
|
||||
RoundMode.Scientific => {
|
||||
round_digit = 1 + precision;
|
||||
},
|
||||
}
|
||||
|
||||
// It suffices to look at just this digit. We don't round and propagate say 0.04999 to 0.05
|
||||
// first, and then to 0.1 in the case of a {.1} single precision.
|
||||
|
||||
// Find the digit which will signify the round point and start rounding backwards.
|
||||
if (round_digit < float_decimal.digits.len and float_decimal.digits[round_digit] - '0' >= 5) {
|
||||
assert(round_digit >= 0);
|
||||
|
||||
var i = round_digit;
|
||||
while (true) {
|
||||
if (i == 0) {
|
||||
// Rounded all the way past the start. This was of the form 9.999...
|
||||
// Slot the new digit in place and increase the exponent.
|
||||
float_decimal.exp += 1;
|
||||
|
||||
// Re-size the buffer to use the reserved leading byte.
|
||||
const one_before = @intToPtr(&u8, @ptrToInt(&float_decimal.digits[0]) - 1);
|
||||
float_decimal.digits = one_before[0..float_decimal.digits.len + 1];
|
||||
float_decimal.digits[0] = '1';
|
||||
return;
|
||||
}
|
||||
|
||||
i -= 1;
|
||||
|
||||
const new_value = (float_decimal.digits[i] - '0' + 1) % 10;
|
||||
float_decimal.digits[i] = new_value + '0';
|
||||
|
||||
// must continue rounding until non-9
|
||||
if (new_value != 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Corrected Errol3 double to ASCII conversion.
|
||||
pub fn errol3(value: f64, buffer: []u8) FloatDecimal {
|
||||
const bits = @bitCast(u64, value);
|
||||
const i = tableLowerBound(bits);
|
||||
if (i < enum3.len and enum3[i] == bits) {
|
||||
const data = enum3_data[i];
|
||||
const digits = buffer[0..data.str.len];
|
||||
const digits = buffer[1..data.str.len + 1];
|
||||
mem.copy(u8, digits, data.str);
|
||||
return FloatDecimal {
|
||||
.digits = digits,
|
||||
|
@ -98,7 +164,11 @@ fn errol3u(val: f64, buffer: []u8) FloatDecimal {
|
|||
}
|
||||
|
||||
// digit generation
|
||||
var buf_index: usize = 0;
|
||||
|
||||
// We generate digits starting at index 1. If rounding a buffer later then it may be
|
||||
// required to generate a preceeding digit in some cases (9.999) in which case we use
|
||||
// the 0-index for this extra digit.
|
||||
var buf_index: usize = 1;
|
||||
while (true) {
|
||||
var hdig = u8(math.floor(high.val));
|
||||
if ((high.val == f64(hdig)) and (high.off < 0))
|
||||
|
@ -128,7 +198,7 @@ fn errol3u(val: f64, buffer: []u8) FloatDecimal {
|
|||
buf_index += 1;
|
||||
|
||||
return FloatDecimal {
|
||||
.digits = buffer[0..buf_index],
|
||||
.digits = buffer[1..buf_index],
|
||||
.exp = exp,
|
||||
};
|
||||
}
|
||||
|
@ -189,6 +259,9 @@ fn gethi(in: f64) f64 {
|
|||
/// Normalize the number by factoring in the error.
|
||||
/// @hp: The float pair.
|
||||
fn hpNormalize(hp: &HP) void {
|
||||
// Required to avoid segfaults causing buffer overrun during errol3 digit output termination.
|
||||
@setFloatMode(this, @import("builtin").FloatMode.Strict);
|
||||
|
||||
const val = hp.val;
|
||||
|
||||
hp.val += hp.off;
|
||||
|
|
|
@ -4,7 +4,7 @@ const debug = std.debug;
|
|||
const assert = debug.assert;
|
||||
const mem = std.mem;
|
||||
const builtin = @import("builtin");
|
||||
const errol3 = @import("errol/index.zig").errol3;
|
||||
const errol = @import("errol/index.zig");
|
||||
|
||||
const max_int_digits = 65;
|
||||
|
||||
|
@ -22,6 +22,8 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),
|
|||
IntegerWidth,
|
||||
Float,
|
||||
FloatWidth,
|
||||
FloatScientific,
|
||||
FloatScientificWidth,
|
||||
Character,
|
||||
Buf,
|
||||
BufWidth,
|
||||
|
@ -87,6 +89,9 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),
|
|||
's' => {
|
||||
state = State.Buf;
|
||||
},
|
||||
'e' => {
|
||||
state = State.FloatScientific;
|
||||
},
|
||||
'.' => {
|
||||
state = State.Float;
|
||||
},
|
||||
|
@ -133,9 +138,33 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),
|
|||
'0' ... '9' => {},
|
||||
else => @compileError("Unexpected character in format string: " ++ []u8{c}),
|
||||
},
|
||||
State.FloatScientific => switch (c) {
|
||||
'}' => {
|
||||
try formatFloatScientific(args[next_arg], null, context, Errors, output);
|
||||
next_arg += 1;
|
||||
state = State.Start;
|
||||
start_index = i + 1;
|
||||
},
|
||||
'0' ... '9' => {
|
||||
width_start = i;
|
||||
state = State.FloatScientificWidth;
|
||||
},
|
||||
else => @compileError("Unexpected character in format string: " ++ []u8{c}),
|
||||
},
|
||||
State.FloatScientificWidth => switch (c) {
|
||||
'}' => {
|
||||
width = comptime (parseUnsigned(usize, fmt[width_start..i], 10) catch unreachable);
|
||||
try formatFloatScientific(args[next_arg], width, context, Errors, output);
|
||||
next_arg += 1;
|
||||
state = State.Start;
|
||||
start_index = i + 1;
|
||||
},
|
||||
'0' ... '9' => {},
|
||||
else => @compileError("Unexpected character in format string: " ++ []u8{c}),
|
||||
},
|
||||
State.Float => switch (c) {
|
||||
'}' => {
|
||||
try formatFloatDecimal(args[next_arg], 0, context, Errors, output);
|
||||
try formatFloatDecimal(args[next_arg], null, context, Errors, output);
|
||||
next_arg += 1;
|
||||
state = State.Start;
|
||||
start_index = i + 1;
|
||||
|
@ -199,7 +228,7 @@ pub fn formatValue(value: var, context: var, comptime Errors: type, output: fn(@
|
|||
return formatInt(value, 10, false, 0, context, Errors, output);
|
||||
},
|
||||
builtin.TypeId.Float => {
|
||||
return formatFloat(value, context, Errors, output);
|
||||
return formatFloatScientific(value, null, context, Errors, output);
|
||||
},
|
||||
builtin.TypeId.Void => {
|
||||
return output(context, "void");
|
||||
|
@ -257,81 +286,237 @@ pub fn formatBuf(buf: []const u8, width: usize,
|
|||
}
|
||||
}
|
||||
|
||||
pub fn formatFloat(value: var, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void {
|
||||
// Print a float in scientific notation to the specified precision. Null uses full precision.
|
||||
// It should be the case that every full precision, printed value can be re-parsed back to the
|
||||
// same type unambiguously.
|
||||
pub fn formatFloatScientific(value: var, maybe_precision: ?usize, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void {
|
||||
var x = f64(value);
|
||||
|
||||
// Errol doesn't handle these special cases.
|
||||
if (math.isNan(x)) {
|
||||
return output(context, "NaN");
|
||||
}
|
||||
if (math.signbit(x)) {
|
||||
try output(context, "-");
|
||||
x = -x;
|
||||
}
|
||||
|
||||
if (math.isNan(x)) {
|
||||
return output(context, "nan");
|
||||
}
|
||||
if (math.isPositiveInf(x)) {
|
||||
return output(context, "Infinity");
|
||||
return output(context, "inf");
|
||||
}
|
||||
if (x == 0.0) {
|
||||
return output(context, "0.0");
|
||||
try output(context, "0");
|
||||
|
||||
if (maybe_precision) |precision| {
|
||||
if (precision != 0) {
|
||||
try output(context, ".");
|
||||
var i: usize = 0;
|
||||
while (i < precision) : (i += 1) {
|
||||
try output(context, "0");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try output(context, ".0");
|
||||
}
|
||||
|
||||
try output(context, "e+00");
|
||||
return;
|
||||
}
|
||||
|
||||
var buffer: [32]u8 = undefined;
|
||||
const float_decimal = errol3(x, buffer[0..]);
|
||||
try output(context, float_decimal.digits[0..1]);
|
||||
try output(context, ".");
|
||||
if (float_decimal.digits.len > 1) {
|
||||
const num_digits = if (@typeOf(value) == f32)
|
||||
math.min(usize(9), float_decimal.digits.len)
|
||||
else
|
||||
float_decimal.digits.len;
|
||||
try output(context, float_decimal.digits[1 .. num_digits]);
|
||||
var float_decimal = errol.errol3(x, buffer[0..]);
|
||||
|
||||
if (maybe_precision) |precision| {
|
||||
errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Scientific);
|
||||
|
||||
try output(context, float_decimal.digits[0..1]);
|
||||
|
||||
// {e0} case prints no `.`
|
||||
if (precision != 0) {
|
||||
try output(context, ".");
|
||||
|
||||
var printed: usize = 0;
|
||||
if (float_decimal.digits.len > 1) {
|
||||
const num_digits = math.min(float_decimal.digits.len, precision + 1);
|
||||
try output(context, float_decimal.digits[1 .. num_digits]);
|
||||
printed += num_digits - 1;
|
||||
}
|
||||
|
||||
while (printed < precision) : (printed += 1) {
|
||||
try output(context, "0");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try output(context, "0");
|
||||
try output(context, float_decimal.digits[0..1]);
|
||||
try output(context, ".");
|
||||
if (float_decimal.digits.len > 1) {
|
||||
const num_digits = if (@typeOf(value) == f32)
|
||||
math.min(usize(9), float_decimal.digits.len)
|
||||
else
|
||||
float_decimal.digits.len;
|
||||
|
||||
try output(context, float_decimal.digits[1 .. num_digits]);
|
||||
} else {
|
||||
try output(context, "0");
|
||||
}
|
||||
}
|
||||
|
||||
if (float_decimal.exp != 1) {
|
||||
try output(context, "e");
|
||||
try formatInt(float_decimal.exp - 1, 10, false, 0, context, Errors, output);
|
||||
try output(context, "e");
|
||||
const exp = float_decimal.exp - 1;
|
||||
|
||||
if (exp >= 0) {
|
||||
try output(context, "+");
|
||||
if (exp > -10 and exp < 10) {
|
||||
try output(context, "0");
|
||||
}
|
||||
try formatInt(exp, 10, false, 0, context, Errors, output);
|
||||
} else {
|
||||
try output(context, "-");
|
||||
if (exp > -10 and exp < 10) {
|
||||
try output(context, "0");
|
||||
}
|
||||
try formatInt(-exp, 10, false, 0, context, Errors, output);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn formatFloatDecimal(value: var, precision: usize, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void {
|
||||
// Print a float of the format x.yyyyy where the number of y is specified by the precision argument.
|
||||
// By default floats are printed at full precision (no rounding).
|
||||
pub fn formatFloatDecimal(value: var, maybe_precision: ?usize, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void {
|
||||
var x = f64(value);
|
||||
|
||||
// Errol doesn't handle these special cases.
|
||||
if (math.isNan(x)) {
|
||||
return output(context, "NaN");
|
||||
}
|
||||
if (math.signbit(x)) {
|
||||
try output(context, "-");
|
||||
x = -x;
|
||||
}
|
||||
|
||||
if (math.isNan(x)) {
|
||||
return output(context, "nan");
|
||||
}
|
||||
if (math.isPositiveInf(x)) {
|
||||
return output(context, "Infinity");
|
||||
return output(context, "inf");
|
||||
}
|
||||
if (x == 0.0) {
|
||||
return output(context, "0.0");
|
||||
try output(context, "0");
|
||||
|
||||
if (maybe_precision) |precision| {
|
||||
if (precision != 0) {
|
||||
try output(context, ".");
|
||||
var i: usize = 0;
|
||||
while (i < precision) : (i += 1) {
|
||||
try output(context, "0");
|
||||
}
|
||||
} else {
|
||||
try output(context, ".0");
|
||||
}
|
||||
} else {
|
||||
try output(context, "0");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// non-special case, use errol3
|
||||
var buffer: [32]u8 = undefined;
|
||||
const float_decimal = errol3(x, buffer[0..]);
|
||||
var float_decimal = errol.errol3(x, buffer[0..]);
|
||||
|
||||
const num_left_digits = if (float_decimal.exp > 0) usize(float_decimal.exp) else 1;
|
||||
if (maybe_precision) |precision| {
|
||||
errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Decimal);
|
||||
|
||||
try output(context, float_decimal.digits[0 .. num_left_digits]);
|
||||
try output(context, ".");
|
||||
if (float_decimal.digits.len > 1) {
|
||||
const num_valid_digtis = if (@typeOf(value) == f32) math.min(usize(7), float_decimal.digits.len)
|
||||
else
|
||||
float_decimal.digits.len;
|
||||
// exp < 0 means the leading is always 0 as errol result is normalized.
|
||||
var num_digits_whole = if (float_decimal.exp > 0) usize(float_decimal.exp) else 0;
|
||||
|
||||
const num_right_digits = if (precision != 0)
|
||||
math.min(precision, (num_valid_digtis-num_left_digits))
|
||||
else
|
||||
num_valid_digtis - num_left_digits;
|
||||
try output(context, float_decimal.digits[num_left_digits .. (num_left_digits + num_right_digits)]);
|
||||
// the actual slice into the buffer, we may need to zero-pad between num_digits_whole and this.
|
||||
var num_digits_whole_no_pad = math.min(num_digits_whole, float_decimal.digits.len);
|
||||
|
||||
if (num_digits_whole > 0) {
|
||||
// We may have to zero pad, for instance 1e4 requires zero padding.
|
||||
try output(context, float_decimal.digits[0 .. num_digits_whole_no_pad]);
|
||||
|
||||
var i = num_digits_whole_no_pad;
|
||||
while (i < num_digits_whole) : (i += 1) {
|
||||
try output(context, "0");
|
||||
}
|
||||
} else {
|
||||
try output(context , "0");
|
||||
}
|
||||
|
||||
// {.0} special case doesn't want a trailing '.'
|
||||
if (precision == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try output(context, ".");
|
||||
|
||||
// Keep track of fractional count printed for case where we pre-pad then post-pad with 0's.
|
||||
var printed: usize = 0;
|
||||
|
||||
// Zero-fill until we reach significant digits or run out of precision.
|
||||
if (float_decimal.exp <= 0) {
|
||||
const zero_digit_count = usize(-float_decimal.exp);
|
||||
const zeros_to_print = math.min(zero_digit_count, precision);
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < zeros_to_print) : (i += 1) {
|
||||
try output(context, "0");
|
||||
printed += 1;
|
||||
}
|
||||
|
||||
if (printed >= precision) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Remaining fractional portion, zero-padding if insufficient.
|
||||
debug.assert(precision >= printed);
|
||||
if (num_digits_whole_no_pad + precision - printed < float_decimal.digits.len) {
|
||||
try output(context, float_decimal.digits[num_digits_whole_no_pad .. num_digits_whole_no_pad + precision - printed]);
|
||||
return;
|
||||
} else {
|
||||
try output(context, float_decimal.digits[num_digits_whole_no_pad ..]);
|
||||
printed += float_decimal.digits.len - num_digits_whole_no_pad;
|
||||
|
||||
while (printed < precision) : (printed += 1) {
|
||||
try output(context, "0");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try output(context, "0");
|
||||
// exp < 0 means the leading is always 0 as errol result is normalized.
|
||||
var num_digits_whole = if (float_decimal.exp > 0) usize(float_decimal.exp) else 0;
|
||||
|
||||
// the actual slice into the buffer, we may need to zero-pad between num_digits_whole and this.
|
||||
var num_digits_whole_no_pad = math.min(num_digits_whole, float_decimal.digits.len);
|
||||
|
||||
if (num_digits_whole > 0) {
|
||||
// We may have to zero pad, for instance 1e4 requires zero padding.
|
||||
try output(context, float_decimal.digits[0 .. num_digits_whole_no_pad]);
|
||||
|
||||
var i = num_digits_whole_no_pad;
|
||||
while (i < num_digits_whole) : (i += 1) {
|
||||
try output(context, "0");
|
||||
}
|
||||
} else {
|
||||
try output(context , "0");
|
||||
}
|
||||
|
||||
// Omit `.` if no fractional portion
|
||||
if (float_decimal.exp >= 0 and num_digits_whole_no_pad == float_decimal.digits.len) {
|
||||
return;
|
||||
}
|
||||
|
||||
try output(context, ".");
|
||||
|
||||
// Zero-fill until we reach significant digits or run out of precision.
|
||||
if (float_decimal.exp < 0) {
|
||||
const zero_digit_count = usize(-float_decimal.exp);
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < zero_digit_count) : (i += 1) {
|
||||
try output(context, "0");
|
||||
}
|
||||
}
|
||||
|
||||
try output(context, float_decimal.digits[num_digits_whole_no_pad ..]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -594,70 +779,210 @@ test "fmt.format" {
|
|||
const result = try bufPrint(buf1[0..], "pointer: {}\n", &value);
|
||||
assert(mem.startsWith(u8, result, "pointer: Struct@"));
|
||||
}
|
||||
|
||||
// TODO get these tests passing in release modes
|
||||
// https://github.com/zig-lang/zig/issues/564
|
||||
if (builtin.mode == builtin.Mode.Debug) {
|
||||
{
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f32 = 1.34;
|
||||
const result = try bufPrint(buf1[0..], "f32: {e}\n", value);
|
||||
assert(mem.eql(u8, result, "f32: 1.34000003e+00\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f32 = 12.34;
|
||||
const result = try bufPrint(buf1[0..], "f32: {e}\n", value);
|
||||
assert(mem.eql(u8, result, "f32: 1.23400001e+01\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = -12.34e10;
|
||||
const result = try bufPrint(buf1[0..], "f64: {e}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: -1.234e+11\n"));
|
||||
}
|
||||
{
|
||||
// This fails on release due to a minor rounding difference.
|
||||
// --release-fast outputs 9.999960000000001e-40 vs. the expected.
|
||||
if (builtin.mode == builtin.Mode.Debug) {
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f32 = 12.34;
|
||||
const result = try bufPrint(buf1[0..], "f32: {}\n", value);
|
||||
assert(mem.eql(u8, result, "f32: 1.23400001e1\n"));
|
||||
const value: f64 = 9.999960e-40;
|
||||
const result = try bufPrint(buf1[0..], "f64: {e}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 9.99996e-40\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = -12.34e10;
|
||||
const result = try bufPrint(buf1[0..], "f64: {}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: -1.234e11\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const result = try bufPrint(buf1[0..], "f64: {}\n", math.nan_f64);
|
||||
assert(mem.eql(u8, result, "f64: NaN\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const result = try bufPrint(buf1[0..], "f64: {}\n", math.inf_f64);
|
||||
assert(mem.eql(u8, result, "f64: Infinity\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const result = try bufPrint(buf1[0..], "f64: {}\n", -math.inf_f64);
|
||||
assert(mem.eql(u8, result, "f64: -Infinity\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f32 = 1.1234;
|
||||
const result = try bufPrint(buf1[0..], "f32: {.1}\n", value);
|
||||
assert(mem.eql(u8, result, "f32: 1.1\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f32 = 1234.567;
|
||||
const result = try bufPrint(buf1[0..], "f32: {.2}\n", value);
|
||||
assert(mem.eql(u8, result, "f32: 1234.56\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f32 = -11.1234;
|
||||
const result = try bufPrint(buf1[0..], "f32: {.4}\n", value);
|
||||
// -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64).
|
||||
// -11.12339... is truncated to -11.1233
|
||||
assert(mem.eql(u8, result, "f32: -11.1233\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f32 = 91.12345;
|
||||
const result = try bufPrint(buf1[0..], "f32: {.}\n", value);
|
||||
assert(mem.eql(u8, result, "f32: 91.12345\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = 91.12345678901235;
|
||||
const result = try bufPrint(buf1[0..], "f64: {.10}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 91.1234567890\n"));
|
||||
}
|
||||
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = 1.409706e-42;
|
||||
const result = try bufPrint(buf1[0..], "f64: {e5}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 1.40971e-42\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = @bitCast(f32, u32(814313563));
|
||||
const result = try bufPrint(buf1[0..], "f64: {e5}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 1.00000e-09\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = @bitCast(f32, u32(1006632960));
|
||||
const result = try bufPrint(buf1[0..], "f64: {e5}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 7.81250e-03\n"));
|
||||
}
|
||||
{
|
||||
// libc rounds 1.000005e+05 to 1.00000e+05 but zig does 1.00001e+05.
|
||||
// In fact, libc doesn't round a lot of 5 cases up when one past the precision point.
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = @bitCast(f32, u32(1203982400));
|
||||
const result = try bufPrint(buf1[0..], "f64: {e5}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 1.00001e+05\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const result = try bufPrint(buf1[0..], "f64: {}\n", math.nan_f64);
|
||||
assert(mem.eql(u8, result, "f64: nan\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const result = try bufPrint(buf1[0..], "f64: {}\n", -math.nan_f64);
|
||||
assert(mem.eql(u8, result, "f64: -nan\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const result = try bufPrint(buf1[0..], "f64: {}\n", math.inf_f64);
|
||||
assert(mem.eql(u8, result, "f64: inf\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const result = try bufPrint(buf1[0..], "f64: {}\n", -math.inf_f64);
|
||||
assert(mem.eql(u8, result, "f64: -inf\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [64]u8 = undefined;
|
||||
const value: f64 = 1.52314e+29;
|
||||
const result = try bufPrint(buf1[0..], "f64: {.}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 152314000000000000000000000000\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f32 = 1.1234;
|
||||
const result = try bufPrint(buf1[0..], "f32: {.1}\n", value);
|
||||
assert(mem.eql(u8, result, "f32: 1.1\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f32 = 1234.567;
|
||||
const result = try bufPrint(buf1[0..], "f32: {.2}\n", value);
|
||||
assert(mem.eql(u8, result, "f32: 1234.57\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f32 = -11.1234;
|
||||
const result = try bufPrint(buf1[0..], "f32: {.4}\n", value);
|
||||
// -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64).
|
||||
// -11.12339... is rounded back up to -11.1234
|
||||
assert(mem.eql(u8, result, "f32: -11.1234\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f32 = 91.12345;
|
||||
const result = try bufPrint(buf1[0..], "f32: {.5}\n", value);
|
||||
assert(mem.eql(u8, result, "f32: 91.12345\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = 91.12345678901235;
|
||||
const result = try bufPrint(buf1[0..], "f64: {.10}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 91.1234567890\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = 0.0;
|
||||
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 0.00000\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = 5.700;
|
||||
const result = try bufPrint(buf1[0..], "f64: {.0}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 6\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = 9.999;
|
||||
const result = try bufPrint(buf1[0..], "f64: {.1}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 10.0\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = 1.0;
|
||||
const result = try bufPrint(buf1[0..], "f64: {.3}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 1.000\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = 0.0003;
|
||||
const result = try bufPrint(buf1[0..], "f64: {.8}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 0.00030000\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = 1.40130e-45;
|
||||
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 0.00000\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = 9.999960e-40;
|
||||
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 0.00000\n"));
|
||||
}
|
||||
// libc checks
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = f64(@bitCast(f32, u32(916964781)));
|
||||
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 0.00001\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = f64(@bitCast(f32, u32(925353389)));
|
||||
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 0.00001\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = f64(@bitCast(f32, u32(1036831278)));
|
||||
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 0.10000\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = f64(@bitCast(f32, u32(1065353133)));
|
||||
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 1.00000\n"));
|
||||
}
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = f64(@bitCast(f32, u32(1092616192)));
|
||||
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 10.00000\n"));
|
||||
}
|
||||
// libc differences
|
||||
{
|
||||
var buf1: [32]u8 = undefined;
|
||||
// This is 0.015625 exactly according to gdb. We thus round down,
|
||||
// however glibc rounds up for some reason. This occurs for all
|
||||
// floats of the form x.yyyy25 on a precision point.
|
||||
const value: f64 = f64(@bitCast(f32, u32(1015021568)));
|
||||
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 0.01563\n"));
|
||||
}
|
||||
// std-windows-x86_64-Debug-bare test case fails
|
||||
{
|
||||
// errol3 rounds to ... 630 but libc rounds to ...632. Grisu3
|
||||
// also rounds to 630 so I'm inclined to believe libc is not
|
||||
// optimal here.
|
||||
var buf1: [32]u8 = undefined;
|
||||
const value: f64 = f64(@bitCast(f32, u32(1518338049)));
|
||||
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
|
||||
assert(mem.eql(u8, result, "f64: 18014400656965630.00000\n"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -281,7 +281,7 @@ test "os.time.Timer" {
|
|||
debug.assert(time_0 > 0 and time_0 < margin);
|
||||
|
||||
const time_1 = timer.lap();
|
||||
debug.assert(time_1 > time_0);
|
||||
debug.assert(time_1 >= time_0);
|
||||
|
||||
timer.reset();
|
||||
debug.assert(timer.read() < time_1);
|
||||
|
|
|
@ -32,10 +32,6 @@ comptime {
|
|||
@export("__fixunstfti", @import("fixunstfti.zig").__fixunstfti, linkage);
|
||||
|
||||
@export("__udivmoddi4", @import("udivmoddi4.zig").__udivmoddi4, linkage);
|
||||
@export("__udivmodti4", @import("udivmodti4.zig").__udivmodti4, linkage);
|
||||
|
||||
@export("__udivti3", @import("udivti3.zig").__udivti3, linkage);
|
||||
@export("__umodti3", @import("umodti3.zig").__umodti3, linkage);
|
||||
|
||||
@export("__udivsi3", __udivsi3, linkage);
|
||||
@export("__udivdi3", __udivdi3, linkage);
|
||||
|
@ -62,9 +58,16 @@ comptime {
|
|||
@export("__chkstk", __chkstk, strong_linkage);
|
||||
@export("___chkstk_ms", ___chkstk_ms, linkage);
|
||||
}
|
||||
@export("__udivti3", @import("udivti3.zig").__udivti3_windows_x86_64, linkage);
|
||||
@export("__udivmodti4", @import("udivmodti4.zig").__udivmodti4_windows_x86_64, linkage);
|
||||
@export("__umodti3", @import("umodti3.zig").__umodti3_windows_x86_64, linkage);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
} else {
|
||||
@export("__udivti3", @import("udivti3.zig").__udivti3, linkage);
|
||||
@export("__udivmodti4", @import("udivmodti4.zig").__udivmodti4, linkage);
|
||||
@export("__umodti3", @import("umodti3.zig").__umodti3, linkage);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,6 +86,16 @@ pub fn panic(msg: []const u8, error_return_trace: ?&builtin.StackTrace) noreturn
|
|||
}
|
||||
}
|
||||
|
||||
pub fn setXmm0(comptime T: type, value: T) void {
|
||||
comptime assert(builtin.arch == builtin.Arch.x86_64);
|
||||
const aligned_value: T align(16) = value;
|
||||
asm volatile (
|
||||
\\movaps (%[ptr]), %%xmm0
|
||||
:
|
||||
: [ptr] "r" (&aligned_value)
|
||||
: "xmm0");
|
||||
}
|
||||
|
||||
extern fn __udivdi3(a: u64, b: u64) u64 {
|
||||
@setRuntimeSafety(is_test);
|
||||
return __udivmoddi4(a, b, null);
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
const udivmod = @import("udivmod.zig").udivmod;
|
||||
const builtin = @import("builtin");
|
||||
const compiler_rt = @import("index.zig");
|
||||
|
||||
pub extern fn __udivmodti4(a: u128, b: u128, maybe_rem: ?&u128) u128 {
|
||||
@setRuntimeSafety(builtin.is_test);
|
||||
return udivmod(u128, a, b, maybe_rem);
|
||||
}
|
||||
|
||||
pub extern fn __udivmodti4_windows_x86_64(a: &const u128, b: &const u128, maybe_rem: ?&u128) void {
|
||||
@setRuntimeSafety(builtin.is_test);
|
||||
compiler_rt.setXmm0(u128, udivmod(u128, *a, *b, maybe_rem));
|
||||
}
|
||||
|
||||
test "import udivmodti4" {
|
||||
_ = @import("udivmodti4_test.zig");
|
||||
}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
const __udivmodti4 = @import("udivmodti4.zig").__udivmodti4;
|
||||
const udivmodti4 = @import("udivmodti4.zig");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pub extern fn __udivti3(a: u128, b: u128) u128 {
|
||||
@setRuntimeSafety(builtin.is_test);
|
||||
return __udivmodti4(a, b, null);
|
||||
return udivmodti4.__udivmodti4(a, b, null);
|
||||
}
|
||||
|
||||
pub extern fn __udivti3_windows_x86_64(a: &const u128, b: &const u128) void {
|
||||
@setRuntimeSafety(builtin.is_test);
|
||||
udivmodti4.__udivmodti4_windows_x86_64(a, b, null);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
const __udivmodti4 = @import("udivmodti4.zig").__udivmodti4;
|
||||
const udivmodti4 = @import("udivmodti4.zig");
|
||||
const builtin = @import("builtin");
|
||||
const compiler_rt = @import("index.zig");
|
||||
|
||||
pub extern fn __umodti3(a: u128, b: u128) u128 {
|
||||
@setRuntimeSafety(builtin.is_test);
|
||||
var r: u128 = undefined;
|
||||
_ = __udivmodti4(a, b, &r);
|
||||
_ = udivmodti4.__udivmodti4(a, b, &r);
|
||||
return r;
|
||||
}
|
||||
|
||||
pub extern fn __umodti3_windows_x86_64(a: &const u128, b: &const u128) void {
|
||||
@setRuntimeSafety(builtin.is_test);
|
||||
compiler_rt.setXmm0(u128, __umodti3(*a, *b));
|
||||
}
|
||||
|
|
|
@ -529,3 +529,10 @@ test "comptime shlWithOverflow" {
|
|||
|
||||
assert(ct_shifted == rt_shifted);
|
||||
}
|
||||
|
||||
test "runtime 128 bit integer division" {
|
||||
var a: u128 = 152313999999999991610955792383;
|
||||
var b: u128 = 10000000000000000000;
|
||||
var c = a / b;
|
||||
assert(c == 15231399999);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user