diff --git a/src/client/camera.rs b/src/client/camera.rs index b7ad179..6a4a50f 100644 --- a/src/client/camera.rs +++ b/src/client/camera.rs @@ -3,10 +3,11 @@ use std::ops::AddAssign; use crate::util::FixedDec; -#[derive(Clone, Copy)] +#[derive(Clone)] pub struct Zoom { - scale: f32, - mult: f32, + exp: f32, + level: i32, + mult: FixedDec, } #[derive(Clone)] @@ -30,30 +31,43 @@ impl Default for Camera { fn default() -> Self { Self { pos: Vector2::zeros(), - zoom: Zoom::new(0.0), + zoom: Zoom::new(0, 0.0), } } } impl Zoom { - pub fn new(scale: f32) -> Self { + pub fn new(level: i32, scale: f32) -> Self { Self { - scale, - mult: mult(scale), + exp: scale, + level, + mult: mult(level, scale), } } - pub fn mult(&self) -> f32 { - self.mult + pub fn mult(&self) -> &FixedDec { + &self.mult + } + pub fn level(&self) -> i32 { + self.level } } impl AddAssign for Zoom { + #[allow(clippy::suspicious_op_assign_impl)] fn add_assign(&mut self, rhs: f32) { - self.scale += rhs; - self.mult = mult(self.scale); + self.exp -= rhs; + while self.exp <= -0.5 { + self.exp += 1.0; + self.level += 1; + } + while self.exp > 0.5 { + self.exp -= 1.0; + self.level -= 1; + } + self.mult = mult(self.level, self.exp); } } -pub fn mult(scale: f32) -> f32 { - (-scale).exp2() +pub fn mult(level: i32, exp: f32) -> FixedDec { + (FixedDec::from(1) >> level) * FixedDec::from(exp.exp2()) } diff --git a/src/client/handle_input.rs b/src/client/handle_input.rs index 41bc93c..34ba234 100644 --- a/src/client/handle_input.rs +++ b/src/client/handle_input.rs @@ -12,10 +12,11 @@ impl Client<'_> { let per_sec = delta.as_secs_f32(); if input.scroll_delta != 0.0 { - // camera.zoom += input.scroll_delta / 5.0; + camera.zoom += input.scroll_delta / 5.0; } - let speed = FixedDec::from(per_sec * 0.5); + let speed = FixedDec::from(per_sec * 0.5) * camera.zoom.mult(); + let old = f32::from(&camera.pos.x); if input.pressed(K::KeyW) { camera.pos.y += &speed; } @@ -28,5 +29,9 @@ impl Client<'_> { if input.pressed(K::KeyD) { camera.pos.x += &speed; } + let new = f32::from(&camera.pos.x); + if (new - old).abs() > 0.5 { + println!("{} + {} = {}", old, f32::from(speed), new); + } } } diff --git a/src/client/render/tile/compute.wgsl b/src/client/render/tile/compute.wgsl new file mode 100644 index 0000000..6556ee0 --- /dev/null +++ b/src/client/render/tile/compute.wgsl @@ -0,0 +1,35 @@ + +@fragment +fn fs_main( + in: VertexOutput, +) -> @location(0) vec4 { + let dec = i32(1) << 13; + let c = vec2(in.world_pos * f32(dec)); + let cx = c.x; + let cy = c.y; + var x = 0; + var y = 0; + var i = 0u; + let thresh = 2 * dec; + let thresh2 = thresh * thresh; + let max = 50u; + loop { + let x2 = x * x; + let y2 = y * y; + if x2 + y2 > thresh2 || i >= max { + break; + } + y = (2 * x * y) / dec + c.y; + x = (x2 - y2) / dec + c.x; + i += 1u; + } + var color = vec3(0.0, 0.0, 0.0); + if i != max { + let pi = 3.1415; + let hue = f32(i) / 30.0; + color.r = cos(hue); + color.g = cos(hue - 2.0 * pi / 3.0); + color.b = cos(hue - 4.0 * pi / 3.0); + } + return vec4(color, 1.0); +} diff --git a/src/client/render/tile/data.rs b/src/client/render/tile/data.rs index 93730a5..6ea9c93 100644 --- a/src/client/render/tile/data.rs +++ b/src/client/render/tile/data.rs @@ -1,30 +1,61 @@ use nalgebra::Vector2; -#[repr(C, align(8))] -#[derive(Clone, Copy)] +use crate::util::FixedDec; + +use super::Camera; + +const VIEW_ALIGN: usize = 4 * 2; + pub struct View { - pub scale: Vector2, + pub bytes: Vec, +} + +impl View { + pub fn bytes(&self) -> &[u8] { + &self.bytes + } } impl Default for View { fn default() -> Self { - Self { - scale: Vector2::zeros(), - } + let val = FixedDec::from_parts(false, 0, vec![0, 0, 0]); + Self::new(Vector2::zeros(), 0, &val, &val, &val) } } impl View { - pub fn new(size: &Vector2) -> Self { + fn new(stretch: Vector2, level: i32, scale: &FixedDec, x: &FixedDec, y: &FixedDec) -> Self { + let mut bytes = Vec::new(); + bytes.extend(bytemuck::cast_slice(&[stretch.x, stretch.y])); + bytes.extend(level.to_le_bytes()); + scale.to_bytes(&mut bytes); + x.to_bytes(&mut bytes); + y.to_bytes(&mut bytes); + let rem = bytes.len() % VIEW_ALIGN; + if rem != 0 { + bytes.extend((0..(VIEW_ALIGN - rem)).map(|_| 0)); + } + Self{ bytes } + } + + pub fn from_camera_size(camera: &Camera, size: &Vector2) -> Self { + let mut x = camera.pos.x.clone(); + x.set_whole_len(1); + x.set_dec_len(2); + let mut y = camera.pos.y.clone(); + y.set_whole_len(1); + y.set_dec_len(2); + let fsize: Vector2 = size.cast(); - let scale = if size.x < size.y { + let stretch = if size.x < size.y { Vector2::new(fsize.x / fsize.y, 1.0) } else { Vector2::new(1.0, fsize.y / fsize.x) }; - View { scale } + + let mut scale = camera.zoom.mult().clone(); + scale.set_precision(3); + + Self::new(stretch, camera.zoom.level(), &scale, &x, &y) } } - -unsafe impl bytemuck::Pod for View {} -unsafe impl bytemuck::Zeroable for View {} diff --git a/src/client/render/tile/layout.rs b/src/client/render/tile/layout.rs index 446b17a..35bd404 100644 --- a/src/client/render/tile/layout.rs +++ b/src/client/render/tile/layout.rs @@ -1,19 +1,23 @@ -use super::{util::Uniform, View}; +use super::{util::Storage, View}; pub struct TileLayout { render_bind_layout: wgpu::BindGroupLayout, render_pipeline_layout: wgpu::PipelineLayout, format: wgpu::TextureFormat, - pub view: Uniform, + pub view: Storage, } impl TileLayout { pub fn init(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> Self { - let view = Uniform::init(device, "view"); + let view = Storage::init_with( + device, + "view", + View::default().bytes(), + ); let render_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[view.bind_group_layout_entry(0)], + entries: &[view.bind_group_layout_entry(0, true)], label: Some("voxel render"), }); diff --git a/src/client/render/tile/mod.rs b/src/client/render/tile/mod.rs index b0e9cfc..2e8ba0a 100644 --- a/src/client/render/tile/mod.rs +++ b/src/client/render/tile/mod.rs @@ -36,7 +36,7 @@ impl TilePipeline { camera: &Camera, size: &Vector2, ) { - self.view.update(device, encoder, belt, View::new(size)); + self.view.update(device, encoder, belt, View::from_camera_size(camera, size).bytes()); } pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { diff --git a/src/client/render/tile/render.wgsl b/src/client/render/tile/render.wgsl index ded747c..9d57402 100644 --- a/src/client/render/tile/render.wgsl +++ b/src/client/render/tile/render.wgsl @@ -1,24 +1,29 @@ -// Vertex shader +const LEN: u32 = 3; +const ILEN: i32 = 3; +const LEN2: u32 = LEN * 2; + +struct View { + stretch: vec2, + level: i32, + scale: FixedDec, + x: FixedDec, + y: FixedDec, +} + +struct FixedDec { + sign: u32, + dec: i32, + parts: array, +} + +@group(0) @binding(0) +var view: View; struct VertexOutput { @builtin(position) vertex_pos: vec4, @location(0) world_pos: vec2, }; -struct View { - scale: vec2, - // x_dec: i32, - // y_dec: i32, - // prec: u32, -} - -@group(0) @binding(0) -var view: View; -// @group(0) @binding(1) -// var vx: array; -// @group(0) @binding(2) -// var vy: array; - @vertex fn vs_main( @builtin(vertex_index) vi: u32, @@ -33,34 +38,30 @@ fn vs_main( out.vertex_pos = vec4(pos.x, -pos.y, 0.0, 1.0); out.world_pos = pos; out.world_pos.y *= -1.0; - out.world_pos *= view.scale; + out.world_pos *= view.stretch; return out; } -// const PREC = 2; - @fragment fn fs_main( in: VertexOutput, ) -> @location(0) vec4 { - let dec = i32(1) << 13; - let c = vec2(in.world_pos * f32(dec)); - let cx = c.x; - let cy = c.y; - var x = 0; - var y = 0; + let cx = add(mul(from_f32(in.world_pos.x), view.scale), view.x); + let cy = add(mul(from_f32(in.world_pos.y), view.scale), view.y); + var x = zero(); + var y = zero(); + let two = from_f32(2.0); + let thresh = from_f32(2.0 * 2.0); var i = 0u; - let thresh = 2 * dec; - let thresh2 = thresh * thresh; - let max = 50u; + let max = 50u + (1u << u32(view.level)); loop { - let x2 = x * x; - let y2 = y * y; - if x2 + y2 > thresh2 || i >= max { + let x2 = mul(x, x); + let y2 = mul(y, y); + if gt(add(x2, y2), thresh) || i >= max { break; } - y = (2 * x * y) / dec + c.y; - x = (x2 - y2) / dec + c.x; + y = add(mul(two, mul(x, y)), cy); + x = add(sub(x2, y2), cx); i += 1u; } var color = vec3(0.0, 0.0, 0.0); @@ -74,37 +75,247 @@ fn fs_main( return vec4(color, 1.0); } -// @fragment -// fn fs_main( -// in: VertexOutput, -// ) -> @location(0) vec4 { -// let dec = i32(1) << 13; -// let c = vec2(in.world_pos * f32(dec)); -// let cx = c.x; -// let cy = c.y; -// var x = 0; -// var y = 0; -// var i = 0u; -// let thresh = 2 * dec; -// let thresh2 = thresh * thresh; -// let max = 50u + u32(in.zoom); -// loop { -// let x2 = x * x; -// let y2 = y * y; -// if x2 + y2 > thresh2 || i >= max { -// break; -// } -// y = (2 * x * y) / dec + c.y; -// x = (x2 - y2) / dec + c.x; -// i += 1u; -// } -// var color = vec3(0.0, 0.0, 0.0); -// if i != max { -// let pi = 3.1415; -// let hue = f32(i) / 30.0; -// color.r = cos(hue); -// color.g = cos(hue - 2.0 * pi / 3.0); -// color.b = cos(hue - 4.0 * pi / 3.0); -// } -// return vec4(color, 1.0); -// } +fn add(lhs: FixedDec, rhs: FixedDec) -> FixedDec { + var dest = FixedDec(); + dest.sign = lhs.sign; + dest.dec = max(lhs.dec, rhs.dec); + var carry = false; + let rhs_offset = rhs.dec - dest.dec; + let lhs_offset = lhs.dec - dest.dec; + var i = ILEN; + if lhs.sign == rhs.sign { + while i > 0 { + i -= 1; + let a = at(lhs, i + lhs_offset); + let b = at(rhs, i + rhs_offset); + let res = a + b + u32(carry); + dest.parts[i] = res; + carry = res < a; + } + if carry { + var i = ILEN - 1; + while i > 0 { + i -= 1; + dest.parts[i + 1] = dest.parts[i]; + } + dest.parts[0] = 1u; + dest.dec += 1; + } + } else { + while i > 0 { + i -= 1; + let a = at(lhs, i + lhs_offset); + let b = at(rhs, i + rhs_offset); + let res = a - b - u32(carry); + dest.parts[i] = res; + carry = a < res; + } + if carry { + dest.sign = u32(dest.sign == 0); + var i = 0; + while i < ILEN { + dest.parts[i] = ~dest.parts[i]; + i += 1; + } + } + } + return dest; +} + +fn sub(lhs: FixedDec, rhs: FixedDec) -> FixedDec { + var r = rhs; + r.sign = u32(r.sign == 0); + return add(lhs, r); +} + +fn at(dec: FixedDec, i: i32) -> u32 { + if i < 0 || i >= ILEN { + return 0u; + } + var parts = dec.parts; + return parts[i]; +} + +const POS: u32 = 0u; +const NEG: u32 = 1u; + +fn mul(lhs: FixedDec, rhs: FixedDec) -> FixedDec { + let sign = u32(lhs.sign != rhs.sign); + var parts = array(); + var dec = lhs.dec + rhs.dec; + var lparts = lhs.parts; + var rparts = rhs.parts; + + var i = LEN; + while i > 0 { + i -= 1u; + let x = lparts[i]; + var carry: u32 = 0; + var j = LEN; + while j > 0 { + j -= 1u; + let y = rparts[j]; + + // widening mul + let lsb = x * y; + let a = x & 0xffff; + let b = x >> 16; + let c = y & 0xffff; + let d = y >> 16; + let ad = a * d + ((a * c) >> 16); + let bc = b * c; + let car = ad > (0xffffffff - bc); + let msb = ((ad + bc) >> 16) + (u32(car) << 16) + b * d; + + let k = i + j + 1; + let res = parts[k] + lsb; + let carry1 = res < lsb; + let res2 = res + carry; + let carry2 = res2 < res; + parts[k] = res2; + carry = u32(carry1 || carry2) + msb; + } + parts[i] = carry; + } + + var new_parts = array(); + i = 0u; + while i < LEN2 && parts[i] == 0 { + dec -= 1; + i += 1u; + } + var j = 0u; + while j < LEN && (i + j) < LEN2 { + new_parts[j] = parts[i + j]; + j += 1u; + } + return FixedDec(sign, dec, new_parts); +} + +fn gt(x: FixedDec, y: FixedDec) -> bool { + if x.dec > y.dec { + return true; + } + if y.dec > x.dec { + return false; + } + return x.parts[0] > y.parts[0]; +} + +fn to_f32(value: FixedDec) -> f32 { + var parts = value.parts; + + var sign = value.sign * (1u << 31); + var skip_count = 0; + + while skip_count < ILEN && parts[skip_count] == 0 { + skip_count += 1; + } + + if skip_count == ILEN { + if value.sign == POS { + return 0.0; + } else { + return -0.0; + } + } + let v = parts[skip_count]; + var start = countLeadingZeros(v) + 1; + let exp_i = (value.dec - skip_count) * 32 - i32(start); + var frac_sh = 0u; + var exp = 0u; + if exp_i >= -127 { + if exp_i == -127 { + start -= 1u; + } + exp = u32(exp_i + 127); + } else { + frac_sh = u32(-(exp_i + 32 * 4 - 1)); + if frac_sh < 23 { + start -= 1u; + } else { + return 0.0; + } + }; + var frac: u32; + if start > 9 { + let sh = start - 9; + let next_i = skip_count + 1; + var v2 = 0u; + if next_i < ILEN { + v2 = parts[next_i] >> (32 - sh); + } + frac = (v << sh) + v2; + } else { + frac = v >> (9 - start); + }; + frac &= ~(1u << 23); + let res = (frac >> frac_sh) + (exp << 23) + sign; + return bitcast(res); +} + +const INV_SIGN_MASK: u32 = (1u << 31) - 1; +const FRAC_BIT: u32 = 1u << 23; +const FRAC_MASK: u32 = FRAC_BIT - 1; + +fn from_f32(value: f32) -> FixedDec { + let raw = bitcast(value) & INV_SIGN_MASK; + var exp = i32(raw >> 23) - 127; + var frac = raw & FRAC_MASK; + var start = -exp; + if exp == -127 { + exp = -126; + start = -exp; + } else { + frac += FRAC_BIT; + start -= 1; + } + let end = -exp + 23; + let start_i = div_euclid(start, 32); + let end_i = div_euclid(end - 1, 32); + var parts = array(); + var dec = -start_i; + if start_i == end_i { + let val = frac << u32(8 - rem_euclid(start, 32)); + if val != 0 { + parts[0] = val; + } + } else { + let s = u32(rem_euclid(end, 32)); + let val_high = frac >> s; + let val_low = frac << (32 - s); + var i = 0; + if val_high != 0 { + parts[0] = val_high; + i += 1; + } else { + dec -= 1; + } + if val_low != 0 { + parts[i] = val_low; + } + } + if parts[0] == 0 && parts[1] == 0 { + dec = 0; + } + return FixedDec( + u32(value < 0.0), + dec, + parts, + ); +} + +fn zero() -> FixedDec { + return FixedDec(0, 0, array()); +} + +fn div_euclid(x: i32, y: i32) -> i32 { + if x < 0 { + return -((-x - 1) / y) - 1; + } + return x / y; +} + +fn rem_euclid(x: i32, y: i32) -> i32 { + return x - div_euclid(x, y) * y; +} diff --git a/src/client/render/util/mod.rs b/src/client/render/util/mod.rs index d130511..206cdd2 100644 --- a/src/client/render/util/mod.rs +++ b/src/client/render/util/mod.rs @@ -5,8 +5,10 @@ mod texture; mod timer; mod uniform; mod array; +mod storage; pub use texture::*; pub use timer::*; pub use uniform::*; pub use array::*; +pub use storage::*; diff --git a/src/client/render/util/storage.rs b/src/client/render/util/storage.rs new file mode 100644 index 0000000..d36fdfc --- /dev/null +++ b/src/client/render/util/storage.rs @@ -0,0 +1,71 @@ +use std::marker::PhantomData; + +use wgpu::util::DeviceExt; + +pub struct Storage { + buffer: wgpu::Buffer, +} + +impl Storage { + pub fn init(device: &wgpu::Device, name: &str) -> Self { + Self { + buffer: device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(&(name.to_owned() + " Uniform Buf")), + contents: bytemuck::cast_slice(&[T::default()]), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }), + } + } + pub fn init_with(device: &wgpu::Device, name: &str, data: &[u8]) -> Self { + Self { + buffer: device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(&(name.to_owned() + " Uniform Buf")), + contents: data, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }), + } + } + pub fn update( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, + data: &[u8], + ) { + let mut view = belt.write_buffer( + encoder, + &self.buffer, + 0, + unsafe { + std::num::NonZeroU64::new_unchecked(std::mem::size_of_val(data) as u64) + }, + device, + ); + view.copy_from_slice(data); + } + + pub fn bind_group_layout_entry( + &self, + binding: u32, + read_only: bool, + ) -> wgpu::BindGroupLayoutEntry { + wgpu::BindGroupLayoutEntry { + binding, + visibility: wgpu::ShaderStages::VERTEX + | wgpu::ShaderStages::FRAGMENT + | wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + } + } + pub fn bind_group_entry(&self, binding: u32) -> wgpu::BindGroupEntry { + wgpu::BindGroupEntry { + binding, + resource: self.buffer.as_entire_binding(), + } + } +} diff --git a/src/main.rs b/src/main.rs index 06ac495..885156f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,6 @@ #![feature(bigint_helper_methods)] +#![feature(int_roundings)] +#![feature(unbounded_shifts)] use client::ClientApp; diff --git a/src/util/fixed/conversion.rs b/src/util/fixed/conversion.rs index c613853..f33cfbf 100644 --- a/src/util/fixed/conversion.rs +++ b/src/util/fixed/conversion.rs @@ -45,15 +45,10 @@ impl From for FixedDec { if parts.is_empty() { dec = 0; } - let s = Self { - sign: POS, + Self { + sign: value.is_sign_negative(), dec, parts, - }; - if value.is_sign_negative() { - -&s - } else { - s } } } @@ -66,16 +61,8 @@ impl From for f32 { impl From<&FixedDec> for f32 { fn from(value: &FixedDec) -> Self { - if value.is_zero() { - return if value.sign == POS { 0.0 } else { -0.0 }; - } - let mut sign = 0; - let value = if value.is_neg() { - sign = 1 << 31; - &-value - } else { - value - }; + let sign = if value.is_neg() { 1 << 31 } else { 0 }; + let mut skip_count = 0; let mut iter = value.parts.iter().peekable(); @@ -85,7 +72,7 @@ impl From<&FixedDec> for f32 { } let Some(v) = iter.next() else { - return 0.0; + return if value.is_pos() { 0.0 } else { -0.0 }; }; let mut start = v.leading_zeros() + 1; let exp_i = (value.dec - skip_count) * 32 - start as i32; @@ -115,3 +102,23 @@ impl From<&FixedDec> for f32 { f32::from_bits(res) } } + +impl From for FixedDec { + fn from(value: u32) -> Self { + Self { + dec: 1, + sign: POS, + parts: vec![value], + } + } +} + +impl From for FixedDec { + fn from(value: i32) -> Self { + Self { + dec: 1, + sign: value.is_negative(), + parts: vec![value.try_into().unwrap_or((-value) as u32)], + } + } +} diff --git a/src/util/fixed/mod.rs b/src/util/fixed/mod.rs index f201587..eba94f8 100644 --- a/src/util/fixed/mod.rs +++ b/src/util/fixed/mod.rs @@ -9,6 +9,14 @@ use std::fmt::{Binary, Display}; const POS: bool = false; const NEG: bool = true; +// dec is from the left instead of from the right +// because this is a fractal viewer, so it's expected +// that people zoom in instead of out; parts also has +// the most significant u32 at 0, so to zoom in you just +// have to add to the vec instead of insert at 0 +// Might want to try to make it abstract over the direction +// of growth, or use a VecDeque, but doesn't seem worth for +// now #[derive(Debug, Clone, PartialEq)] pub struct FixedDec { sign: bool, @@ -17,6 +25,10 @@ pub struct FixedDec { } impl FixedDec { + pub fn from_parts(sign: bool, dec: i32, parts: Vec) -> Self { + Self { sign, dec, parts } + } + pub fn zeros() -> Self { Self::zero() } @@ -41,11 +53,7 @@ impl FixedDec { } pub fn trim(&mut self) { - let rem_beg = self - .parts - .iter() - .take_while(|&&x| x == 0) - .count(); + let rem_beg = self.parts.iter().take_while(|&&x| x == 0).count(); self.parts.drain(0..rem_beg); let rem_end = self.parts.iter().rev().take_while(|&&x| x == 0).count(); self.parts.truncate(self.parts.len() - rem_end); @@ -55,6 +63,37 @@ impl FixedDec { self.dec -= rem_beg as i32; } } + + pub fn set_whole_len(&mut self, len: i32) { + let diff = len - self.dec; + self.parts + .splice(0..usize::try_from(-diff).unwrap_or(0), (0..diff).map(|_| 0)); + self.dec += diff; + if self.parts.is_empty() { + self.dec = 0; + } + } + + pub fn set_dec_len(&mut self, len: i32) { + let len = usize::try_from(len + self.dec).unwrap_or(0); + self.parts.resize(len, 0); + if self.parts.is_empty() { + self.dec = 0; + } + } + + pub fn set_precision(&mut self, prec: usize) { + self.parts.resize(prec, 0); + if self.parts.is_empty() { + self.dec = 0; + } + } + + pub fn to_bytes(&self, bytes: &mut Vec) { + bytes.extend((self.sign as u32).to_le_bytes()); + bytes.extend(self.dec.to_le_bytes()); + bytes.extend(self.parts.iter().flat_map(|p| p.to_le_bytes())); + } } impl Display for FixedDec { @@ -70,8 +109,9 @@ impl Binary for FixedDec { } if self.dec < 0 { write!(f, ".")?; - for _ in 0..(-self.dec) { - write!(f, "00000000000000000000000000000000")?; + write!(f, "00000000000000000000000000000000")?; + for _ in 0..(-self.dec - 1) { + write!(f, "_00000000000000000000000000000000")?; } } for (i, part) in self.parts.iter().enumerate() { @@ -82,6 +122,12 @@ impl Binary for FixedDec { } write!(f, "{:032b}", part)?; } + let diff = usize::try_from(self.dec) + .unwrap_or(0) + .saturating_sub(self.parts.len()); + for _ in 0..diff { + write!(f, "_00000000000000000000000000000000")?; + } Ok(()) } } diff --git a/src/util/fixed/op.rs b/src/util/fixed/op.rs index ccbabb2..3593f9a 100644 --- a/src/util/fixed/op.rs +++ b/src/util/fixed/op.rs @@ -16,19 +16,19 @@ impl Zero for FixedDec { } } -impl Shr for FixedDec { +impl Shr for FixedDec { type Output = Self; - fn shr(self, rhs: u32) -> Self::Output { - let mut parts = Vec::new(); - let sr = rhs % 32; - let sl = 32 - sr; + fn shr(self, rhs: i32) -> Self::Output { + let mut parts = Vec::with_capacity(self.parts.len()); + let sr = rhs.rem_euclid(32); + let sl = 32 - sr as u32; let mask = (1 << sr) - 1; - let dec = self.dec - (rhs / 32) as i32; + let dec = self.dec - rhs.div_floor(32); let mut rem = 0; for part in self.parts { parts.push((part >> sr) ^ rem); - rem = (part & mask) << sl; + rem = (part & mask).unbounded_shl(sl); } if rem != 0 { parts.push(rem); @@ -98,22 +98,31 @@ impl Add for &FixedDec { } } -fn add(dest: &mut FixedDec, at: &impl Fn(usize) -> u32, rhs: &FixedDec) { +fn add(dest: &mut FixedDec, src: &impl Fn(usize) -> u32, rhs: &FixedDec) { let mut carry = false; - let same_sign = dest.sign == rhs.sign; let rhs_offset = rhs.dec - dest.dec; - for i in (0..dest.parts.len()).rev() { - let a = at(i); - let b = rhs.part(i as i32 + rhs_offset); - (dest.parts[i], carry) = carry_add(a, b, same_sign, carry); - } - if same_sign { + if dest.sign == rhs.sign { + for i in (0..dest.parts.len()).rev() { + let a = src(i); + let b = rhs.part(i as i32 + rhs_offset); + (dest.parts[i], carry) = a.carrying_add(b, carry); + } if carry { dest.parts.insert(0, 1); dest.dec += 1; } - } else if carry { - dest.sign = !dest.sign + } else { + for i in (0..dest.parts.len()).rev() { + let a = src(i); + let b = rhs.part(i as i32 + rhs_offset); + (dest.parts[i], carry) = a.borrowing_sub(b, carry); + } + if carry { + dest.sign = !dest.sign; + for part in &mut dest.parts { + *part = !*part; + } + } } } @@ -125,16 +134,6 @@ fn new_dec(x: &FixedDec, y: &FixedDec) -> (i32, usize) { (dec, len) } -fn carry_add(a: u32, b: u32, same_sign: bool, carry: bool) -> (u32, bool) { - if same_sign { - a.carrying_add(b, carry) - } else { - let (res, c) = a.overflowing_sub(b); - let (res, c2) = res.overflowing_sub(carry as u32); - (res, c || c2) - } -} - impl Sub for &FixedDec { type Output = FixedDec; @@ -147,7 +146,7 @@ impl Sub for FixedDec { type Output = Self; fn sub(mut self, rhs: Self) -> Self::Output { - self += &(-rhs); + self -= &rhs; self } } @@ -192,8 +191,7 @@ impl Mul for &FixedDec { // let (lsb, msb) = mul_lmsb(x, y); let k = i + j + 1; let (res, carry1) = parts[k].overflowing_add(lsb); - parts[k] = res; - let (res, carry2) = parts[k].overflowing_add(carry); + let (res, carry2) = res.overflowing_add(carry); parts[k] = res; // dude I have no clue if this can overflow; I know msb can take 1 without // overflowing, but I'm not sure if 2 can get here when it's max @@ -216,6 +214,22 @@ impl Mul for FixedDec { } } +impl Mul<&FixedDec> for FixedDec { + type Output = FixedDec; + + fn mul(self, rhs: &FixedDec) -> Self::Output { + &self * rhs + } +} + +impl Mul for &FixedDec { + type Output = FixedDec; + + fn mul(self, rhs: FixedDec) -> Self::Output { + self * &rhs + } +} + fn mul_lmsb(x: u32, y: u32) -> (u32, u32) { let lsb = x.wrapping_mul(y); let a = x & 0xffff; diff --git a/src/util/fixed/test.rs b/src/util/fixed/test.rs index 7615b72..9ad90da 100644 --- a/src/util/fixed/test.rs +++ b/src/util/fixed/test.rs @@ -1,37 +1,50 @@ use super::*; -macro_rules! assert_bits_eq { +const EPSILON: f32 = 0.000001; + +macro_rules! assert_eq_f32 { ($left:expr, $right:expr, $dec:expr $(,)?) => { + assert_eq_bits!($left, $left.to_bits(), $right, $right.to_bits(), $dec) + }; + ($left:expr, $right:expr, $dec:expr, $arg:tt, $($args:tt)+) => { + assert_eq_bits!($left, $left.to_bits(), $right, $right.to_bits(), $dec, $arg, $($args)+) + }; +} + +macro_rules! assert_eq_bits { + ($left:expr, $left_bits:expr, $right:expr, $right_bits:expr, $dec:expr $(,)?) => { assert!( - $left == $right, + ($left - $right).abs() < EPSILON, "\n left: {:032b} = {:?}\n right: {:032b} = {:?}\n from: {:?}", - $left.to_bits(), + $left_bits, $left, - $right.to_bits(), + $right_bits, $right, $dec, ) }; - ($left:expr, $right:expr, $dec:expr, $arg:tt, $($args:tt)+) => { + ($left:expr, $left_bits:expr, $right:expr, $right_bits:expr, $dec:expr, $arg:tt, $($args:tt)+) => { assert!( - $left == $right, + ($left - $right).abs() < EPSILON, concat!("\n expr: ", $arg, "\n left: {:032b} = {:?}\n right: {:032b} = {:?}\n from: {:?}"), $($args)+, - $left.to_bits(), + $left_bits, $left, - $right.to_bits(), + $right_bits, $right, $dec, ) - } + }; } #[test] fn conversion() { fn test(x: f32) { let dec = FixedDec::from(x); - assert_bits_eq!(x, f32::from(&dec), dec) + assert_eq_f32!(x, f32::from(&dec), dec) } + test(0.0); + test(-0.0); test(f32::from_bits(0b00000000_00000000_00000000_00000001)); test(f32::from_bits(0b00000000_01000000_00000000_00000001)); test(f32::from_bits(0b00010000_01000000_00000000_00000001)); @@ -53,44 +66,67 @@ fn conversion() { #[test] fn add_sub() { - fn test_add(x: f32, y: f32) { + fn test(x: f32, y: f32) { let a = x + y; let dec = FixedDec::from(x) + FixedDec::from(y); - assert_bits_eq!(a, f32::from(&dec), dec, "{} + {}", x, y); + assert_eq_f32!(a, f32::from(&dec), dec, "{} + {}", x, y); } - test_add(0.25, 0.75); - test_add(1.25, 0.125); - test_add(1.25, -0.125); - test_add(100.25, -0.125); - test_add(-1.25, 0.125); - test_add(-100.25, -0.125); - test_add(100.25, -0.125); - // test_add(0.25, -0.00000000125); - test_add(0.25, -0.0000125); - test_add(100000000000000.0, -100000000000.0); + test(0.25, 0.75); + test(1.25, 0.125); + test(1.25, -0.125); + test(100.25, -0.125); + test(-1.25, 0.125); + test(-100.25, -0.125); + test(100.25, -0.125); + test(0.25, -0.00000000125); + test(0.25, -0.0000125); + test(100000000000000.0, -100000000000.0); + test(0.0000310, -0.0042042); + test(-0.0000310, 0.0002042); + let x = -0.0016598016; + let y = 0.0028538946; + let mut a = FixedDec::from(x); + a.set_whole_len(1); + let b = FixedDec::from(y); + let res = a + b; + assert_eq_f32!(x + y, f32::from(&res), res, "{} + {}", x, y); } #[test] fn mul() { - fn test_mul(x: f32, y: f32) { + fn test(x: f32, y: f32) { let a = x * y; let dec = FixedDec::from(x) * FixedDec::from(y); - assert_bits_eq!(a, f32::from(&dec), dec, "{:?} * {:?}", x, y); + assert_eq_f32!(a, f32::from(&dec), dec, "{:?} * {:?}", x, y); } - test_mul(0.0, 0.0); - test_mul(1.0, 1.0); - test_mul(1.0, 0.0); - test_mul(2.0, 1.0); - test_mul(2.0, 0.5); - test_mul(20.0, 0.245); - test_mul(0.03819, 0.0183488); - test_mul(30492.39, 9130.391); + test(0.0, 0.0); + test(1.0, 1.0); + test(1.0, 0.0); + test(2.0, 1.0); + test(2.0, 0.5); + test(20.0, 0.245); + test(0.03819, 0.0183488); + test(30492.39, 9130.391); - test_mul(2.0, -1.0); - test_mul(0.0, -1.0); - test_mul(-1.0, 0.0); - test_mul(1.0, -1.0); - test_mul(2.0, -1.20904); - test_mul(-249.0, -1.20904); - test_mul(-30492.39, 9130.391); + test(2.0, -1.0); + test(0.0, -1.0); + test(-1.0, 0.0); + test(1.0, -1.0); + test(2.0, -1.20904); + test(-249.0, -1.20904); + test(-30492.39, 9130.391); + test(-249.0, 0.000031421); +} + +#[test] +fn shr() { + fn test(x: i32, y: i32) { + let a = (x as f32) / 2f32.powi(y); + let dec = FixedDec::from(x) >> y; + assert_eq_f32!(a, f32::from(&dec), dec, "{:?} * {:?}", x, y); + } + test(1, 3); + test(1, -3); + test(1, 33); + test(1, -33); }