Repository navigation
feat(ts): translate f32 as f64 - #296
Conversation
Quest-Claim: 82ac1b971f0533f4
An f32 is a number a single holds exactly, each arithmetic result rounded with Math.fround. Casts to and from f64 and integers round once, formatting prints the shortest digits that read back as the single, and parsing rounds once to the nearest single. Duration's f32 methods and bytes' get_f32/put_f32 map. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # ts/fixtures/lang/caller.ts # ts/fixtures/lang/expected/harness.ts # ts/fixtures/lang/src/harness.rs # ts/fixtures/reject/census.md
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 44 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
# Conflicts: # ts/fixtures/lang/expected/harness.ts # ts/fixtures/lang/src/harness.rs
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 2187f10
No actionable bugs found in the f32 lowering/runtime changes. The shared Float dispatch and format-parameterized parsing keep the implementation coherent; explicit single rounding, round-to-odd wide-integer conversion, and separate shortest-single formatting address the important differences from f64 without duplicating the entire runtime. The checked-in differential cases cover arithmetic, casts, formatting/parsing boundaries, NaN layout, and Duration integration.
Verification: static review of the changes and relevant runtime code only. I did not run tests, builds, the reported exhaustive sweep, or performance measurements; the PR's reported results were not independently verified.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
rust2-ts refused every
f32: the type, its constants, arithmetic, casts, methods, formatting, parsing, andDuration'sf32methods. Completes quest/m3/ts-f32.md.Approach
Reuses #165 and #260's design: a JS number, with the runtime holding std's algorithms where JS differs.
f32is a number a single holds exactly, typedrt.f32.float.rsnow has aFloatkind (F32/F64) every call site dispatches on.+ - * /becomeMath.fround(l op r). A double holds 53 bits, at least 2 * 24 + 2, so rounding the double result again gives the single nearest the exact value (the same holds forsqrt).%(exact fmod), comparisons, and negation need no rounding.f32 as f64keeps the number;f64 as f32isMath.fround. An integer of at most 32 bits is exactly a double, thenMath.froundrounds it once. 64- and 128-bit integers go through newrt.u64ToF32etc.: round to odd at 53 bits, thenMath.fround, so the rounding happens once (2^60 + 2^36 + 1gives2^60 + 2^37, where the nearest double would give2^60). Float to integer reusesfromF64on the exact double.div_euclid,rem_euclid,powi(__powisf2, rounding each product to a single), andatanhwork in singles;midpointworks in doubles, as std does on wasm32;to_degreesandto_radiansmultiply by std'sf32constants; libm's functions areMath.fround(Math.f(x)). Where anf64method gives the same single (round,signum,max,total_cmp, ...),f32Xaliases it.mul_addis still refused with a span, as forf64.f32NaN is the double NaN with the single's sign and payload in its top bits.f32::from_bitsandto_bitsbuild and read that layout by hand, becausegetFloat32would quiet a signaling NaN. As withf64, V8 still quiets a signaling NaN stored in an array.shortest32follows Dragon: at the first length where the truncated digits or the next digits up read back as the single, take the one that does; if both do, take the closer, or the greater on a tie. Precision formatting reuses the exact expansion, which is the same for the single and its double.Debugswitches to an exponent at1e-4f32and1e16f32, as core compares inf32.nearestandquotienttake aBinaryformat.f32FromStruses Clinger's fast path for up to 7 digits and|exp| <= 10(a double result of two singles, rounded once). Otherwise it divides exactly withBigIntand builds the single's bits directly, so it never rounds through a double.as_secs_f32anddiv_duration_f32round each step to a single, as std does.try_from_secs_f32andfrom_secs_f32sharef64's runtime: std's integer algorithm rounds the exact value to the nearest nanosecond either way, so the results are identical.mul_f32anddiv_f32aremul_f64anddiv_f64in std too.bytes'get_f32*/put_f32*map as theirf64counterparts do.f16. The feature goes on the existing#![feature]line, so no other span shifts.Tests:
singles(native wasm32 vs translated), withsingles::singleskinds 0 to 9,singles::formats, andsingles::parses. Named operands: each side of 2^23, 2^24, and each integer bound;f32::MAX; normal/subnormal edges; theDebugswitch points; a signaling NaN; a payload NaN.tests/nan.tschecks thef32NaN layout.Math.froundfrom arithmetic fails the snapshot. Parsingf32throughf64gives 106 differential differences, and taking the lesser on a shortest-digits tie gives 195.{:e}, and parsing the{:e}of the double halfway above each, against native Rust. 0 differences.Census (
just ts censusat the pin, origin/main vs this branch): moq-net 1294 → 1279 refusals, with nothing added. The 15 removed are the 12 inbytes'get_f32*and 3f32types inrs/moq-net/src/transport/boxed.rs. Model 387 and kio 132 are unchanged. No benchmark: moq's source does nof32arithmetic, and neither the codec fixture nor the task fixture touchesf32.Impact
f32translates asrt.f32, where every use was refused before, so nothing breaks.f32type,f32*methods,f32Display/Debug/LowerExp/UpperExp,f32FromStr,f32Clamp,f32Add/f32Mul,f32PartialCmp,u64ToF32/i64ToF32/u128ToF32/i128ToF32, anddurationAsSecsF32,durationDivDurationF32,durationTryFromSecsF32,durationFromSecsF32,durationMulF32,durationDivF32. Existingf64exports are unchanged.Alternatives
flt2dec/dec2fltMIR: blocked for the same reasons as feat(ts): format and parse f64 as std does #260 (unsafecode,&mutwrites, a bignum the engine already has asBigInt).toPrecisionplus a readback: still needs an exact single parse, and at powers of two the closest candidate can fail while the farther one reads back. The exact expansion is simpler and always correct.f64FromStrthenMath.fround: rounds twice (the mutation above).Float32ArrayorDataViewper value: no benefit over a number, andgetFloat32quiets signaling NaNs.Choices made without asking
mul_addstays refused forf32, matchingf64. A single's FMA could be done exactly (TwoSum plus round to odd), but that is new scope for both types.try_from_secs_f32,from_secs_f32,mul_f32, anddiv_f32map to thef64runtime functions, not separate ports. The reasoning is above, and the differential covers them.f16, which stays refused, so the fixture keeps its "unsupported type" and census-over-generics cases.Follow-ups
to_ne_bytes(andfrom_ne_bytes) has no mapping, thoughto_le_bytesdoes. I found this while writing the fixture, which usesto_le_bytesinstead. No quest covers it yet; it's left for/quest-plan.mul_addforf32andf64(see the choices above).Review
Codex (
quest review), gpt-6.1-sol, medium effort. Round 2 reviewed head2187f1045c546ec0e49ebd26dcc5574769adc7faand reported no findings.691c2dc1) had one finding, refuted: Reverse-wrapped enum keys losing custom equality atts/src/lower/collections.rs:544. This branch doesn't touch that code. origin/main had moved past the branch point (feat(ts): translate accessors of elements whose None is rt.None #292, fix(ts): read no scrutinee for a switch whose other variants can't exist #293, chore(quest): plan unit closures as values and std's unmapped accessors #298), so round 1's diff showed main's change as a removal. Head2187f104merges origin/main, and this branch's only change incollections.rsis the one-linepartialCmpdispatch.2187f104): no findings.just cipassed locally on2187f104insidenix develop. The merge of origin/main (9169909e) conflicted only in the lang harness case lists and generated files. I kept main's cases first, then this PR's, and regenerated the snapshots.Merge of main at
cecbf0d7(#295) into2187f104, as54af929c: ordered main'skeys::lent_*cases beforesingles::*in the case list and regeneratedharness.ts(the new cases only). Recorded skip, too trivial to review;just cipassed on54af929c.🤖 Generated with Claude Code
(written by Claude Opus 5.5)