| | | @@ 変更前 1行目〜 / 変更後 1行目〜 @@ |
| 1 | 1 | | //! コンクリート・生コンの必要量。 |
| 2 | | − | //! v1: 素直に書いた版(f64 と round / ceil、表示は (x * 1000).round() / 1000 を {} で)。 |
| 2 | + | //! v2: 入力の小数を i128 の分数(分子・分母)のまま計算し、四捨五入と切り上げを整数の商と余りで行う。 |
| 3 | + | //! f64 だと 0.3 ÷ 0.1 が 3.0000000000000004 になって切り上げで4袋になり、0.1125 が 0.11249999999999999 になって四捨五入で外れる。 |
| 3 | 4 | | |
| 4 | 5 | | use crate::json::{n, obj, s, Value}; |
| 5 | 6 | | |
| 6 | | − | fn num(t: &str) -> Result<f64, String> { |
| 7 | | − | t.parse::<f64>().map_err(|e| format!("{e}")) |
| 7 | + | /// 分数 p / q(q > 0、約分済み) |
| 8 | + | #[derive(Clone, Copy)] |
| 9 | + | struct Frac(i128, i128); |
| 10 | + | |
| 11 | + | fn gcd(a: i128, b: i128) -> i128 { |
| 12 | + | if b == 0 { a.abs() } else { gcd(b, a % b) } |
| 8 | 13 | | } |
| 9 | 14 | | |
| 10 | | − | fn fmt_vol(x: f64) -> String { |
| 11 | | − | format!("{}", (x * 1000.0).round() / 1000.0) |
| 15 | + | impl Frac { |
| 16 | + | fn new(p: i128, q: i128) -> Frac { |
| 17 | + | let g = gcd(p, q).max(1); |
| 18 | + | let sg = if q < 0 { -1 } else { 1 }; |
| 19 | + | Frac(sg * p / g, sg * q / g) |
| 20 | + | } |
| 21 | + | /// "2.4" → 12/5("-5" も読む) |
| 22 | + | fn parse(t: &str) -> Result<Frac, String> { |
| 23 | + | let (int, frac) = t.split_once('.').unwrap_or((t, "")); |
| 24 | + | if frac.len() > 18 || !frac.bytes().all(|b| b.is_ascii_digit()) { |
| 25 | + | return Err(format!("invalid decimal: {t}")); |
| 26 | + | } |
| 27 | + | let p: i128 = format!("{int}{frac}").parse().map_err(|_| format!("invalid decimal: {t}"))?; |
| 28 | + | Ok(Frac::new(p, 10i128.pow(frac.len() as u32))) |
| 29 | + | } |
| 30 | + | fn mul(self, o: Frac) -> Frac { |
| 31 | + | Frac::new(self.0 * o.0, self.1 * o.1) |
| 32 | + | } |
| 33 | + | fn div(self, o: Frac) -> Frac { |
| 34 | + | Frac::new(self.0 * o.1, self.1 * o.0) |
| 35 | + | } |
| 36 | + | fn add(self, o: Frac) -> Frac { |
| 37 | + | Frac::new(self.0 * o.1 + o.0 * self.1, self.1 * o.1) |
| 38 | + | } |
| 39 | + | fn int(k: i128) -> Frac { |
| 40 | + | Frac(k, 1) |
| 41 | + | } |
| 42 | + | /// 0 以上の値を整数に四捨五入(0.5 は大きい方へ)= floor((2p + q) / 2q) |
| 43 | + | fn half_up(self) -> i128 { |
| 44 | + | (2 * self.0 + self.1) / (2 * self.1) |
| 45 | + | } |
| 46 | + | /// 0 以上の値の切り上げ |
| 47 | + | fn ceil(self) -> i128 { |
| 48 | + | (self.0 + self.1 - 1) / self.1 |
| 49 | + | } |
| 50 | + | } |
| 51 | + | |
| 52 | + | fn fmt_vol(x: Frac) -> String { |
| 53 | + | let k = x.mul(Frac::int(1000)).half_up(); // 小数第3位まで(千分の1単位の整数) |
| 54 | + | let t = format!("{}.{:03}", k / 1000, k % 1000); |
| 55 | + | t.trim_end_matches('0').trim_end_matches('.').to_string() |
| 12 | 56 | | } |
| 13 | 57 | | |
| 14 | 58 | | pub fn calc(area: Option<&str>, width: Option<&str>, depth: Option<&str>, thickness_cm: &str, loss_pct: &str, bag_m3: &str) -> Result<Value, String> { |
| 15 | 59 | | let a = match area { |
| 16 | | − | Some(t) => num(t)?, |
| 17 | | − | None => num(width.ok_or("width がありません")?)? * num(depth.ok_or("depth がありません")?)?, |
| 60 | + | Some(t) => Frac::parse(t)?, |
| 61 | + | None => Frac::parse(width.ok_or("width がありません")?)?.mul(Frac::parse(depth.ok_or("depth がありません")?)?), |
| 18 | 62 | | }; |
| 19 | | − | let (t, loss, bag) = (num(thickness_cm)?, num(loss_pct)?, num(bag_m3)?); |
| 20 | | − | if a <= 0.0 || t <= 0.0 || loss < 0.0 || bag < 0.0 { |
| 63 | + | let (t, loss, bag) = (Frac::parse(thickness_cm)?, Frac::parse(loss_pct)?, Frac::parse(bag_m3)?); |
| 64 | + | if a.0 <= 0 || t.0 <= 0 || loss.0 < 0 || bag.0 < 0 { |
| 21 | 65 | | return Ok(obj(vec![("error", s("invalid_input"))])); |
| 22 | 66 | | } |
| 23 | | − | let v = a * (t / 100.0); |
| 24 | | − | let lv = v * (1.0 + loss / 100.0); |
| 25 | | − | let bags = if bag > 0.0 { n((v / bag).ceil() as i64) } else { Value::Null }; |
| 67 | + | let v = a.mul(t).div(Frac::int(100)); |
| 68 | + | let lv = v.mul(Frac::int(1).add(loss.div(Frac::int(100)))); |
| 69 | + | let bags = if bag.0 > 0 { n(v.div(bag).ceil()) } else { Value::Null }; |
| 26 | 70 | | Ok(obj(vec![ |
| 27 | 71 | | ("volume", s(fmt_vol(v))), |
| 28 | | − | ("liters", n((v * 1000.0).round() as i64)), |
| 72 | + | ("liters", n(v.mul(Frac::int(1000)).half_up())), |
| 29 | 73 | | ("loss_volume", s(fmt_vol(lv))), |
| 30 | | − | ("loss_liters", n((lv * 1000.0).round() as i64)), |
| 74 | + | ("loss_liters", n(lv.mul(Frac::int(1000)).half_up())), |
| 31 | 75 | | ("bags", bags), |
| 32 | 76 | | ])) |
| 33 | 77 | | } |