Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/unreleased/1709.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- nanvm: bigint string coercion now produces JavaScript-compatible decimal text
9 changes: 0 additions & 9 deletions fjs/nanvm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,3 @@ form (engine-specific source text). `nanvm-lib` has no object methods yet.
formatting, multi-limb bigint arithmetic, serialization round-trips, and the
exact text of `nanvm-lib`'s own error messages. These are properties of the VM,
not of JavaScript.

## Known divergence

`String(123n)` is `"123"` in JavaScript and `"0x7Bn"` in `nanvm-lib` — see
[bigint-decimal-string-coercion](../../nanvm-lib/todo/bigint-decimal-string-coercion.md).
The two affected cases carry a `rust` reason, so the gap is recorded in the data
itself rather than in a coverage table. That is the point of the arrangement: a
divergence is a property of a case, and a table of them goes stale the moment
someone fixes one.
7 changes: 2 additions & 5 deletions fjs/nanvm/module.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -344,9 +344,6 @@ const mulCases = [
{ name: 'numberByBigint', args: [1, 1n], expected: throws },
]

const hexadecimalBigint =
'nanvm-lib prints bigints in hexadecimal; see nanvm-lib/todo/bigint-decimal-string-coercion.md'

/**
* `String(x)`.
*
Expand All @@ -369,8 +366,8 @@ const stringCoercionCases = [
{ name: 'null', args: [null], expected: 'null' },
{ name: 'undefined', args: [undefined], expected: 'undefined' },
{ name: 'string', args: ['already'], expected: 'already' },
{ name: 'bigint', args: [123n], expected: '123', rust: hexadecimalBigint },
{ name: 'negativeBigint', args: [-456n], expected: '-456', rust: hexadecimalBigint },
{ name: 'bigint', args: [123n], expected: '123' },
{ name: 'negativeBigint', args: [-456n], expected: '-456' },
Comment thread
sasha-gil marked this conversation as resolved.
{ name: 'emptyArray', args: [[]], expected: '' },
{ name: 'singletonArray', args: [[1]], expected: '1' },
{ name: 'array', args: [[1, 2, 3]], expected: '1,2,3' },
Expand Down
41 changes: 41 additions & 0 deletions nanvm-lib/src/vm/bigint/display.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use crate::{
common::sized_index::SizedIndex,
sign::Sign,
vm::{BigInt, IContainer, IVm},
};
use core::fmt::{Display, Formatter, Result, Write};

const DECIMAL_BASE: u64 = 10_000_000_000_000_000_000;

impl<A: IVm> Display for BigInt<A> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
if self.is_zero() {
return f.write_char('0');
}
if self.sign() == Sign::Negative {
f.write_char('-')?;
}

let items = self.0.items();
let mut words: Vec<u64> = (0..items.length()).map(|i| items[i]).collect();
let mut groups = Vec::new();
while !words.is_empty() {
let mut remainder = 0u128;
for word in words.iter_mut().rev() {
let dividend = (remainder << 64) | *word as u128;
*word = (dividend / DECIMAL_BASE as u128) as u64;
remainder = dividend % DECIMAL_BASE as u128;
}
groups.push(remainder as u64);
while words.last() == Some(&0) {
words.pop();
}
}

write!(f, "{}", groups.pop().unwrap())?;
for group in groups.iter().rev() {
write!(f, "{group:019}")?;
}
Ok(())
}
}
1 change: 1 addition & 0 deletions nanvm-lib/src/vm/bigint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod add;
mod cmp;
mod debug;
mod default;
mod display;
mod from;
mod index;
mod mul;
Expand Down
3 changes: 1 addition & 2 deletions nanvm-lib/src/vm/string_coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ impl<A: IVm> Dispatch<A> for StringCoercion {
}

fn bigint(self, v: BigInt<A>) -> Self::Result {
// TODO: we should use different algorithm for large numbers.
to_result(&format!("{v:?}"))
to_result(&v.to_string())
Comment thread
sasha-gil marked this conversation as resolved.
}

fn object(self, v: Object<A>) -> Self::Result {
Expand Down
6 changes: 2 additions & 4 deletions nanvm-lib/tests/test/generated.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions nanvm-lib/tests/test/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,28 @@ fn bigint_debug_format<A: IVm>() {
}
}

/// Decimal display across limb and decimal-group boundaries.
fn bigint_display_format<A: IVm>() {
let zero: BigInt<A> = 0u64.into();
assert_eq!(zero.to_string(), "0");

let two_to_64 = BigInt::<A>::normalize_new(Sign::Positive, [0, 1]);
assert_eq!(two_to_64.to_string(), "18446744073709551616");

let decimal_group_boundary =
BigInt::<A>::normalize_new(Sign::Positive, [10_000_000_000_000_000_000]);
assert_eq!(decimal_group_boundary.to_string(), "10000000000000000000");

let max_u128 = BigInt::<A>::normalize_new(Sign::Positive, [u64::MAX, u64::MAX]);
assert_eq!(
max_u128.to_string(),
"340282366920938463463374607431768211455"
);

let negative = BigInt::<A>::normalize_new(Sign::Negative, [0, 1]);
assert_eq!(negative.to_string(), "-18446744073709551616");
}

fn format_fn<A: IVm>() {
let f = Function::<A>(A::InternalFunction::new_ok(
("myfunc".into(), 2),
Expand Down Expand Up @@ -185,6 +207,7 @@ fn gen_test<A: IVm>() {
conversions::<A>();
debug_format::<A>();
bigint_debug_format::<A>();
bigint_display_format::<A>();
unary_plus_bigint_message::<A>();
bigint_add::<A>();
bigint_mul::<A>();
Expand Down
54 changes: 0 additions & 54 deletions nanvm-lib/todo/bigint-decimal-string-coercion.md

This file was deleted.