diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..48efcbc --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,235 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'ryu-js'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=ryu-js" + ], + "filter": { + "name": "ryu-js", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'upstream_benchmark'", + "cargo": { + "args": [ + "build", + "--example=upstream_benchmark", + "--package=ryu-js" + ], + "filter": { + "name": "upstream_benchmark", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'upstream_benchmark'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=upstream_benchmark", + "--package=ryu-js" + ], + "filter": { + "name": "upstream_benchmark", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'd2s_test'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=d2s_test", + "--package=ryu-js" + ], + "filter": { + "name": "d2s_test", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 's2f_test'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=s2f_test", + "--package=ryu-js" + ], + "filter": { + "name": "s2f_test", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'to_fixed'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=to_fixed", + "--package=ryu-js" + ], + "filter": { + "name": "to_fixed", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 's2d_test'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=s2d_test", + "--package=ryu-js" + ], + "filter": { + "name": "s2d_test", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'common_test'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=common_test", + "--package=ryu-js" + ], + "filter": { + "name": "common_test", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'exhaustive'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=exhaustive", + "--package=ryu-js" + ], + "filter": { + "name": "exhaustive", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'f2s_test'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=f2s_test", + "--package=ryu-js" + ], + "filter": { + "name": "f2s_test", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'd2s_table_test'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=d2s_table_test", + "--package=ryu-js" + ], + "filter": { + "name": "d2s_table_test", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug benchmark 'bench'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bench=bench", + "--package=ryu-js" + ], + "filter": { + "name": "bench", + "kind": "bench" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/Cargo.toml b/Cargo.toml index dbb04fd..2b880c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,4 +38,3 @@ targets = ["x86_64-unknown-linux-gnu"] [[bench]] name = "bench" harness = false - diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index c4408d1..0a0fdb3 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -1,3 +1,5 @@ +use crate::pretty::to_fixed::MAX_BUFFER_SIZE; + use crate::raw; use core::mem::MaybeUninit; use core::{slice, str}; @@ -8,6 +10,8 @@ const NAN: &str = "NaN"; const INFINITY: &str = "Infinity"; const NEG_INFINITY: &str = "-Infinity"; +const BUFFER_SIZE: usize = MAX_BUFFER_SIZE; + /// Safe API for formatting floating point numbers to text. /// /// ## Example @@ -17,8 +21,9 @@ const NEG_INFINITY: &str = "-Infinity"; /// let printed = buffer.format_finite(1.234); /// assert_eq!(printed, "1.234"); /// ``` +#[derive(Copy, Clone)] pub struct Buffer { - bytes: [MaybeUninit; 25], + bytes: [MaybeUninit; BUFFER_SIZE], } impl Buffer { @@ -27,7 +32,7 @@ impl Buffer { #[inline] #[cfg_attr(feature = "no-panic", no_panic)] pub fn new() -> Self { - let bytes = [MaybeUninit::::uninit(); 25]; + let bytes = [MaybeUninit::::uninit(); BUFFER_SIZE]; Buffer { bytes } } @@ -80,14 +85,37 @@ impl Buffer { str::from_utf8_unchecked(slice) } } -} -impl Copy for Buffer {} + /// Print a floating point number into this buffer using the `Number.prototype.toFixed()` notation + /// and return a reference to its string representation within the buffer. + /// + /// The `fraction_digits` argument must be between `[0, 100]` inclusive, + /// If a values value that is greater than the max is passed in will be clamped to max. + /// + /// # Special cases + /// + /// This function formats NaN as the string "NaN", positive infinity as + /// "Infinity", and negative infinity as "-Infinity" to match the [ECMAScript specification][spec]. + /// + /// [spec]: https://tc39.es/ecma262/#sec-numeric-types-number-tofixed + #[cfg_attr(feature = "no-panic", inline)] + #[cfg_attr(feature = "no-panic", no_panic)] + pub fn format_to_fixed(&mut self, f: F, fraction_digits: u8) -> &str { + let fraction_digits = fraction_digits.min(100); + + if f.is_nonfinite() { + return f.format_nonfinite(); + } -impl Clone for Buffer { - #[inline] - fn clone(&self) -> Self { - *self + unsafe { + let n = f.write_to_ryu_buffer_to_fixed( + fraction_digits, + self.bytes.as_mut_ptr().cast::(), + ); + debug_assert!(n <= self.bytes.len()); + let slice = slice::from_raw_parts(self.bytes.as_ptr().cast::(), n); + str::from_utf8_unchecked(slice) + } } } @@ -99,19 +127,31 @@ impl Default for Buffer { } } -/// A floating point number, f32 or f64, that can be written into a +/// A floating point number, [`f32`] or [`f64`], that can be written into a /// [`ryu_js::Buffer`][Buffer]. /// /// This trait is sealed and cannot be implemented for types outside of the -/// `ryu_js` crate. +/// `ryu-js` crate. pub trait Float: Sealed {} impl Float for f32 {} impl Float for f64 {} +/// A floating point number that can be written into a +/// [`ryu_js::Buffer`][Buffer] using the fixed notation as defined in the +/// [`Number.prototype.toFixed( fractionDigits )`][spec] ECMAScript specification. +/// +/// This trait is sealed and cannot be implemented for types outside of the +/// `ryu-js` crate. +/// +/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tofixed +pub trait FloatToFixed: Sealed {} +impl FloatToFixed for f64 {} + pub trait Sealed: Copy { fn is_nonfinite(self) -> bool; fn format_nonfinite(self) -> &'static str; unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize; + unsafe fn write_to_ryu_buffer_to_fixed(self, fraction_digits: u8, result: *mut u8) -> usize; } impl Sealed for f32 { @@ -141,6 +181,11 @@ impl Sealed for f32 { unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize { raw::format32(self, result) } + + #[inline] + unsafe fn write_to_ryu_buffer_to_fixed(self, _fraction_digits: u8, _result: *mut u8) -> usize { + panic!("toFixed for f32 type is not implemented yet!") + } } impl Sealed for f64 { @@ -170,4 +215,9 @@ impl Sealed for f64 { unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize { raw::format64(self, result) } + + #[inline] + unsafe fn write_to_ryu_buffer_to_fixed(self, fraction_digits: u8, result: *mut u8) -> usize { + raw::format64_to_fixed(self, fraction_digits, result) + } } diff --git a/src/lib.rs b/src/lib.rs index 7762ea2..5674318 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,9 +87,10 @@ mod f2s; mod f2s_intrinsics; mod pretty; -pub use crate::buffer::{Buffer, Float}; +pub use crate::buffer::{Buffer, Float, FloatToFixed}; /// Unsafe functions that mirror the API of the C implementation of Ryū. pub mod raw { + pub use crate::pretty::format64_to_fixed; pub use crate::pretty::{format32, format64}; } diff --git a/src/pretty/mod.rs b/src/pretty/mod.rs index 6977339..bd18e95 100644 --- a/src/pretty/mod.rs +++ b/src/pretty/mod.rs @@ -10,6 +10,9 @@ use core::ptr; #[cfg(feature = "no-panic")] use no_panic::no_panic; +pub mod to_fixed; +pub use to_fixed::{format64_to_fixed, Cursor}; + /// Print f64 to the given buffer and return number of bytes written. /// /// At most 25 bytes will be written. diff --git a/src/pretty/to_fixed/d2fixed_full_table.rs b/src/pretty/to_fixed/d2fixed_full_table.rs new file mode 100644 index 0000000..68e07f9 --- /dev/null +++ b/src/pretty/to_fixed/d2fixed_full_table.rs @@ -0,0 +1,566 @@ +pub(crate) const TABLE_SIZE: usize = 3; + +pub(crate) const POW10_OFFSET: [u8; TABLE_SIZE] = [0, 2, 5]; + +#[rustfmt::skip] +pub(crate) const POW10_SPLIT: [[u64; 3]; 8] = [ + [ 1, 72057594037927936, 0], // 0 + [ 699646928636035157, 72057594, 0], // 1 + [ 1, 0, 256], // 2 + [11902091922964236229, 4722366482869, 0], // 3 + [ 6760415703743915872, 4722, 0], // 4 + [ 1, 0, 16777216], // 5 + + [13369850649504950658, 309485009821345068, 0], // 5 (+1 length) + [15151142278969419334, 309485009, 0], // 5 (+2 length) +]; + +pub(crate) const TABLE_SIZE_2: usize = 27; +pub(crate) const ADDITIONAL_BITS_2: usize = 120; + +#[rustfmt::skip] +pub(crate) const POW10_OFFSET_2: [u16; TABLE_SIZE_2] = [ + /* 00 */ 0, + /* 01 */ 2, + /* 02 */ 6, + /* 03 */ 12, + /* 04 */ 20, + /* 05 */ 29, + /* 06 */ 40, + /* 07 */ 52, + /* 08 */ 66, + /* 09 */ 80, + /* 10 */ 95, + /* 11 */ 112, + /* 12 */ 130, + /* 13 */ 150, + /* 14 */ 170, + /* 15 */ 192, + /* 16 */ 215, + /* 17 */ 240, + /* 18 */ 265, + /* 19 */ 292, + /* 20 */ 320, + /* 21 */ 350, + /* 22 */ 381, + /* 23 */ 413, + /* 24 */ 446, + /* 25 */ 480, + /* 26 */ 516, +]; + +#[rustfmt::skip] +pub(crate) const MIN_BLOCK_2: [u8; TABLE_SIZE_2] = [ + /* 00 */ 0, + /* 01 */ 0, + /* 02 */ 0, + /* 03 */ 0, + /* 04 */ 0, + /* 05 */ 0, + /* 06 */ 1, + /* 07 */ 1, + /* 08 */ 2, + /* 09 */ 3, + /* 10 */ 3, + /* 11 */ 4, + /* 12 */ 4, + /* 13 */ 5, + /* 14 */ 5, + /* 15 */ 6, + /* 16 */ 6, + /* 17 */ 7, + /* 18 */ 7, + /* 19 */ 8, + /* 20 */ 8, + /* 21 */ 9, + /* 22 */ 9, + /* 23 */ 10, + /* 24 */ 11, + /* 25 */ 11, + /* 26 */ 12, +]; + +#[rustfmt::skip] +pub(crate) const POW10_SPLIT_2: [[u64; 3]; 480 + 1] = [ + [0, 0, 3906250], + [0, 0, 202000000000], + [0, 11153727427136454656, 59], + [0, 7205759403792793600, 59604644775], + [0, 0, 167390625000], + [0, 0, 232000000000], + [0, 16777216000000000, 0], + [0, 12945425605062557696, 909494], + [0, 4388757836872548352, 182701772928], + [0, 1152921504606846976, 128237915039], + [0, 0, 159062500000], + [0, 0, 160000000000], + [0, 256000000000, 0], + [0, 16192327041775828992, 13], + [0, 15024075324038053888, 13877787807], + [0, 5449091666327633920, 159814456755], + [0, 2494994193563254784, 179295395851], + [0, 4611686018427387904, 11135253906], + [0, 0, 146250000000], + [0, 0, 128000000000], + [0, 3906250, 0], + [0, 3906250000000000, 0], + [0, 4368439412768899072, 211758], + [0, 1563676642168012800, 46236813575], + [0, 11532349341402398720, 7084767080], + [0, 9048364970084925440, 104625169910], + [0, 16609275425742389248, 246490512847], + [0, 0, 207900390625], + [0, 0, 225000000000], + [11153727427136454656, 59, 0], + [7205759403792793600, 59604644775, 0], + [0, 4264412554261970152, 3], + [0, 14485570586272534528, 3231174267], + [0, 17827675094632103936, 123785264354], + [0, 7347197909193981952, 226966440203], + [0, 13677404030777688064, 11398292396], + [0, 3810326759732150272, 172741453558], + [0, 9943947977234055168, 246206558227], + [0, 0, 19539062500], + [0, 0, 228000000000], + [12945425605062557696, 909494, 0], + [4388757836872548352, 909494701772928, 0], + [1152921504606846976, 14878706826214591391, 49303], + [0, 4387341015746028192, 151806576313], + [0, 651726680428265472, 185237838233], + [0, 2570638187944738816, 153035330174], + [0, 7419175577111756800, 126139354575], + [0, 17299322326264840192, 207402194313], + [0, 7990511638862102528, 137937798142], + [0, 16717361816799281152, 254433166503], + [0, 0, 167906250000], + [0, 0, 16000000000], + [16192327041775828992, 13, 0], + [15024075324038053888, 13877787807, 0], + [5449091666327633920, 13877787807814456755, 0], + [2494994193563254784, 9707857417284919307, 752316384], + [4611686018427387904, 1844515466944871826, 224526264005], + [0, 15167599819856275072, 197099991383], + [0, 14830185305589481472, 87822237233], + [0, 6163721531743535104, 49803945956], + [0, 14122847407012052992, 228334136013], + [0, 335491783960035328, 205765601092], + [0, 941252322120433664, 68018187046], + [0, 11529215046068469760, 38051025390], + [0, 0, 238625000000], + [0, 0, 64000000000], + [4368439412768899072, 211758, 0], + [1563676642168012800, 211758236813575, 0], + [11532349341402398720, 8061591463141767016, 11479], + [9048364970084925440, 16628725344207857142, 215437019748], + [16609275425742389248, 3555541870038531535, 100901445007], + [0, 18316647450161853665, 143192746310], + [0, 16709574568378075648, 70992947447], + [0, 7696022835795591168, 247905827852], + [0, 16664449640376041472, 12417202233], + [0, 3109186955116544000, 57903381625], + [0, 10515518101817131008, 121168549362], + [0, 9961962375743537152, 242570047378], + [0, 9223372036854775808, 146540039062], + [0, 0, 150500000000], + [14485570586272534528, 3231174267, 0], + [17827675094632103936, 3231174267785264354, 0], + [7347197909193981952, 748977172262750475, 175162308], + [13677404030777688064, 15965033457315095468, 196040602133], + [3810326759732150272, 16809402149066729206, 21865466197], + [9943947977234055168, 7563769067065700371, 85911239516], + [0, 13550322810840051428, 92410032742], + [0, 8663209637545764864, 102734564471], + [0, 8969247575312957440, 119469633535], + [0, 6193172891660451840, 255486223885], + [0, 3427954273864908800, 13335732575], + [0, 10058367555266936832, 95185829773], + [0, 13907115649320091648, 141545265197], + [0, 0, 45753906250], + [0, 0, 74000000000], + [14878706826214591391, 49303, 0], + [4387341015746028192, 49303806576313, 0], + [651726680428265472, 14106411361315920281, 2672], + [2570638187944738816, 3609034283485221502, 112764710092], + [7419175577111756800, 9896072247338192335, 204195646140], + [17299322326264840192, 8889095178479228297, 188536467151], + [7990511638862102528, 3631796911038383102, 207481878815], + [16717361816799281152, 898318840772166823, 31196880105], + [0, 17293677953982795024, 233048697961], + [0, 7353628266884669440, 105937492160], + [0, 2404693032470315008, 192398640987], + [0, 9191155893041889280, 91130358670], + [0, 6353946855033798656, 142498253559], + [0, 3767824038248841216, 247344448149], + [0, 7205759403792793600, 149204254150], + [0, 0, 198390625000], + [0, 0, 232000000000], + [9707857417284919307, 752316384, 0], + [1844515466944871826, 752316384526264005, 0], + [15167599819856275072, 17063068157692817751, 40783152], + [14830185305589481472, 5385330256507239985, 48924990778], + [6163721531743535104, 3373050282752075748, 58291939338], + [14122847407012052992, 4116064001262906061, 10182853422], + [335491783960035328, 11306582046748043076, 46223132276], + [941252322120433664, 17035410946089626406, 116612931040], + [11529215046068469760, 15618595715183448558, 224923491477], + [0, 5141740092277295680, 149846685770], + [0, 16973644291514990592, 74278734288], + [0, 14625255268443750400, 208920143100], + [0, 14021170507320131584, 252792836676], + [0, 4451355232865091584, 68760089176], + [0, 12891553933348044800, 88241308450], + [0, 1152921504606846976, 34698852539], + [0, 0, 187062500000], + [0, 0, 160000000000], + [8061591463141767016, 11479, 0], + [16628725344207857142, 11479437019748, 0], + [3555541870038531535, 5562205901560339855, 622], + [18316647450161853665, 2106077949367544134, 110301527786], + [16709574568378075648, 7496855998374373623, 234114170714], + [7696022835795591168, 229183437194837004, 90406405378], + [16664449640376041472, 465169186276472889, 2012424059], + [3109186955116544000, 2152980561625316473, 123025216872], + [10515518101817131008, 2059790725449340402, 104116713310], + [9961962375743537152, 17891190926410198930, 94111661478], + [9223372036854775808, 9930696175609809814, 166969883403], + [0, 7276914261609005312, 11538344118], + [0, 10539762974036983808, 182394482312], + [0, 12851089458992250880, 136571361695], + [0, 9449311677678878720, 159696658955], + [0, 8699564697382289408, 11512248212], + [0, 4224376450473525248, 148471604347], + [0, 4611686018427387904, 123229003906], + [0, 0, 130250000000], + [0, 0, 128000000000], + [748977172262750475, 175162308, 0], + [15965033457315095468, 175162308040602133, 0], + [16809402149066729206, 13756840147955779925, 9495567], + [7563769067065700371, 13788447602092505948, 15745759798], + [13550322810840051428, 4972540435632173670, 54747473242], + [8663209637545764864, 2844874687533091959, 90269561957], + [8969247575312957440, 15377573779532804095, 101154220965], + [6193172891660451840, 17824715805091194381, 165833619944], + [3427954273864908800, 18277569135638159711, 232966279779], + [10058367555266936832, 4254645803379752845, 99990829008], + [13907115649320091648, 2933643244178200621, 208230644811], + [0, 17188148801879487562, 75159033118], + [0, 11069762501163246592, 30931771413], + [0, 11676570643941818368, 21600093027], + [0, 17840016768744030208, 99632988162], + [0, 16463817321652158464, 2967109246], + [0, 6954191143357644800, 126892505325], + [0, 5080060379673919488, 237376987457], + [0, 0, 65275390625], + [0, 0, 161000000000], + [14106411361315920281, 2672, 0], + [3609034283485221502, 2672764710092, 0], + [9896072247338192335, 16433563478020213436, 144], + [8889095178479228297, 4194750497955655375, 144890865261], + [3631796911038383102, 2691539602252904735, 109227397880], + [898318840772166823, 3775467271962795241, 248145908654], + [17293677953982795024, 16980212613224918121, 174204668490], + [7353628266884669440, 4172857038337333440, 74920499170], + [2404693032470315008, 5936867627376461659, 226226211033], + [9191155893041889280, 17856837443266866062, 217321838238], + [6353946855033798656, 8956297047799810807, 158968021097], + [3767824038248841216, 15356974049716912789, 105485521835], + [7205759403792793600, 6923608913322982854, 171832503231], + [0, 4855902993563955944, 191375329591], + [0, 13835893222288330752, 55263239028], + [0, 9114973913760137216, 116750045274], + [0, 17937099003422310400, 90494123725], + [0, 7007960010734960640, 205972372085], + [0, 7683422439270776832, 117379902273], + [0, 720575940379279360, 65416519165], + [0, 0, 253039062500], + [0, 0, 228000000000], + [17063068157692817751, 40783152, 0], + [5385330256507239985, 40783152924990778, 0], + [3373050282752075748, 2768933352715741194, 2210859], + [4116064001262906061, 15201941611824153390, 43150104177], + [11306582046748043076, 1418128541727000180, 113824098906], + [17035410946089626406, 5353350204565757408, 90076876902], + [15618595715183448558, 1721001680354286741, 102290205696], + [5141740092277295680, 637631411660453962, 93295688], + [16973644291514990592, 1630012588870568400, 72034566068], + [14625255268443750400, 9253063571656828156, 180088363159], + [14021170507320131584, 6029146854993203780, 151501609581], + [4451355232865091584, 16987401965352759896, 109326840705], + [12891553933348044800, 14499131620542087970, 129920888905], + [1152921504606846976, 1978417255298660539, 73785999500], + [0, 5790079354402454176, 140107250214], + [0, 13748918935842078720, 38313880830], + [0, 18047438014740692992, 254745330388], + [0, 3116889656839372800, 212978353575], + [0, 15995952446606147584, 167168966926], + [0, 12530140063251562496, 14867142319], + [0, 16717361816799281152, 175679260253], + [0, 0, 93906250000], + [0, 0, 16000000000], + [5562205901560339855, 622, 0], + [2106077949367544134, 622301527786, 0], + [7496855998374373623, 13558973353698967386, 33], + [229183437194837004, 6228991722850501890, 33735033418], + [465169186276472889, 16886831391703377787, 74337674317], + [2152980561625316473, 1181713637872883048, 77915436964], + [2059790725449340402, 12393932434925221726, 164064060824], + [17891190926410198930, 10684799845419711910, 152671876423], + [9930696175609809814, 4590318792215640843, 71579224160], + [7276914261609005312, 6383712187366189238, 96248841680], + [10539762974036983808, 1904270214927675016, 208346061731], + [12851089458992250880, 3711506775113308575, 163103230695], + [9449311677678878720, 8091219444738793995, 231201201185], + [8699564697382289408, 39436684991068052, 33438625885], + [4224376450473525248, 18025182908196512891, 93002137866], + [4611686018427387904, 7853924592034603138, 10977147123], + [0, 4815749283615688320, 243425762105], + [0, 14242399906544287744, 57261062291], + [0, 76242322576113664, 147772082046], + [0, 10858088421377703936, 126004133104], + [0, 14293835879041466368, 240588618152], + [0, 12182236992037191680, 168774870395], + [0, 11529215046068469760, 123660400390], + [0, 0, 6625000000], + [0, 0, 64000000000], + [13756840147955779925, 9495567, 0], + [13788447602092505948, 9495567745759798, 0], + [4972540435632173670, 14000097438505379162, 514755], + [2844874687533091959, 16451062686452429925, 195758946802], + [15377573779532804095, 4009347599785716645, 242891813895], + [17824715805091194381, 16544162347546196456, 7217347168], + [18277569135638159711, 17674258299745817187, 96896860837], + [4254645803379752845, 5215238411201214416, 165958123462], + [2933643244178200621, 14253990228345322571, 198282718640], + [17188148801879487562, 11214836553940194590, 176772710358], + [11069762501163246592, 14620711348380590101, 214607957507], + [11676570643941818368, 6638710787931587427, 3792590350], + [17840016768744030208, 17320000343692853250, 14359885232], + [16463817321652158464, 75147386268843646, 176938919100], + [6954191143357644800, 17938801582125480173, 188004073747], + [5080060379673919488, 6573358613626446145, 19972464382], + [0, 8688505427903736481, 254356342484], + [0, 539870168696556032, 212471004823], + [0, 9002861336394465280, 151029266420], + [0, 17989846818158018560, 244488046090], + [0, 2700938287723315200, 10975231550], + [0, 17800090499088908288, 62146418157], + [0, 8809040871136690176, 237964944839], + [0, 9223372036854775808, 199477539062], + [0, 0, 246500000000], + [16433563478020213436, 144, 0], + [4194750497955655375, 144890865261, 0], + [2691539602252904735, 15763656745260536568, 7], + [3775467271962795241, 8787336846248645550, 7854549544], + [16980212613224918121, 17584084447880694346, 40476362484], + [4172857038337333440, 18041672551129683938, 244953235127], + [5936867627376461659, 14025886302294509785, 183978041028], + [17856837443266866062, 18430498103283160734, 196760344819], + [8956297047799810807, 3292348826238025833, 243999119304], + [15356974049716912789, 9211721212658275243, 200178478587], + [6923608913322982854, 10233245872666307519, 251499368407], + [4855902993563955944, 6200995035623311671, 215554745370], + [13835893222288330752, 8480542380570450804, 26336156614], + [9114973913760137216, 11870363864499900506, 198459731123], + [17937099003422310400, 9301051379839581901, 179643493714], + [7007960010734960640, 11456694803569638005, 82504211005], + [7683422439270776832, 14327208890643983169, 61621068669], + [720575940379279360, 4510081789599866365, 125776679550], + [0, 13255356976020303332, 126244492023], + [0, 9658806854127314944, 247718574341], + [0, 13708435528809971712, 5523604968], + [0, 1580190652103131136, 232743135779], + [0, 16557336970347413504, 35085662306], + [0, 12751520132434493440, 98897575035], + [0, 9295429630892703744, 123691261291], + [0, 0, 107503906250], + [0, 0, 202000000000], + [2768933352715741194, 2210859, 0], + [15201941611824153390, 2210859150104177, 0], + [1418128541727000180, 16872870088062921306, 119850], + [5353350204565757408, 5112979788807802982, 42914680120], + [1721001680354286741, 13742728082020150272, 56277175189], + [637631411660453962, 2217110934613627976, 149744994782], + [1630012588870568400, 11021433940188610484, 222120189824], + [9253063571656828156, 1713669895470733463, 128597473131], + [6029146854993203780, 3313382510572018285, 107092898231], + [16987401965352759896, 14976595232784069505, 183179618825], + [14499131620542087970, 7213172372862496841, 9811882854], + [1978417255298660539, 15836474542502248588, 102391026857], + [5790079354402454176, 3221099285878340134, 169858497005], + [13748918935842078720, 3265814602578095358, 237174616142], + [18047438014740692992, 6502528252282225364, 78177040164], + [3116889656839372800, 16392476834556790183, 36352502762], + [15995952446606147584, 15167629413417091342, 234888637949], + [12530140063251562496, 1366763272626280111, 253822238838], + [16717361816799281152, 8720523635169216093, 118074092385], + [0, 9649171375767398672, 97472740533], + [0, 7647980704001073152, 181523082628], + [0, 13286434495608651776, 132414597864], + [0, 4358271637167013888, 232720259057], + [0, 15954987941890097152, 241236262378], + [0, 7911135695429697536, 234864921629], + [0, 7205759403792793600, 29428863525], + [0, 0, 37390625000], + [0, 0, 232000000000], + [13558973353698967386, 33, 0], + [6228991722850501890, 33735033418, 0], + [16886831391703377787, 15288289344628122701, 1], + [1181713637872883048, 952589339068938148, 1828779826], + [12393932434925221726, 10058155040190817688, 50051639971], + [10684799845419711910, 5322725640026584391, 163545253677], + [4590318792215640843, 2269982385930389600, 45288545535], + [6383712187366189238, 13216683679976310224, 255123055991], + [1904270214927675016, 17417440642083494819, 119716477857], + [3711506775113308575, 3029180749090900711, 161944201349], + [8091219444738793995, 8315443826261908513, 133164212217], + [39436684991068052, 1488962797247197277, 249450781113], + [18025182908196512891, 18009099634999034122, 185080716834], + [7853924592034603138, 8092455412807497971, 34976275247], + [4815749283615688320, 17808458047236758329, 47438692886], + [14242399906544287744, 3164591817527425171, 22965398445], + [76242322576113664, 3314036340472350590, 173171552866], + [10858088421377703936, 33234902404332784, 98179654270], + [14293835879041466368, 12349284717857274280, 126001801667], + [12182236992037191680, 18209607903013119355, 195669456065], + [11529215046068469760, 7891549145984268038, 193987144822], + [0, 7703609897518594624, 118427801736], + [0, 6336912652634587136, 136417613529], + [0, 4461621834659397632, 217343524723], + [0, 5484660635557953536, 115241865004], + [0, 15142619273265938432, 44297324048], + [0, 12170977992968765440, 16820883035], + [0, 1152921504606846976, 91659790039], + [0, 0, 215062500000], + [0, 0, 160000000000], + [14000097438505379162, 514755, 0], + [16451062686452429925, 514755758946802, 0], + [4009347599785716645, 17812314011563521031, 27904], + [16544162347546196456, 7684138864490314336, 965607477], + [17674258299745817187, 9740522787420029605, 53416558002], + [5215238411201214416, 6701109407732989894, 178528034798], + [14253990228345322571, 16534886227502443952, 238363267868], + [11214836553940194590, 8908667306968317910, 28896357978], + [14620711348380590101, 7531472173477105155, 90482939822], + [6638710787931587427, 11527371604834801166, 174408281924], + [17320000343692853250, 15688593496691078576, 68624900066], + [75147386268843646, 11394944804253312188, 226850480357], + [17938801582125480173, 11182279880854372627, 229617721195], + [6573358613626446145, 150579373068361470, 107606192607], + [8688505427903736481, 3147220002440857300, 223008162924], + [539870168696556032, 3630514817795505815, 108170611138], + [9002861336394465280, 11708796588334233588, 194196810602], + [17989846818158018560, 16844495466426369546, 106634735134], + [2700938287723315200, 17636655472325475902, 30913141928], + [17800090499088908288, 17038926655686645229, 168956085008], + [8809040871136690176, 15602838456783529415, 16923682064], + [9223372036854775808, 10869815869248876790, 16845831567], + [0, 18407124180939800832, 143589253898], + [0, 5705018517251293184, 10997852201], + [0, 9660452258743058432, 41309269673], + [0, 5646292272224927744, 169523694166], + [0, 7410409304047484928, 86306086117], + [0, 5953758707383795712, 229401719093], + [0, 4611686018427387904, 53322753906], + [0, 0, 114250000000], + [0, 0, 128000000000], + [15763656745260536568, 7, 0], + [8787336846248645550, 7854549544, 0], + [17584084447880694346, 7854549544476362484, 0], + [18041672551129683938, 15035424419724983, 425795984], + [14025886302294509785, 18280822466032836292, 144000815071], + [18430498103283160734, 11524250747302615283, 223991005371], + [3292348826238025833, 15212285943691810760, 187624730884], + [9211721212658275243, 7951804027551297019, 4824659673], + [10233245872666307519, 1706416229965221847, 217431068160], + [6200995035623311671, 3406023111930700826, 92505009], + [8480542380570450804, 16132696204133391302, 177184640882], + [11870363864499900506, 11593846688794356915, 114874555213], + [9301051379839581901, 6875759884161133906, 77628503688], + [11456694803569638005, 3593593325323835965, 136372735690], + [14327208890643983169, 9542049733257388925, 202194809084], + [4510081789599866365, 9926551925937787518, 252517275552], + [13255356976020303332, 3128491553219547895, 160538119458], + [9658806854127314944, 17158408656931354885, 34169595866], + [13708435528809971712, 2065169543154992616, 218930159197], + [1580190652103131136, 4832622393556232739, 93111953065], + [16557336970347413504, 16505930714733656162, 169261976984], + [12751520132434493440, 18270988073492888699, 152894788296], + [9295429630892703744, 2525111411519708523, 200990472248], + [0, 16728989342518570442, 56136886563], + [0, 7974052022039438336, 35906880329], + [0, 5356554962386550784, 73432274226], + [0, 6693869495028547584, 50290379426], + [0, 8157517147199766528, 162362875392], + [0, 12065776720423157760, 442219890], + [0, 11997589407315001344, 114654087066], + [0, 0, 154650390625], + [0, 0, 97000000000], + [16872870088062921306, 119850, 0], + [5112979788807802982, 119850914680120, 0], + [13742728082020150272, 2418433229320326037, 6497], + [2217110934613627976, 1143911773589293534, 97131103528], + [11021433940188610484, 9276183703610924928, 40062011581], + [1713669895470733463, 3532180128827684715, 189502862926], + [3313382510572018285, 8563997501322031543, 78191479868], + [14976595232784069505, 14843890409658460681, 60464255234], + [7213172372862496841, 9489417861634552678, 2804688911], + [15836474542502248588, 1113198223322322089, 15514422373], + [3221099285878340134, 11190777557146597869, 101060346596], + [3265814602578095358, 17764553645932638286, 228606653266], + [6502528252282225364, 14900777150991234852, 82963018382], + [16392476834556790183, 17364899863357893610, 142807772747], + [15167629413417091342, 15537570181590167037, 75941353107], + [1366763272626280111, 5558052627121307766, 147842293367], + [8720523635169216093, 12095241565795232609, 119301302636], + [9649171375767398672, 2187936505958366389, 108655684359], + [7647980704001073152, 12009203621325860228, 7118608275], + [13286434495608651776, 14814842834750302952, 147651020232], + [4358271637167013888, 5965296499605198833, 200803114239], + [15954987941890097152, 4051026394962148842, 255323379371], + [7911135695429697536, 16799526299141688349, 171219606580], + [7205759403792793600, 9460214166646215205, 52910704145], + [0, 10750736995029068008, 17512839237], + [0, 5377963045376430080, 69582798620], + [0, 15996910350253424640, 28291539960], + [0, 13651157529655246848, 248867194247], + [0, 9771305410219737088, 135740030732], + [0, 12709439623416250368, 12529703527], + [0, 9943947977234055168, 103688980102], + [0, 0, 134539062500], + [0, 0, 228000000000], + [952589339068938148, 1828779826, 0], + [10058155040190817688, 1828779826051639971, 0], + [5322725640026584391, 371564423966525229, 99138353], + [2269982385930389600, 14464859121514339583, 49020142547], + [13216683679976310224, 3913119023023056247, 211784141584], + [17417440642083494819, 5493396321716566945, 16212130607], + [3029180749090900711, 5837454566818211973, 47297797611], + [8315443826261908513, 2886670683193253881, 235316449046], + [1488962797247197277, 5504823105587173817, 22156486731], + [18009099634999034122, 9431834277334851106, 75298417058], + [8092455412807497971, 12921661346456247087, 162511300760], + [17808458047236758329, 3643076516404724246, 152700484665], + [3164591817527425171, 12559396953196866477, 57197491573], + [3314036340472350590, 1626880974916825698, 117680846273], + [33234902404332784, 6806994170946429566, 193088193394], + [12349284717857274280, 7596631230206896579, 114369007893], + [18209607903013119355, 3100480253729502401, 21411814204], + [7891549145984268038, 6310570748781063286, 60168077371], + [7703609897518594624, 14251867077375744136, 59342096725], + [6336912652634587136, 6701165793751570137, 85772595262], + [4461621834659397632, 10856833140463959923, 62363270925], + [5484660635557953536, 15867563727561248556, 13588550103], + [15142619273265938432, 5048961008671491600, 215860182353], + [12170977992968765440, 13278183119599849051, 81273704724], + [1152921504606846976, 4547591784941053655, 20719811749], + [0, 11815437715887182496, 165246525444], + [0, 398495392178782208, 4640516162], + [0, 9154841240825495552, 66021602478], + [0, 1902683298245640192, 174496284938], + [0, 5081900962138816512, 10103144668], + [0, 3234710432358858752, 220275490403], + [0, 16717361816799281152, 99175354003], + [0, 0, 147906250000], + [0, 0, 16000000000], + [17812314011563521031, 27904, 0], +]; diff --git a/src/pretty/to_fixed/mod.rs b/src/pretty/to_fixed/mod.rs new file mode 100644 index 0000000..0a75683 --- /dev/null +++ b/src/pretty/to_fixed/mod.rs @@ -0,0 +1,648 @@ +use crate::{ + d2s::{DOUBLE_BIAS, DOUBLE_EXPONENT_BITS, DOUBLE_MANTISSA_BITS}, + digit_table::DIGIT_TABLE, + pretty::{ + format64, + to_fixed::d2fixed_full_table::{ + ADDITIONAL_BITS_2, MIN_BLOCK_2, POW10_OFFSET, POW10_OFFSET_2, POW10_SPLIT, + POW10_SPLIT_2, + }, + }, +}; +#[cfg(feature = "no-panic")] +use no_panic::no_panic; + +mod d2fixed_full_table; + +/// Max bytes/characters required for `toFixed` representation of a [`f64`] value: +/// +/// - 1 byte for sign (-) +/// - `22` bytes for whole part: +/// Because we have a check for if `>= 1e21` (1 byte extra, just in case) +/// - `1` byte for dot (`.`) +/// - `108` (`9 * 12`) bytes for fraction part: +/// We write digits in blocks, which consist of `9` digits. +/// +/// Total: `1 + 22 + 1 + 108 = 132` +pub const MAX_BUFFER_SIZE: usize = 132; + +pub struct Cursor { + buffer: *mut u8, + len: isize, + index: isize, +} + +impl Cursor { + #[cfg_attr(feature = "no-panic", no_panic)] + pub fn new(buffer: *mut u8, len: usize) -> Self { + debug_assert!(!buffer.is_null()); + Self { + buffer, + len: len as isize, + index: 0, + } + } + + /// Append one byte to buffer. + /// + /// # Safety + /// + /// The caller must ensure that there is enough space for the given byte. + #[cfg_attr(feature = "no-panic", no_panic)] + unsafe fn append_byte(&mut self, c: u8) { + debug_assert!(self.index < self.len); + + *self.buffer.offset(self.index) = c; + self.index += 1; + } + + /// Append the byte `count` times into the buffer. + /// + /// # Safety + /// + /// The caller must ensure that there is enough space for the given bytes. + #[cfg_attr(feature = "no-panic", no_panic)] + unsafe fn append_bytes(&mut self, c: u8, count: usize) { + debug_assert!(self.index + count as isize <= self.len); + + self.buffer.offset(self.index).write_bytes(c, count); + self.index += count as isize; + } + + /// Gets the current [`Cursor`] index. + /// + /// The `index` is also the amount of bytes that have been written into the buffer. + #[cfg_attr(feature = "no-panic", no_panic)] + fn index(&self) -> usize { + self.index as usize + } + + /// Convert `digits` to decimal and write the last 9 decimal digits to result. + /// If `digits` contains additional digits, then those are silently ignored. + /// + /// # Safety + /// + /// The caller must ensure that the buffer has enough space for `9` bytes. + #[cfg_attr(feature = "no-panic", no_panic)] + unsafe fn append_nine_digits(&mut self, mut digits: u32) { + let count = 9; + + debug_assert!(self.index + count <= self.len); + + if digits == 0 { + self.append_bytes(b'0', 9); + return; + } + + let result = self.buffer.offset(self.index); + + for i in [0, 4] { + let c = digits % 10000; + digits /= 10000; + let c0 = (c % 100) << 1; + let c1 = (c / 100) << 1; + + // memcpy(result + 7 - i, DIGIT_TABLE + c0, 2); + // memcpy(result + 5 - i, DIGIT_TABLE + c1, 2); + result + .offset(7 - i as isize) + .copy_from_nonoverlapping(DIGIT_TABLE.as_ptr().offset(c0 as isize), 2); + result + .offset(5 - i as isize) + .copy_from_nonoverlapping(DIGIT_TABLE.as_ptr().offset(c1 as isize), 2); + } + *(result.offset(0)) = b'0' + digits as u8; + + self.index += count; + } + + /// Convert `digits` to a sequence of decimal digits. Append the digits to the result. + /// + /// # Safety + /// + /// The caller has to guarantee that: + /// + /// - 10^(olength-1) <= digits < 10^olength + /// e.g., by passing `olength` as `decimalLength9(digits)`. + /// + /// - That the buffer has enough space for the decimal length of the given integer. + #[cfg_attr(feature = "no-panic", no_panic)] + unsafe fn append_n_digits(&mut self, mut digits: u32) { + let olength = decimal_length9(digits); + + debug_assert!(self.index + olength as isize <= self.len); + + let result = self.buffer.offset(self.index); + + let mut i = 0; + while digits >= 10000 { + let c = digits % 10000; + + digits /= 10000; + let c0 = (c % 100) << 1; + let c1 = (c / 100) << 1; + + // memcpy(result + olength - i - 2, DIGIT_TABLE + c0, 2); + // memcpy(result + olength - i - 4, DIGIT_TABLE + c1, 2); + result + .offset(olength as isize - i as isize - 2) + .copy_from_nonoverlapping(DIGIT_TABLE.as_ptr().offset(c0 as isize), 2); + result + .offset(olength as isize - i as isize - 4) + .copy_from_nonoverlapping(DIGIT_TABLE.as_ptr().offset(c1 as isize), 2); + + i += 4; + } + if digits >= 100 { + let c = (digits % 100) << 1; + digits /= 100; + + // memcpy(result + olength - i - 2, DIGIT_TABLE + c, 2); + result + .offset(olength as isize - i as isize - 2) + .copy_from_nonoverlapping(DIGIT_TABLE.as_ptr().offset(c as isize), 2); + + i += 2; + } + if digits >= 10 { + let c = digits << 1; + + // memcpy(result + olength - i - 2, DIGIT_TABLE + c, 2); + result + .offset(olength as isize - i as isize - 2) + .copy_from_nonoverlapping(DIGIT_TABLE.as_ptr().offset(c as isize), 2); + } else { + *result = b'0' + digits as u8; + } + + self.index += olength as isize; + } + + /// Convert `digits` to decimal and write the last `count` decimal digits to result. + /// If `digits` contains additional digits, then those are silently ignored. + /// + /// # Safety + /// + /// The caller must ensure that the buffer has enough space for the given `count`. + #[cfg_attr(feature = "no-panic", no_panic)] + unsafe fn append_c_digits(&mut self, count: u32, mut digits: u32) { + debug_assert!(self.index + count as isize <= self.len); + + let result = self.buffer.offset(self.index); + + // Copy pairs of digits from DIGIT_TABLE. + let mut i: u32 = 0; + // for (; i < count - 1; i += 2) { + while i < count - 1 { + let c: u32 = (digits % 100) << 1; + digits /= 100; + + // memcpy(result + count - i - 2, DIGIT_TABLE + c, 2); + result + .offset((count - i - 2) as isize) + .copy_from_nonoverlapping(DIGIT_TABLE.as_ptr().offset(c as isize), 2); + + i += 2; + } + // Generate the last digit if count is odd. + if i < count { + let c = b'0' + (digits % 10) as u8; + + // result[count - i - 1] = c; + *result.offset((count - i - 1) as isize) = c; + } + + self.index += count as isize; + } + + /// Get the byte at the given index. + /// + /// # Safety + /// + /// The caller must ensure that the index is within `[0, len)`. + #[cfg_attr(feature = "no-panic", no_panic)] + unsafe fn get(&mut self, i: isize) -> u8 { + debug_assert!((0..self.len).contains(&i)); + + *self.buffer.offset(i) + } + + /// Set the byte at the given index with the value. + /// + /// # Safety + /// + /// The caller must ensure that the index is within `[0, len)`. + #[cfg_attr(feature = "no-panic", no_panic)] + unsafe fn set(&mut self, i: isize, c: u8) { + debug_assert!((0..self.len).contains(&i)); + + *self.buffer.offset(i) = c; + } +} + +/// Because of the abs(f) >= 1e21 check, falls back to ToString ([`format64`]). +/// +/// See tests. +const MAX_EXPONENT: u32 = 0b100_0100_0100; // 1029 +const MIN_EXPONENT: u16 = 0b010_1001_0011; + +/// `e2 = exponent - bias - |mantissa|` +const MAX_E2: i32 = MAX_EXPONENT as i32 - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS as i32; +const MIN_E2: i32 = MIN_EXPONENT as i32 - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS as i32; + +const MAX_POW10_SPLIT_2_INX: i32 = -MIN_E2 / 16; + +const POW10_ADDITIONAL_BITS: u32 = 120; + +/// Returns `floor(log_10(2^e))` requires `0 <= e <= 1650`. +#[cfg_attr(feature = "no-panic", no_panic)] +fn log10_pow2(e: i32) -> u32 { + // The first value this approximation fails for is 2^1651 which is just greater than 10^297. + debug_assert!((0..=1650).contains(&e)); + + ((e as u32) * 78913) >> 18 +} + +/// Get index from `e2` value. +/// +/// Range `[0, 2]` inclusive. +#[cfg_attr(feature = "no-panic", no_panic)] +fn index_for_exponent(e: u32) -> u32 { + debug_assert!((0..=MAX_E2 as u32).contains(&e)); + + let result = (e + 15) / 16; + + debug_assert!((0..=2).contains(&result)); + + result +} + +#[cfg_attr(feature = "no-panic", no_panic)] +fn pow10_bits_for_index(idx: u32) -> u32 { + 16 * idx + POW10_ADDITIONAL_BITS +} + +/// Get the length from the index. +/// +/// Range `[2, 3]` inclusive. +/// +// TODO: Because the ranges are so small we could have tables, too speed up execution. +#[cfg_attr(feature = "no-panic", no_panic)] +fn length_for_index(idx: u32) -> u32 { + // +1 for ceil, +16 for mantissa, +8 to round up when dividing by 9 + (log10_pow2(16 * idx as i32) + 1 + 16 + 8) / 9 +} + +#[cfg_attr(feature = "no-panic", no_panic)] +fn umul256(a: u128, b_hi: u64, b_lo: u64) -> (u128, u128) { + let a_lo = a as u64; + let a_hi = (a >> 64) as u64; + + let b00 = (a_lo as u128) * (b_lo as u128); + let b01 = (a_lo as u128) * (b_hi as u128); + let b10 = (a_hi as u128) * (b_lo as u128); + let b11 = (a_hi as u128) * (b_hi as u128); + + let b00_lo = b00 as u64; + let b00_hi = (b00 >> 64) as u64; + + let mid1 = b10 + b00_hi as u128; + let mid1_lo = (mid1) as u64; + let mid1_hi = (mid1 >> 64) as u64; + + let mid2 = b01 + mid1_lo as u128; + let mid2_lo = (mid2) as u64; + let mid2_hi = (mid2 >> 64) as u64; + + let p_hi = b11 + mid1_hi as u128 + mid2_hi as u128; + let p_lo = ((mid2_lo as u128) << 64) | b00_lo as u128; + + (p_hi, p_lo) +} + +// Returns the high 128 bits of the 256-bit product of a and b. +#[cfg_attr(feature = "no-panic", no_panic)] +fn umul256_hi(a: u128, b_hi: u64, b_lo: u64) -> u128 { + // Reuse the umul256 implementation. + // Optimizers will likely eliminate the instructions used to compute the + // low part of the product. + let (hi, _lo) = umul256(a, b_hi, b_lo); + hi +} + +// Unfortunately, gcc/clang do not automatically turn a 128-bit integer division +// into a multiplication, so we have to do it manually. +#[cfg_attr(feature = "no-panic", no_panic)] +fn uint128_mod1e9(v: u128) -> u32 { + // After multiplying, we're going to shift right by 29, then truncate to uint32_t. + // This means that we need only 29 + 32 = 61 bits, so we can truncate to uint64_t before shifting. + let multiplied = umul256_hi(v, 0x89705F4136B4A597, 0x31680A88F8953031) as u64; + + // For uint32_t truncation, see the mod1e9() comment in d2s_intrinsics.rs + let shifted = (multiplied >> 29) as u32; + + (v as u32).wrapping_sub(1000000000u32.wrapping_mul(shifted)) +} + +// Best case: use 128-bit type. +#[cfg_attr(feature = "no-panic", no_panic)] +fn mul_shift_mod1e9(m: u64, mul: &[u64; 3], j: i32) -> u32 { + let b0 = m as u128 * mul[0] as u128; // 0 + let b1 = m as u128 * mul[1] as u128; // 64 + let b2 = m as u128 * mul[2] as u128; // 128 + + debug_assert!((128..=180).contains(&j)); + + let mid = b1 + ((b0 >> 64) as u64) as u128; // 64 + let s1 = b2 + ((mid >> 64) as u64) as u128; // 128 + uint128_mod1e9(s1 >> (j - 128)) +} + +// Returns the number of decimal digits in v, which must not contain more than 9 digits. +#[cfg_attr(feature = "no-panic", no_panic)] +fn decimal_length9(v: u32) -> u32 { + // Function precondition: v is not a 10-digit number. + // (f2s: 9 digits are sufficient for round-tripping.) + // (d2fixed: We print 9-digit blocks.) + debug_assert!(v < 1000000000); + + if v >= 100000000 { + 9 + } else if v >= 10000000 { + 8 + } else if v >= 1000000 { + 7 + } else if v >= 100000 { + 6 + } else if v >= 10000 { + 5 + } else if v >= 1000 { + 4 + } else if v >= 100 { + 3 + } else if v >= 10 { + 2 + } else { + 1 + } +} + +/// Print [`f64`] to the given buffer using fixed notation, +/// as defined in the ECMAScript `Number.prototype.toFixed()` method +/// and return number of bytes written. +/// +/// At most 132 bytes will be written. +/// +/// ## Special cases +/// +/// This function **does not** check for NaN or infinity. If the input +/// number is not a finite float, the printed representation will be some +/// correctly formatted but unspecified numerical value. +/// +/// Please check [`is_finite`] yourself before calling this function, or +/// check [`is_nan`] and [`is_infinite`] and handle those cases yourself. +/// +/// [`is_finite`]: https://doc.rust-lang.org/std/primitive.f64.html#method.is_finite +/// [`is_nan`]: https://doc.rust-lang.org/std/primitive.f64.html#method.is_nan +/// [`is_infinite`]: https://doc.rust-lang.org/std/primitive.f64.html#method.is_infinite +/// +/// ## Safety +/// +/// The `result` pointer argument must point to sufficiently many writable bytes +/// to hold Ryū's representation of `f`. +/// +/// ## Example +/// +/// ``` +/// use std::{mem::MaybeUninit, slice, str}; +/// +/// let f = 1.235f64; +/// +/// unsafe { +/// let mut buffer = [MaybeUninit::::uninit(); 132]; +/// let len = ryu_js::raw::format64_to_fixed(f, 2, buffer.as_mut_ptr() as *mut u8); +/// let slice = slice::from_raw_parts(buffer.as_ptr() as *const u8, len); +/// let print = str::from_utf8_unchecked(slice); +/// assert_eq!(print, "1.24"); +/// } +/// ``` +#[must_use] +#[cfg_attr(feature = "no-panic", no_panic)] +pub unsafe fn format64_to_fixed(f: f64, fraction_digits: u8, result: *mut u8) -> usize { + // SKIPPED: 1. Let x be ? thisNumberValue(this value). + // SKIPPED: 2. Let f be ? ToIntegerOrInfinity(fractionDigits). + // SKIPPED: 3. Assert: If fractionDigits is undefined, then f is 0. + // SKIPPED: 4. If f is not finite, throw a RangeError exception. + // 5. If f < 0 or f > 100, throw a RangeError exception. + debug_assert!((0..=100).contains(&fraction_digits)); + + // 10. If x ≥ 10^21, then + let f_abs = if f < 0.0 { -f } else { f }; + if f_abs >= 1e21 { + // a. Let m be ! ToString(𝔽(x)). + return format64(f, result); + } + + let mut result = Cursor::new(result, MAX_BUFFER_SIZE); + + let bits = f.to_bits(); + let sign = ((bits >> (DOUBLE_MANTISSA_BITS + DOUBLE_EXPONENT_BITS)) & 1) != 0; + let ieee_mantissa = bits & ((1u64 << DOUBLE_MANTISSA_BITS) - 1); + let ieee_exponent = + (bits >> DOUBLE_MANTISSA_BITS) as u32 & ((1u32 << DOUBLE_EXPONENT_BITS) - 1); + + // Special case when it's 0 or -0 it's the same. + // + // Return and append '.' and '0's is needed. + // + // See: https://tc39.es/ecma262/#%E2%84%9D + if ieee_exponent == 0 && ieee_mantissa == 0 { + result.append_byte(b'0'); + if fraction_digits == 0 { + return result.index(); + } + result.append_byte(b'.'); + result.append_bytes(b'0', fraction_digits as usize); + return result.index(); + } + + debug_assert!((0..=MAX_EXPONENT).contains(&ieee_exponent)); + + if sign { + result.append_byte(b'-'); + } + + let (e2, m2) = if ieee_exponent == 0 { + (1 - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS as i32, ieee_mantissa) + } else { + ( + ieee_exponent as i32 - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS as i32, + (1 << DOUBLE_MANTISSA_BITS) | ieee_mantissa, + ) + }; + + debug_assert!((..=MAX_E2).contains(&e2)); + + let mut nonzero = false; + + // Write the whole part (integral part) of the floating point. + // + // xxxxxxx.1234567 (write xs) + if e2 >= -(DOUBLE_MANTISSA_BITS as i32) { + // 0 <= idx <= 2 + let idx = if e2 < 0 { + 0 + } else { + index_for_exponent(e2 as u32) + }; + let p10bits = pow10_bits_for_index(idx); + let len = length_for_index(idx) as i32; + + for i in (0..len).rev() { + let j = p10bits as i32 - e2; + // SAFETY: 0 <= idx <= 2, putting idx inside the index bounds of `POW10_OFFSET`. + let split_idx = *POW10_OFFSET.get_unchecked(idx as usize) as usize; + + // SAFETY: The max value inside `POW10_OFFSET` is 5, and the max value of `i` is 2, + // putting `split_idx + i` inside the index bounds of `POW10_SPLIT`. + let mul = POW10_SPLIT.get_unchecked(split_idx + i as usize); + + // Temporary: j is usually around 128, and by shifting a bit, we push it to 128 or above, which is + // a slightly faster code path in mulShift_mod1e9. Instead, we can just increase the multipliers. + let digits = mul_shift_mod1e9(m2 << 8, mul, j + 8); + if nonzero { + result.append_nine_digits(digits); + } else if digits != 0 { + result.append_n_digits(digits); + nonzero = true; + } + } + } + + // If the whole part is zero (nothing was writen), write a zero. + if !nonzero { + result.append_byte(b'0'); + } + + // If fraction_digits is not zero, then write the dot. + if fraction_digits != 0 { + result.append_byte(b'.'); + } + + // Check if it has fractional part. + if e2 >= 0 { + result.append_bytes(b'0', fraction_digits as usize); + return result.index(); + } + + // Write fractional part. + // + // 1234567.yyyyyyy (write ys) + + let fraction_digits = fraction_digits as u32; + + let idx = (-e2 / 16).min(MAX_POW10_SPLIT_2_INX) as usize; + + let min_block = MIN_BLOCK_2[idx]; + + // fraction_digits is defined to be [0, 100] inclusive. + // + // Therefore blocks can be [1, 12] inclusive. + let blocks: u32 = fraction_digits / 9 + 1; + if blocks <= min_block as u32 { + result.append_bytes(b'0', fraction_digits as usize); + return result.index(); + } + + debug_assert!(idx <= 25); + + let mut round_up = false; + + for i in 0..blocks { + let p: isize = POW10_OFFSET_2[idx] as isize + i as isize - min_block as isize; + debug_assert!(p >= 0); + let p = p as usize; + + // SAFETY: `idx` <= 26 per the min operation above. If `idx == 26` (which is the last index + // of `POW10_OFFSET_2`), blocks <= min_block will always be true, since `1 <= blocks <= 12` + // and `MIN_BLOCK_2[26]` = 12. Hence, for that value of `idx` this won't be executed. + // Finally, for `idx <= 25` it is always true that `idx + 1 <= 26`, making this access always + // in bounds for `POW10_OFFSET_2`. + if p >= *POW10_OFFSET_2.get_unchecked(idx + 1) as usize { + // If the remaining digits are all 0, then we might as well use memset. + // No rounding required in this case. + let fill = fraction_digits as usize - 9 * i as usize; + // memset(result + index, '0', fill); + result.append_bytes(b'0', fill); + break; + } + + debug_assert!(p <= 480); + + // Temporary: j is usually around 128, and by shifting a bit, we push it to 128 or above, which is + // a slightly faster code path in mulShift_mod1e9. Instead, we can just increase the multipliers. + let j: isize = ADDITIONAL_BITS_2 as isize + (-(e2 as isize) - 16 * idx as isize); + + // SAFETY: Since `idx <= 25`, the maximum value of `POW10_OFFSET_2[idx]` must be `480` for + // `idx == 25`. + // However, this also means that `min_block == 11` for that value of `idx`. + // Hence, `POW10_OFFSET_2[25] - MIN_BLOCK_2[25] == 469`, and for that value of `blocks`, + // `0 <= 1 <= 10`. + // + // This shows that the maximum value of `p` is `480`, which is exactly the biggest valid + // index for `POW10_SPLIT_2`. + let mut digits: u32 = + mul_shift_mod1e9(m2 << 8, POW10_SPLIT_2.get_unchecked(p), j as i32 + 8); + + if i < blocks - 1 { + result.append_nine_digits(digits); + } else { + let maximum: u32 = fraction_digits - 9 * i; + let mut last_digit: u32 = 0; + for _k in 0..(9 - maximum) { + last_digit = digits % 10; + digits /= 10; + } + + // If last digit is 5 or above, round up. + round_up = last_digit >= 5; + + if maximum != 0 { + result.append_c_digits(maximum, digits); + } + break; + } + } + + // Roundup if needed. + if round_up { + let mut round_index = result.index; + let mut dot_index = 0; // '.' can't be located at index 0 + loop { + round_index -= 1; + + let c = result.get(round_index); + if round_index == -1 || c == b'-' { + result.set(round_index + 1, b'1'); + if dot_index > 0 { + result.set(dot_index, b'0'); + result.set(dot_index + 1, b'.'); + } + result.append_byte(b'0'); + break; + } + if c == b'.' { + dot_index = round_index; + continue; + } else if c == b'9' { + result.set(round_index, b'0'); + continue; + } + + result.set(round_index, c + 1); + break; + } + } + + result.index() +} diff --git a/tests/to_fixed.rs b/tests/to_fixed.rs new file mode 100644 index 0000000..c66367d --- /dev/null +++ b/tests/to_fixed.rs @@ -0,0 +1,381 @@ +#![allow( + clippy::approx_constant, + clippy::excessive_precision, + clippy::cast_lossless, + clippy::float_cmp, + clippy::int_plus_one, + clippy::non_ascii_literal, + clippy::unreadable_literal, + clippy::unseparated_literal_suffix +)] + +fn pretty_to_fixed(f: f64, exp: u8) -> String { + ryu_js::Buffer::new().format_to_fixed(f, exp).to_owned() +} + +fn pretty_to_string(f: f64) -> String { + ryu_js::Buffer::new().format(f).to_owned() +} + +#[test] +fn range_over_100() { + let expected = "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + assert_eq!(pretty_to_fixed(0.0, 101), expected); +} + +#[test] +fn nan() { + for fraction_digits in 0..=100u8 { + assert_eq!(pretty_to_fixed(f64::NAN, fraction_digits), "NaN"); + } +} + +#[test] +fn infinity() { + for fraction_digits in 0..=100u8 { + assert_eq!(pretty_to_fixed(f64::INFINITY, fraction_digits), "Infinity"); + } + for fraction_digits in 0..=100u8 { + assert_eq!( + pretty_to_fixed(f64::NEG_INFINITY, fraction_digits), + "-Infinity" + ); + } +} + +#[test] +fn positive_zero() { + assert_eq!(pretty_to_fixed(0.0, 0), "0"); + for fraction_digits in 1..=100u8 { + let expected = "0".repeat(usize::from(fraction_digits)); + assert_eq!( + pretty_to_fixed(0.0, fraction_digits), + format!("0.{expected}") + ); + } +} + +#[test] +fn negative_zero() { + assert_eq!(pretty_to_fixed(-0.0, 0), "0"); + for fraction_digits in 1..=100u8 { + let expected = "0".repeat(usize::from(fraction_digits)); + assert_eq!( + pretty_to_fixed(-0.0, fraction_digits), + format!("0.{expected}") + ); + } +} + +const WHOLE_NUMBERS: &[f64] = &[ + 1.0, + 10.0, + 100.0, + 123.0, + 1234567890.0, + i32::MAX as f64, + 12_345_678_910_111_213.0, + 9_007_199_254_740_992.0, +]; + +#[track_caller] +fn check_whole_number(test_case: usize, number: f64) { + for fraction_digits in 0..=100u8 { + let mut fraction = "0".repeat(usize::from(fraction_digits)); + if fraction_digits != 0 { + fraction = format!(".{fraction}"); + } + let expected = format!("{number}{fraction}"); + + assert_eq!( + pretty_to_fixed(number, fraction_digits), + expected, + "Test case {test_case}. expected {number} with fraction_digits {fraction_digits} to equal {expected}" + ); + } +} + +#[test] +fn test_positive_whole_numbers() { + for (test_case, number) in WHOLE_NUMBERS.iter().copied().enumerate() { + check_whole_number(test_case, number); + } +} + +#[test] +fn test_negative_whole_numbers() { + for (test_case, number) in WHOLE_NUMBERS.iter().copied().map(|x| -x).enumerate() { + check_whole_number(test_case, number); + } +} + +// https://github.com/boa-dev/boa/issues/2609 +#[test] +fn boa_issue_2609() { + assert_eq!(pretty_to_fixed(1.25, 1), "1.3"); + assert_eq!(pretty_to_fixed(1.35, 1), "1.4"); +} + +#[test] +fn test262() { + // test262 commit: be0abd93cd799a758714b5707fa87c9048fc38ce + + // test/built-ins/Number/prototype/toFixed/S15.7.4.5_A1.1_T02.js + assert_eq!(pretty_to_fixed(1.0, 0), "1"); + assert_eq!(pretty_to_fixed(1.0, 1), "1.0"); + + // test/built-ins/Number/prototype/toFixed/S15.7.4.5_A1.4_T01.js + assert_eq!(pretty_to_fixed(1e21, 1), pretty_to_string(1e21)); + + // test/built-ins/Number/prototype/toFixed/exactness.js + assert_eq!( + pretty_to_fixed(1000000000000000128.0, 0), + "1000000000000000128" + ); +} + +#[test] +fn rounding() { + assert_eq!(pretty_to_fixed(1.5, 0), "2"); + assert_eq!(pretty_to_fixed(2.9, 0), "3"); + + assert_eq!(pretty_to_fixed(2.55, 1), "2.5"); + assert_eq!(pretty_to_fixed(2.449999999999999999, 1), "2.5"); + + assert_eq!(pretty_to_fixed(1010.954526123, 9), "1010.954526123"); + assert_eq!(pretty_to_fixed(1010.954526123, 8), "1010.95452612"); + assert_eq!(pretty_to_fixed(1010.954526123, 7), "1010.9545261"); + assert_eq!(pretty_to_fixed(1010.954526123, 6), "1010.954526"); + assert_eq!(pretty_to_fixed(1010.954526123, 5), "1010.95453"); + assert_eq!(pretty_to_fixed(1010.954526123, 4), "1010.9545"); + assert_eq!(pretty_to_fixed(1010.954526123, 3), "1010.955"); + assert_eq!(pretty_to_fixed(1010.954526123, 2), "1010.95"); + assert_eq!(pretty_to_fixed(1010.954526123, 1), "1011.0"); + assert_eq!(pretty_to_fixed(1010.954526123, 0), "1011"); +} + +#[test] +fn test_to_fixed_fraction_digits_50() { + assert_eq!( + pretty_to_fixed(0.3, 50), + "0.29999999999999998889776975374843459576368331909180" + ); +} + +#[test] +fn test_to_fixed_fraction_digits_100() { + assert_eq!(pretty_to_fixed(1.0, 100), "1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + assert_eq!(pretty_to_fixed(1.256, 100), "1.2560000000000000053290705182007513940334320068359375000000000000000000000000000000000000000000000000"); + assert_eq!(pretty_to_fixed(1.12345678910111213, 100), "1.1234567891011122409139488809159956872463226318359375000000000000000000000000000000000000000000000000"); +} + +#[test] +#[rustfmt::skip] +fn test_exponential_notation() { + assert_eq!(pretty_to_fixed(1.23, 2), "1.23"); + assert_eq!(pretty_to_fixed(1.23e0, 2), "1.23"); + assert_eq!(pretty_to_fixed(1.23e1, 2), "12.30"); + assert_eq!(pretty_to_fixed(1.23e2, 2), "123.00"); + assert_eq!(pretty_to_fixed(1.23e3, 2), "1230.00"); + assert_eq!(pretty_to_fixed(1.23e4, 2), "12300.00"); + assert_eq!(pretty_to_fixed(1.23e5, 2), "123000.00"); + assert_eq!(pretty_to_fixed(1.23e6, 2), "1230000.00"); + assert_eq!(pretty_to_fixed(1.23e7, 2), "12300000.00"); + assert_eq!(pretty_to_fixed(1.23e8, 2), "123000000.00"); + assert_eq!(pretty_to_fixed(1.23e9, 2), "1230000000.00"); + assert_eq!(pretty_to_fixed(1.23e10, 2), "12300000000.00"); + assert_eq!(pretty_to_fixed(1.23e11, 2), "123000000000.00"); + assert_eq!(pretty_to_fixed(1.23e12, 2), "1230000000000.00"); + assert_eq!(pretty_to_fixed(1.23e13, 2), "12300000000000.00"); + assert_eq!(pretty_to_fixed(1.23e14, 2), "123000000000000.00"); + assert_eq!(pretty_to_fixed(1.23e15, 2), "1230000000000000.00"); + assert_eq!(pretty_to_fixed(1.23e16, 2), "12300000000000000.00"); + assert_eq!(pretty_to_fixed(1.23e17, 2), "123000000000000000.00"); + assert_eq!(pretty_to_fixed(1.23e18, 2), "1230000000000000000.00"); + assert_eq!(pretty_to_fixed(1.23e19, 2), "12300000000000000000.00"); + assert_eq!(pretty_to_fixed(1.23e20, 2), "123000000000000000000.00"); + + // fallback to exponential notation + assert_eq!(pretty_to_fixed(1.23e21, 2), "1.23e+21"); + assert_eq!(pretty_to_fixed(1.23e22, 2), "1.23e+22"); + assert_eq!(pretty_to_fixed(1.23e23, 2), "1.23e+23"); + assert_eq!(pretty_to_fixed(1.23e24, 2), "1.23e+24"); + assert_eq!(pretty_to_fixed(1.23e25, 2), "1.23e+25"); +} + +const DOUBLE_MANTISSA_BITS: u8 = 52; +const DOUBLE_BIAS: i32 = 1023; + +fn f64_and_e2_from_parts(sign: bool, exponent: u16, mantissa: u64) -> (f64, i32) { + assert!(exponent <= 0b111_1111_1111, "Invalid f64 exponent"); + + let mut bits: u64 = 0; + + bits |= mantissa; + bits |= (u64::from(exponent)) << 52; + bits |= u64::from(sign) << (52 + 11); + + let e2 = if exponent == 0 { + 1 - DOUBLE_BIAS - i32::from(DOUBLE_MANTISSA_BITS) + } else { + i32::from(exponent) - DOUBLE_BIAS - i32::from(DOUBLE_MANTISSA_BITS) + }; + + (f64::from_bits(bits), e2) +} + +fn f64_from_parts(sign: bool, exponent: u16, mantissa: u64) -> f64 { + f64_and_e2_from_parts(sign, exponent, mantissa).0 +} + +#[test] +fn test_f64_from_parts() { + assert_eq!(pretty_to_fixed(f64_from_parts(false, 0, 0), 2), "0.00"); + assert_eq!(pretty_to_fixed(f64_from_parts(true, 0, 0), 2), "0.00"); +} + +#[test] +fn test_max_exponent_boundry_zero_mantissa() { + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0000_0000, 0), 2), + "2.00" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0011_1111, 0), 2), + "18446744073709551616.00" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0100_0000, 0), 2), + "36893488147419103232.00" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0100_0001, 0), 2), + "73786976294838206464.00" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0100_0010, 0), 2), + "147573952589676412928.00" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0100_0011, 0), 2), + "295147905179352825856.00" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0100_0100, 0), 2), + "590295810358705651712.00" + ); + + // ToString fallback + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0100_0101, 0), 2), + "1.1805916207174113e+21" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0100_0110, 0), 2), + "2.3611832414348226e+21" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0100_0111, 0), 2), + "4.722366482869645e+21" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0111_1111, 0), 2), + "3.402823669209385e+38" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_1111_1111, 0), 2), + "1.157920892373162e+77" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b101_1111_1111, 0), 2), + "1.3407807929942597e+154" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b111_1111_1111, 0), 2), + "Infinity" + ); +} + +#[test] +fn test_max_exponent_boundry_and_full_mantissa() { + let m = !(u64::MAX << u64::from(DOUBLE_MANTISSA_BITS)); + + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0000_0000, m), 2), + "4.00" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0011_1111, m), 2), + "36893488147419099136.00" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0100_0000, m), 2), + "73786976294838198272.00" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0100_0001, m), 2), + "147573952589676396544.00" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0100_0010, m), 2), + "295147905179352793088.00" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0100_0011, m), 2), + "590295810358705586176.00" + ); + + // ToString fallback + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0100_0100, m), 2), + "1.1805916207174112e+21" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b100_0100_0101, m), 2), + "2.3611832414348223e+21" + ); + assert_eq!( + pretty_to_fixed(f64_from_parts(false, 0b111_1111_1111, m), 2), + "NaN" + ); +} + +const MIN_EXPONENT: u16 = 0b010_1001_0011; + +#[test] +fn test_min_exponent_boundry_zero_mantissa() { + let m = 0; + + let expected = "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + + for exponent in 0..=MIN_EXPONENT { + assert_eq!( + pretty_to_fixed(f64_from_parts(false, exponent, m), 100), + expected + ); + } + + assert_ne!( + pretty_to_fixed(f64_from_parts(false, MIN_EXPONENT + 1, m), 100), + expected + ); +} + +#[test] +fn test_min_exponent_boundry_full_mantissa() { + let m = !(u64::MAX << u64::from(DOUBLE_MANTISSA_BITS)); + + let expected = "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + + for exponent in 0..=MIN_EXPONENT { + assert_eq!( + pretty_to_fixed(f64_from_parts(false, exponent, m), 100), + expected + ); + } + + assert_ne!( + pretty_to_fixed(f64_from_parts(false, MIN_EXPONENT + 1, m), 100), + expected + ); +}